Skip to content
Merged
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
5 changes: 5 additions & 0 deletions actions/setup/js/safe_output_handler_manager.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const { createReviewBuffer } = require("./pr_review_buffer.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { createManifestLogger, ensureManifestExists, extractCreatedItemFromResult } = require("./safe_output_manifest.cjs");
const { loadCustomSafeOutputJobTypes } = require("./safe_output_helpers.cjs");
const { emitSafeOutputActionOutputs } = require("./safe_outputs_action_outputs.cjs");

/**
* Handler map configuration
Expand Down Expand Up @@ -1017,6 +1018,10 @@ async function main() {
core.info(`Exported ${codePushFailureCount} code push failure(s)`);
}

// Emit named action outputs (e.g. created_issue_number, created_issue_url)
// for the first successful result of each safe output type.
emitSafeOutputActionOutputs(processingResult);

// Ensure the manifest file always exists for artifact upload (even if no items were created).
// Skip in staged mode — no real items were created so no manifest should be emitted.
// Note: createManifestLogger() also calls ensureManifestExists() when the logger is created,
Expand Down
41 changes: 41 additions & 0 deletions actions/setup/js/safe_output_handler_manager.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -973,4 +973,45 @@ describe("Safe Output Handler Manager", () => {
expect(result.codePushFailures).toHaveLength(0);
});
});

describe("output emission via emitSafeOutputActionOutputs", () => {
it("processMessages result includes create_issue result with number and url for emission", async () => {
const messages = [{ type: "create_issue", title: "My Issue" }];
const mockHandler = vi.fn().mockResolvedValue({ number: 42, url: "https://github.com/owner/repo/issues/42" });
const handlers = new Map([["create_issue", mockHandler]]);

const result = await processMessages(handlers, messages);

const issueResult = result.results.find(r => r.type === "create_issue" && r.success);
expect(issueResult).toBeDefined();
expect(issueResult.result.number).toBe(42);
expect(issueResult.result.url).toBe("https://github.com/owner/repo/issues/42");
});

it("processMessages result with failed create_issue does not include success result for emission", async () => {
const messages = [{ type: "create_issue", title: "Failing Issue" }];
const mockHandler = vi.fn().mockRejectedValue(new Error("API error"));
const handlers = new Map([["create_issue", mockHandler]]);

const result = await processMessages(handlers, messages);

const successfulIssueResult = result.results.find(r => r.type === "create_issue" && r.success);
expect(successfulIssueResult).toBeUndefined();
});

it("core.setOutput is called with created_issue_number when create_issue succeeds", async () => {
const messages = [{ type: "create_issue", title: "My Issue" }];
const mockHandler = vi.fn().mockResolvedValue({ number: 7, url: "https://github.com/owner/repo/issues/7" });
const handlers = new Map([["create_issue", mockHandler]]);

const result = await processMessages(handlers, messages);

// Simulate what main() does: call emitSafeOutputActionOutputs with the result
const { emitSafeOutputActionOutputs } = await import("./safe_outputs_action_outputs.cjs");
emitSafeOutputActionOutputs(result);

expect(global.core.setOutput).toHaveBeenCalledWith("created_issue_number", "7");
expect(global.core.setOutput).toHaveBeenCalledWith("created_issue_url", "https://github.com/owner/repo/issues/7");
});
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

These tests don't actually verify the regression described in the PR (that safe_output_handler_manager.main() invokes emitSafeOutputActionOutputs). Because the test calls emitSafeOutputActionOutputs(result) directly, it would still pass even if the call were removed from main(). Consider adding an integration-style test that exercises main() (or mocking/spying on ./safe_outputs_action_outputs.cjs) to assert the emitter is invoked during normal handler-manager execution.

This issue also appears on line 977 of the same file.

Suggested change
});
});
it("main invokes emitSafeOutputActionOutputs during normal handler-manager execution", async () => {
// Spy on the real emitSafeOutputActionOutputs implementation
const outputsModule = await import("./safe_outputs_action_outputs.cjs");
const emitSpy = vi.spyOn(outputsModule, "emitSafeOutputActionOutputs");
const { main } = await import("./safe_output_handler_manager.cjs");
await main();
expect(emitSpy).toHaveBeenCalled();
const [firstCallArg] = emitSpy.mock.calls[0] || [];
expect(firstCallArg).toBeDefined();
expect(firstCallArg).toHaveProperty("results");
});

Copilot uses AI. Check for mistakes.
});
});
Loading