diff --git a/actions/setup/js/dispatch_workflow.test.cjs b/actions/setup/js/dispatch_workflow.test.cjs index 73890a1a31..a11b4ef690 100644 --- a/actions/setup/js/dispatch_workflow.test.cjs +++ b/actions/setup/js/dispatch_workflow.test.cjs @@ -195,6 +195,33 @@ describe("dispatch_workflow handler factory", () => { ); }); + it("should handle workflows with no inputs", async () => { + const config = { + workflows: ["no-inputs-workflow"], + workflow_files: { + "no-inputs-workflow": ".lock.yml", + }, + }; + const handler = await main(config); + + // Test with inputs property missing entirely + const message = { + type: "dispatch_workflow", + workflow_name: "no-inputs-workflow", + }; + + const result = await handler(message, {}); + + expect(result.success).toBe(true); + expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + workflow_id: "no-inputs-workflow.lock.yml", + ref: expect.any(String), + inputs: {}, // Should pass empty object even when inputs property is missing + }); + }); + it("should delay 5 seconds between dispatches", async () => { const config = { workflows: ["workflow1", "workflow2"], diff --git a/actions/setup/js/safe_outputs_tools_loader.cjs b/actions/setup/js/safe_outputs_tools_loader.cjs index 5d5af91538..56df445b7d 100644 --- a/actions/setup/js/safe_outputs_tools_loader.cjs +++ b/actions/setup/js/safe_outputs_tools_loader.cjs @@ -67,18 +67,14 @@ function attachHandlers(tools, handlers) { // Check if this is a dispatch_workflow tool (dynamic tool with workflow metadata) if (tool._workflow_name) { - // Create a custom handler that adds workflow_name and uses dispatch_workflow type + // Create a custom handler that wraps args in inputs and adds workflow_name const workflowName = tool._workflow_name; tool.handler = args => { - // Add workflow_name to the args and call default handler with dispatch_workflow type - const entry = { - ...args, + // Wrap args in inputs property to match dispatch_workflow schema + return handlers.defaultHandler("dispatch_workflow")({ + inputs: args, workflow_name: workflowName, - type: "dispatch_workflow", - }; - - // Use the default handler logic but with dispatch_workflow type - return handlers.defaultHandler("dispatch_workflow")({ ...args, workflow_name: workflowName }); + }); }; } }); diff --git a/actions/setup/js/safe_outputs_tools_loader.test.cjs b/actions/setup/js/safe_outputs_tools_loader.test.cjs index da322a9f00..f61aeb5f71 100644 --- a/actions/setup/js/safe_outputs_tools_loader.test.cjs +++ b/actions/setup/js/safe_outputs_tools_loader.test.cjs @@ -190,7 +190,7 @@ describe("safe_outputs_tools_loader", () => { expect(defaultHandler).toHaveBeenCalledWith("dispatch_workflow"); }); - it("should include workflow_name in dispatch_workflow handler args", () => { + it("should wrap args in inputs property for dispatch_workflow handler", () => { const tools = [{ name: "ci_workflow", description: "CI workflow", _workflow_name: "ci" }]; const mockHandlerFunction = vi.fn(); const defaultHandler = vi.fn(() => mockHandlerFunction); @@ -204,16 +204,63 @@ describe("safe_outputs_tools_loader", () => { const result = attachHandlers(tools, handlers); // Call the handler - const mockArgs = { input1: "value1" }; + const mockArgs = { input1: "value1", input2: "value2" }; result[0].handler(mockArgs); - // Verify the handler function was called with workflow_name - expect(mockHandlerFunction).toHaveBeenCalledWith( - expect.objectContaining({ - workflow_name: "ci", + // Verify the handler function was called with workflow_name and inputs wrapped + expect(mockHandlerFunction).toHaveBeenCalledWith({ + workflow_name: "ci", + inputs: { input1: "value1", - }) - ); + input2: "value2", + }, + }); + }); + + it("should handle dispatch_workflow with no inputs (empty object)", () => { + const tools = [{ name: "no_inputs_workflow", description: "No inputs workflow", _workflow_name: "no-inputs" }]; + const mockHandlerFunction = vi.fn(); + const defaultHandler = vi.fn(() => mockHandlerFunction); + const handlers = { + createPullRequestHandler: vi.fn(), + pushToPullRequestBranchHandler: vi.fn(), + uploadAssetHandler: vi.fn(), + defaultHandler: defaultHandler, + }; + + const result = attachHandlers(tools, handlers); + + // Call the handler with empty object (typical for MCP tools with no inputs) + result[0].handler({}); + + // Verify inputs is still included as empty object + expect(mockHandlerFunction).toHaveBeenCalledWith({ + workflow_name: "no-inputs", + inputs: {}, + }); + }); + + it("should handle dispatch_workflow with undefined args", () => { + const tools = [{ name: "undefined_workflow", description: "Undefined workflow", _workflow_name: "undefined-test" }]; + const mockHandlerFunction = vi.fn(); + const defaultHandler = vi.fn(() => mockHandlerFunction); + const handlers = { + createPullRequestHandler: vi.fn(), + pushToPullRequestBranchHandler: vi.fn(), + uploadAssetHandler: vi.fn(), + defaultHandler: defaultHandler, + }; + + const result = attachHandlers(tools, handlers); + + // Call the handler with undefined (edge case) + result[0].handler(undefined); + + // When args is undefined, inputs should not be included + // The dispatch_workflow handler will handle missing inputs property + expect(mockHandlerFunction).toHaveBeenCalledWith({ + workflow_name: "undefined-test", + }); }); });