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
27 changes: 27 additions & 0 deletions actions/setup/js/dispatch_workflow.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
14 changes: 5 additions & 9 deletions actions/setup/js/safe_outputs_tools_loader.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
});
};
}
});
Expand Down
63 changes: 55 additions & 8 deletions actions/setup/js/safe_outputs_tools_loader.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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",
});
});
});

Expand Down
Loading