Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions pkg/cli/compile_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1981,6 +1981,81 @@ Call notify_dynamic to send notifications.
}
}

// TestCompileStagedSafeOutputsUpdateDiscussionWithTargetRepo verifies that the exact
// issue scenario from GitHub issue "staged: true does not work within individual safe outputs"
// compiles correctly: update-discussion with staged: true, target-repo, and additional options
// must include staged: true in GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG and exclude discussions: write.
func TestCompileStagedSafeOutputsUpdateDiscussionWithTargetRepo(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()

testWorkflow := `---
name: Staged Update Discussion
on:
workflow_dispatch:
permissions:
contents: read
engine: copilot
safe-outputs:
github-token: ${{ secrets.READ_DISCUSSIONS_TOKEN }}
allowed-github-references: [mona/lisa]
create-issue:
github-token: ${{ secrets.WRITE_ISSUE_TOKEN }}
title-prefix: "[MonaLisa]"
close-older-issues: true
expires: 7d
update-discussion:
staged: true
target: "*"
target-repo: mona/discussions
max: 120
labels:
allowed-labels:
- Label1
- Label2
---

Test that per-handler staged mode for update-discussion with cross-repo config compiles correctly.
`
testWorkflowPath := filepath.Join(setup.workflowsDir, "staged-update-discussion.md")
if err := os.WriteFile(testWorkflowPath, []byte(testWorkflow), 0644); err != nil {
t.Fatalf("Failed to write test workflow file: %v", err)
}

cmd := exec.Command(setup.binaryPath, "compile", testWorkflowPath)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("CLI compile command failed: %v\nOutput: %s", err, string(output))
}

lockFilePath := filepath.Join(setup.workflowsDir, "staged-update-discussion.lock.yml")
lockContent, err := os.ReadFile(lockFilePath)
if err != nil {
t.Fatalf("Failed to read lock file: %v", err)
}
lockContentStr := string(lockContent)

// GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG must include staged: true for update_discussion
if !strings.Contains(lockContentStr, `\"staged\":true`) {
t.Errorf("GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG should include staged:true for update_discussion\nLock file content:\n%s", lockContentStr)
}
Comment on lines +2038 to +2041
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The substring check for staged in the lockfile is likely over-escaped. Other integration tests look for JSON within GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG using \"key\":... (single backslash before quotes), but this test uses \\\"staged\\\":true (double backslashes). That will fail to match the emitted handler config (which is produced via %q and escapes quotes with a single backslash). Update the check to match the actual escaping used elsewhere (e.g., \"staged\":true).

Copilot uses AI. Check for mistakes.

// update-discussion is staged, so discussions: write must NOT be in safe_outputs job permissions
if strings.Contains(lockContentStr, "discussions: write") {
t.Errorf("Lock file should NOT contain 'discussions: write' when update-discussion is staged\nLock file content:\n%s", lockContentStr)
}

// create-issue is NOT staged, so issues: write must be present
if !strings.Contains(lockContentStr, "issues: write") {
t.Errorf("Lock file should contain 'issues: write' for non-staged create-issue\nLock file content:\n%s", lockContentStr)
}

// Global GH_AW_SAFE_OUTPUTS_STAGED must NOT be set (only individual handler is staged)
if strings.Contains(lockContentStr, `GH_AW_SAFE_OUTPUTS_STAGED: "true"`) {
t.Errorf("Lock file should NOT set global GH_AW_SAFE_OUTPUTS_STAGED when only individual handlers are staged\nLock file content:\n%s", lockContentStr)
}
}

// TestCompileWithActionsRepoFlag verifies that the --actions-repo flag causes the
// custom repository to be used in action mode instead of the default github/gh-aw-actions.
func TestCompileWithActionsRepoFlag(t *testing.T) {
Expand Down
184 changes: 184 additions & 0 deletions pkg/workflow/compiler_safe_outputs_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1828,6 +1828,190 @@ func TestHandlerConfigStagedMode(t *testing.T) {
},
handlerKey: "call_workflow",
},
{
name: "close_issue staged",
safeOutputs: &SafeOutputsConfig{
CloseIssues: &CloseEntityConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "close_issue",
},
{
name: "close_discussion staged",
safeOutputs: &SafeOutputsConfig{
CloseDiscussions: &CloseEntityConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "close_discussion",
},
{
name: "create_discussion staged",
safeOutputs: &SafeOutputsConfig{
CreateDiscussions: &CreateDiscussionsConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "create_discussion",
},
{
name: "hide_comment staged",
safeOutputs: &SafeOutputsConfig{
HideComment: &HideCommentConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "hide_comment",
},
{
name: "add_reviewer staged",
safeOutputs: &SafeOutputsConfig{
AddReviewer: &AddReviewerConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "add_reviewer",
},
{
name: "update_release staged",
safeOutputs: &SafeOutputsConfig{
UpdateRelease: &UpdateReleaseConfig{
UpdateEntityConfig: UpdateEntityConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
},
handlerKey: "update_release",
},
{
name: "remove_labels staged",
safeOutputs: &SafeOutputsConfig{
RemoveLabels: &RemoveLabelsConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "remove_labels",
},
{
name: "assign_to_user staged",
safeOutputs: &SafeOutputsConfig{
AssignToUser: &AssignToUserConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "assign_to_user",
},
{
name: "unassign_from_user staged",
safeOutputs: &SafeOutputsConfig{
UnassignFromUser: &UnassignFromUserConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "unassign_from_user",
},
{
name: "create_code_scanning_alert staged",
safeOutputs: &SafeOutputsConfig{
CreateCodeScanningAlerts: &CreateCodeScanningAlertsConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "create_code_scanning_alert",
},
{
name: "autofix_code_scanning_alert staged",
safeOutputs: &SafeOutputsConfig{
AutofixCodeScanningAlert: &AutofixCodeScanningAlertConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "autofix_code_scanning_alert",
},
{
name: "link_sub_issue staged",
safeOutputs: &SafeOutputsConfig{
LinkSubIssue: &LinkSubIssueConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "link_sub_issue",
},
{
name: "assign_milestone staged",
safeOutputs: &SafeOutputsConfig{
AssignMilestone: &AssignMilestoneConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "assign_milestone",
},
{
name: "set_issue_type staged",
safeOutputs: &SafeOutputsConfig{
SetIssueType: &SetIssueTypeConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "set_issue_type",
},
{
name: "mark_pull_request_as_ready_for_review staged",
safeOutputs: &SafeOutputsConfig{
MarkPullRequestAsReadyForReview: &MarkPullRequestAsReadyForReviewConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
},
},
handlerKey: "mark_pull_request_as_ready_for_review",
},
{
name: "update_discussion staged with target-repo (cross-repo config)",
safeOutputs: &SafeOutputsConfig{
UpdateDiscussions: &UpdateDiscussionsConfig{
UpdateEntityConfig: UpdateEntityConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{
Staged: true,
},
SafeOutputTargetConfig: SafeOutputTargetConfig{
Target: "*",
TargetRepoSlug: "mona/discussions",
},
},
},
},
handlerKey: "update_discussion",
},
}

for _, tt := range tests {
Expand Down
Loading