diff --git a/__tests__/run-command.test.js b/__tests__/run-command.test.js new file mode 100644 index 000000000..014a7db50 --- /dev/null +++ b/__tests__/run-command.test.js @@ -0,0 +1,81 @@ +"use strict"; + +/** + * Coverage for cli/run.js — the `supercli run ` + * one-shot discover+install+execute command (issue #335). + * + * These tests cover the usage-validation paths, which return before any + * network/catalog/install calls. cli/executor is mocked with an explicit + * factory (rather than left to load for real) since it pulls in the vm2 + * native sandbox, which these tests have no need to exercise. + */ + +jest.mock("../cli/executor", () => ({ execute: jest.fn() })); + +const { handleRunCommand } = require("../cli/run"); + +describe("handleRunCommand — usage validation", () => { + test("missing plugin name reports invalid_argument with usage message", async () => { + const outputError = jest.fn(); + const output = jest.fn(); + + await handleRunCommand({ + positional: ["run"], + flags: {}, + humanMode: false, + output, + outputError, + }); + + expect(output).not.toHaveBeenCalled(); + expect(outputError).toHaveBeenCalledWith( + expect.objectContaining({ + code: 85, + type: "invalid_argument", + message: expect.stringContaining("Usage: supercli run"), + recoverable: false, + }) + ); + }); + + test("missing resource/action reports invalid_argument scoped to the plugin", async () => { + const outputError = jest.fn(); + const output = jest.fn(); + + await handleRunCommand({ + positional: ["run", "claude-session-optimizer"], + flags: {}, + humanMode: false, + output, + outputError, + }); + + expect(output).not.toHaveBeenCalled(); + expect(outputError).toHaveBeenCalledWith( + expect.objectContaining({ + code: 85, + type: "invalid_argument", + message: "Usage: supercli run claude-session-optimizer [--args]", + recoverable: false, + }) + ); + }); + + test("missing action alone also reports invalid_argument", async () => { + const outputError = jest.fn(); + const output = jest.fn(); + + await handleRunCommand({ + positional: ["run", "claude-session-optimizer", "self"], + flags: {}, + humanMode: false, + output, + outputError, + }); + + expect(output).not.toHaveBeenCalled(); + expect(outputError).toHaveBeenCalledWith( + expect.objectContaining({ code: 85, type: "invalid_argument" }) + ); + }); +}); diff --git a/__tests__/skills-mcp.test.js b/__tests__/skills-mcp.test.js new file mode 100644 index 000000000..ed1fcd005 --- /dev/null +++ b/__tests__/skills-mcp.test.js @@ -0,0 +1,58 @@ +"use strict"; + +const { buildMcpServersUsageSkillMarkdown } = require("../cli/skills-mcp"); + +function fakeRenderYamlObject(obj) { + return JSON.stringify(obj); +} + +describe("buildMcpServersUsageSkillMarkdown", () => { + test("wraps rendered frontmatter in --- fences followed by the instruction body", () => { + const md = buildMcpServersUsageSkillMarkdown({ renderYamlObject: fakeRenderYamlObject }); + + expect(md.startsWith("---\n")).toBe(true); + expect(md).toContain("\n---\n\n# Instruction"); + expect(md).toContain("supercli mcp list --json"); + }); + + test("omits the dag field by default", () => { + let capturedFrontmatter; + buildMcpServersUsageSkillMarkdown({ + renderYamlObject: (obj) => { + capturedFrontmatter = obj; + return ""; + }, + }); + + expect(capturedFrontmatter.dag).toBeUndefined(); + expect(capturedFrontmatter.command).toBe("skills get mcp.servers.usage"); + }); + + test("includes the reasoning DAG when showDag is true", () => { + let capturedFrontmatter; + buildMcpServersUsageSkillMarkdown({ + showDag: true, + renderYamlObject: (obj) => { + capturedFrontmatter = obj; + return ""; + }, + }); + + expect(Array.isArray(capturedFrontmatter.dag)).toBe(true); + expect(capturedFrontmatter.dag).toHaveLength(4); + expect(capturedFrontmatter.dag[0]).toMatchObject({ id: 1, type: "list_or_register_mcp_server" }); + }); + + test("honors a custom skillId in both the command and dag-less frontmatter", () => { + let capturedFrontmatter; + buildMcpServersUsageSkillMarkdown({ + skillId: "mcp.custom.usage", + renderYamlObject: (obj) => { + capturedFrontmatter = obj; + return ""; + }, + }); + + expect(capturedFrontmatter.command).toBe("skills get mcp.custom.usage"); + }); +});