mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-08 10:09:30 +00:00
test: Add 43 tests for display command output (cmdAgentInfo, cmdList, cmdAgents, cmdClouds, cmdHelp, cmdUpdate) (#251)
Cover previously untested happy paths for user-facing display commands: - cmdAgentInfo: agent details, available clouds, no-clouds fallback, many clouds - cmdList: matrix table rendering, implemented counts, edge cases - cmdAgents: agent listing with cloud counts, singular/plural, descriptions - cmdClouds: cloud listing with agent counts, singular/plural, descriptions - cmdHelp: all sections (USAGE, EXAMPLES, AUTHENTICATION, TROUBLESHOOTING, INSTALL) - cmdUpdate: version match, fetch failure, non-ok response Agent: test-engineer Co-authored-by: A <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
16cabed6d8
commit
281ea2a74f
1 changed files with 681 additions and 0 deletions
681
cli/src/__tests__/commands-display.test.ts
Normal file
681
cli/src/__tests__/commands-display.test.ts
Normal file
|
|
@ -0,0 +1,681 @@
|
|||
import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test";
|
||||
import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers";
|
||||
import { loadManifest } from "../manifest";
|
||||
|
||||
/**
|
||||
* Tests for display/output commands: cmdAgentInfo (happy path), cmdList,
|
||||
* cmdAgents, cmdClouds, cmdHelp, and cmdUpdate error paths.
|
||||
*
|
||||
* Existing tests cover:
|
||||
* - cmdAgentInfo error paths (commands-error-paths.test.ts)
|
||||
* - cmdCloudInfo full coverage (commands-cloud-info.test.ts)
|
||||
* - cmdRun validation and error paths (commands-error-paths.test.ts)
|
||||
*
|
||||
* This file covers the UNTESTED happy paths and output formatting of:
|
||||
* - cmdAgentInfo: displaying agent details and available clouds
|
||||
* - cmdList: rendering the full matrix table
|
||||
* - cmdAgents: listing all agents with cloud counts
|
||||
* - cmdClouds: listing all clouds with agent counts
|
||||
* - cmdHelp: verifying help output content
|
||||
* - cmdUpdate: version check and error handling paths
|
||||
*
|
||||
* Agent: test-engineer
|
||||
*/
|
||||
|
||||
const mockManifest = createMockManifest();
|
||||
|
||||
// Manifest with no implementations for edge case testing
|
||||
const noImplManifest = {
|
||||
...mockManifest,
|
||||
matrix: {
|
||||
"sprite/claude": "missing",
|
||||
"sprite/aider": "missing",
|
||||
"hetzner/claude": "missing",
|
||||
"hetzner/aider": "missing",
|
||||
},
|
||||
};
|
||||
|
||||
// Manifest with a single implementation
|
||||
const singleImplManifest = {
|
||||
...mockManifest,
|
||||
matrix: {
|
||||
"sprite/claude": "implemented",
|
||||
"sprite/aider": "missing",
|
||||
"hetzner/claude": "missing",
|
||||
"hetzner/aider": "missing",
|
||||
},
|
||||
};
|
||||
|
||||
// Manifest with many clouds (> 3) to test "see all" hint
|
||||
const manyCloudManifest = {
|
||||
agents: {
|
||||
claude: mockManifest.agents.claude,
|
||||
},
|
||||
clouds: {
|
||||
sprite: mockManifest.clouds.sprite,
|
||||
hetzner: mockManifest.clouds.hetzner,
|
||||
vultr: {
|
||||
name: "Vultr",
|
||||
description: "Cloud compute",
|
||||
url: "https://vultr.com",
|
||||
type: "cloud",
|
||||
auth: "token",
|
||||
provision_method: "api",
|
||||
exec_method: "ssh",
|
||||
interactive_method: "ssh",
|
||||
},
|
||||
linode: {
|
||||
name: "Linode",
|
||||
description: "Cloud hosting",
|
||||
url: "https://linode.com",
|
||||
type: "cloud",
|
||||
auth: "token",
|
||||
provision_method: "api",
|
||||
exec_method: "ssh",
|
||||
interactive_method: "ssh",
|
||||
},
|
||||
digitalocean: {
|
||||
name: "DigitalOcean",
|
||||
description: "Cloud infrastructure",
|
||||
url: "https://digitalocean.com",
|
||||
type: "cloud",
|
||||
auth: "token",
|
||||
provision_method: "api",
|
||||
exec_method: "ssh",
|
||||
interactive_method: "ssh",
|
||||
},
|
||||
},
|
||||
matrix: {
|
||||
"sprite/claude": "implemented",
|
||||
"hetzner/claude": "implemented",
|
||||
"vultr/claude": "implemented",
|
||||
"linode/claude": "implemented",
|
||||
"digitalocean/claude": "implemented",
|
||||
},
|
||||
};
|
||||
|
||||
// Mock @clack/prompts
|
||||
const mockLogError = mock(() => {});
|
||||
const mockLogInfo = mock(() => {});
|
||||
const mockLogStep = mock(() => {});
|
||||
const mockLogWarn = mock(() => {});
|
||||
const mockSpinnerStart = mock(() => {});
|
||||
const mockSpinnerStop = mock(() => {});
|
||||
|
||||
mock.module("@clack/prompts", () => ({
|
||||
spinner: () => ({
|
||||
start: mockSpinnerStart,
|
||||
stop: mockSpinnerStop,
|
||||
message: mock(() => {}),
|
||||
}),
|
||||
log: {
|
||||
step: mockLogStep,
|
||||
info: mockLogInfo,
|
||||
error: mockLogError,
|
||||
warn: mockLogWarn,
|
||||
success: mock(() => {}),
|
||||
},
|
||||
intro: mock(() => {}),
|
||||
outro: mock(() => {}),
|
||||
cancel: mock(() => {}),
|
||||
select: mock(() => {}),
|
||||
isCancel: () => false,
|
||||
}));
|
||||
|
||||
// Import commands after mock setup
|
||||
const { cmdAgentInfo, cmdList, cmdAgents, cmdClouds, cmdHelp, cmdUpdate } =
|
||||
await import("../commands.js");
|
||||
|
||||
describe("Commands Display Output", () => {
|
||||
let consoleMocks: ReturnType<typeof createConsoleMocks>;
|
||||
let originalFetch: typeof global.fetch;
|
||||
let processExitSpy: ReturnType<typeof spyOn>;
|
||||
|
||||
beforeEach(async () => {
|
||||
consoleMocks = createConsoleMocks();
|
||||
mockLogError.mockClear();
|
||||
mockLogInfo.mockClear();
|
||||
mockLogStep.mockClear();
|
||||
mockLogWarn.mockClear();
|
||||
mockSpinnerStart.mockClear();
|
||||
mockSpinnerStop.mockClear();
|
||||
|
||||
processExitSpy = spyOn(process, "exit").mockImplementation((() => {
|
||||
throw new Error("process.exit");
|
||||
}) as any);
|
||||
|
||||
originalFetch = global.fetch;
|
||||
global.fetch = mock(async () => ({
|
||||
ok: true,
|
||||
json: async () => mockManifest,
|
||||
text: async () => JSON.stringify(mockManifest),
|
||||
})) as any;
|
||||
|
||||
await loadManifest(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
processExitSpy.mockRestore();
|
||||
restoreMocks(consoleMocks.log, consoleMocks.error);
|
||||
});
|
||||
|
||||
// ── cmdAgentInfo happy path ────────────────────────────────────────
|
||||
|
||||
describe("cmdAgentInfo - happy path", () => {
|
||||
it("should display agent name and description for claude", async () => {
|
||||
await cmdAgentInfo("claude");
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("Claude Code");
|
||||
expect(output).toContain("AI coding assistant");
|
||||
});
|
||||
|
||||
it("should display Available clouds header", async () => {
|
||||
await cmdAgentInfo("claude");
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("Available clouds");
|
||||
});
|
||||
|
||||
it("should list implemented clouds for claude", async () => {
|
||||
await cmdAgentInfo("claude");
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
// claude is implemented on both sprite and hetzner
|
||||
expect(output).toContain("sprite");
|
||||
expect(output).toContain("hetzner");
|
||||
});
|
||||
|
||||
it("should show launch command hint for each cloud", async () => {
|
||||
await cmdAgentInfo("claude");
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("spawn claude sprite");
|
||||
expect(output).toContain("spawn claude hetzner");
|
||||
});
|
||||
|
||||
it("should show aider agent info with only sprite cloud", async () => {
|
||||
await cmdAgentInfo("aider");
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("Aider");
|
||||
expect(output).toContain("AI pair programmer");
|
||||
expect(output).toContain("spawn aider sprite");
|
||||
expect(output).not.toContain("spawn aider hetzner");
|
||||
});
|
||||
|
||||
it("should show no-clouds message when agent has no implementations", async () => {
|
||||
global.fetch = mock(async () => ({
|
||||
ok: true,
|
||||
json: async () => noImplManifest,
|
||||
text: async () => JSON.stringify(noImplManifest),
|
||||
})) as any;
|
||||
await loadManifest(true);
|
||||
|
||||
await cmdAgentInfo("claude");
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("No implemented clouds");
|
||||
});
|
||||
|
||||
it("should use spinner while loading manifest", async () => {
|
||||
await cmdAgentInfo("claude");
|
||||
expect(mockSpinnerStart).toHaveBeenCalled();
|
||||
expect(mockSpinnerStop).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ── cmdList ────────────────────────────────────────────────────────
|
||||
|
||||
describe("cmdList", () => {
|
||||
it("should display cloud names in header", async () => {
|
||||
await cmdList();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("Sprite");
|
||||
expect(output).toContain("Hetzner Cloud");
|
||||
});
|
||||
|
||||
it("should display agent names in rows", async () => {
|
||||
await cmdList();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("Claude Code");
|
||||
expect(output).toContain("Aider");
|
||||
});
|
||||
|
||||
it("should show implemented count", async () => {
|
||||
await cmdList();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
// 3 implemented out of 4 total (2 agents x 2 clouds)
|
||||
expect(output).toContain("3/4");
|
||||
});
|
||||
|
||||
it("should show legend for + and -", async () => {
|
||||
await cmdList();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("implemented");
|
||||
expect(output).toContain("not yet available");
|
||||
});
|
||||
|
||||
it("should show + for implemented and - for missing", async () => {
|
||||
await cmdList();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("+");
|
||||
expect(output).toContain("-");
|
||||
});
|
||||
|
||||
it("should show 0 implemented when nothing is implemented", async () => {
|
||||
global.fetch = mock(async () => ({
|
||||
ok: true,
|
||||
json: async () => noImplManifest,
|
||||
text: async () => JSON.stringify(noImplManifest),
|
||||
})) as any;
|
||||
await loadManifest(true);
|
||||
|
||||
await cmdList();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("0/4");
|
||||
});
|
||||
|
||||
it("should use spinner while loading manifest", async () => {
|
||||
await cmdList();
|
||||
expect(mockSpinnerStart).toHaveBeenCalled();
|
||||
expect(mockSpinnerStop).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ── cmdAgents ──────────────────────────────────────────────────────
|
||||
|
||||
describe("cmdAgents", () => {
|
||||
it("should display Agents header", async () => {
|
||||
await cmdAgents();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("Agents");
|
||||
});
|
||||
|
||||
it("should list all agents with their display names", async () => {
|
||||
await cmdAgents();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("claude");
|
||||
expect(output).toContain("Claude Code");
|
||||
expect(output).toContain("aider");
|
||||
expect(output).toContain("Aider");
|
||||
});
|
||||
|
||||
it("should show cloud counts for each agent", async () => {
|
||||
await cmdAgents();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
// claude has 2 clouds, aider has 1 cloud
|
||||
expect(output).toContain("2 clouds");
|
||||
expect(output).toContain("1 cloud");
|
||||
});
|
||||
|
||||
it("should show correct singular/plural for cloud count", async () => {
|
||||
await cmdAgents();
|
||||
const calls = consoleMocks.log.mock.calls.map((c: any[]) => c.join(" "));
|
||||
// Find the line with aider (1 cloud - singular)
|
||||
const aiderLine = calls.find(
|
||||
(line: string) => line.includes("aider") && line.includes("cloud")
|
||||
);
|
||||
expect(aiderLine).toBeDefined();
|
||||
expect(aiderLine).toContain("1 cloud");
|
||||
expect(aiderLine).not.toContain("1 clouds");
|
||||
});
|
||||
|
||||
it("should show agent descriptions", async () => {
|
||||
await cmdAgents();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("AI coding assistant");
|
||||
expect(output).toContain("AI pair programmer");
|
||||
});
|
||||
|
||||
it("should show usage hint at bottom", async () => {
|
||||
await cmdAgents();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("spawn <agent>");
|
||||
});
|
||||
});
|
||||
|
||||
// ── cmdClouds ──────────────────────────────────────────────────────
|
||||
|
||||
describe("cmdClouds", () => {
|
||||
it("should display Cloud Providers header", async () => {
|
||||
await cmdClouds();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("Cloud Providers");
|
||||
});
|
||||
|
||||
it("should list all clouds with their display names", async () => {
|
||||
await cmdClouds();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("sprite");
|
||||
expect(output).toContain("Sprite");
|
||||
expect(output).toContain("hetzner");
|
||||
expect(output).toContain("Hetzner Cloud");
|
||||
});
|
||||
|
||||
it("should show agent counts for each cloud", async () => {
|
||||
await cmdClouds();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
// sprite has 2 agents, hetzner has 1 agent
|
||||
expect(output).toContain("2 agents");
|
||||
expect(output).toContain("1 agent");
|
||||
});
|
||||
|
||||
it("should show correct singular/plural for agent count", async () => {
|
||||
await cmdClouds();
|
||||
const calls = consoleMocks.log.mock.calls.map((c: any[]) => c.join(" "));
|
||||
// hetzner has 1 agent (singular)
|
||||
const hetznerLine = calls.find(
|
||||
(line: string) => line.includes("hetzner") && line.includes("agent")
|
||||
);
|
||||
expect(hetznerLine).toBeDefined();
|
||||
expect(hetznerLine).toContain("1 agent");
|
||||
expect(hetznerLine).not.toContain("1 agents");
|
||||
});
|
||||
|
||||
it("should show cloud descriptions", async () => {
|
||||
await cmdClouds();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("Lightweight VMs");
|
||||
expect(output).toContain("European cloud provider");
|
||||
});
|
||||
|
||||
it("should show usage hint at bottom", async () => {
|
||||
await cmdClouds();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("spawn <cloud>");
|
||||
});
|
||||
});
|
||||
|
||||
// ── cmdHelp ────────────────────────────────────────────────────────
|
||||
|
||||
describe("cmdHelp", () => {
|
||||
it("should display usage section", () => {
|
||||
cmdHelp();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("USAGE");
|
||||
});
|
||||
|
||||
it("should show all subcommands", () => {
|
||||
cmdHelp();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("spawn list");
|
||||
expect(output).toContain("spawn agents");
|
||||
expect(output).toContain("spawn clouds");
|
||||
expect(output).toContain("spawn update");
|
||||
expect(output).toContain("spawn version");
|
||||
expect(output).toContain("spawn help");
|
||||
});
|
||||
|
||||
it("should show examples section", () => {
|
||||
cmdHelp();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("EXAMPLES");
|
||||
});
|
||||
|
||||
it("should show authentication section", () => {
|
||||
cmdHelp();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("AUTHENTICATION");
|
||||
expect(output).toContain("OpenRouter");
|
||||
});
|
||||
|
||||
it("should show troubleshooting section", () => {
|
||||
cmdHelp();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("TROUBLESHOOTING");
|
||||
});
|
||||
|
||||
it("should show --prompt and --prompt-file usage", () => {
|
||||
cmdHelp();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("--prompt");
|
||||
expect(output).toContain("--prompt-file");
|
||||
});
|
||||
|
||||
it("should mention SPAWN_NO_UNICODE env var", () => {
|
||||
cmdHelp();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("SPAWN_NO_UNICODE");
|
||||
});
|
||||
|
||||
it("should show install section with curl command", () => {
|
||||
cmdHelp();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("INSTALL");
|
||||
expect(output).toContain("curl");
|
||||
expect(output).toContain("install.sh");
|
||||
});
|
||||
|
||||
it("should show repository URL", () => {
|
||||
cmdHelp();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("OpenRouterTeam/spawn");
|
||||
});
|
||||
});
|
||||
|
||||
// ── cmdUpdate ──────────────────────────────────────────────────────
|
||||
|
||||
describe("cmdUpdate", () => {
|
||||
it("should show already up to date when versions match", async () => {
|
||||
const pkg = await import("../../package.json");
|
||||
global.fetch = mock(async () => ({
|
||||
ok: true,
|
||||
json: async () => ({ version: pkg.default.version }),
|
||||
})) as any;
|
||||
|
||||
await cmdUpdate();
|
||||
|
||||
// Spinner should have been used
|
||||
expect(mockSpinnerStart).toHaveBeenCalled();
|
||||
expect(mockSpinnerStop).toHaveBeenCalled();
|
||||
// Stop message should mention "up to date"
|
||||
const stopCalls = mockSpinnerStop.mock.calls.map((c: any[]) =>
|
||||
c.join(" ")
|
||||
);
|
||||
expect(
|
||||
stopCalls.some((msg: string) => msg.includes("up to date"))
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle fetch failure gracefully", async () => {
|
||||
global.fetch = mock(async () => {
|
||||
throw new Error("Network timeout");
|
||||
}) as any;
|
||||
|
||||
await cmdUpdate();
|
||||
|
||||
// Should stop spinner with failure message
|
||||
expect(mockSpinnerStop).toHaveBeenCalled();
|
||||
// Should print error details
|
||||
const errorOutput = consoleMocks.error.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(errorOutput).toContain("Network timeout");
|
||||
});
|
||||
|
||||
it("should handle non-ok fetch response", async () => {
|
||||
global.fetch = mock(async () => ({
|
||||
ok: false,
|
||||
})) as any;
|
||||
|
||||
await cmdUpdate();
|
||||
|
||||
// Should stop spinner with failure message
|
||||
expect(mockSpinnerStop).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ── cmdList with varied manifests ──────────────────────────────────
|
||||
|
||||
describe("cmdList - edge cases", () => {
|
||||
it("should handle single implementation correctly", async () => {
|
||||
global.fetch = mock(async () => ({
|
||||
ok: true,
|
||||
json: async () => singleImplManifest,
|
||||
text: async () => JSON.stringify(singleImplManifest),
|
||||
})) as any;
|
||||
await loadManifest(true);
|
||||
|
||||
await cmdList();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("1/4");
|
||||
});
|
||||
|
||||
it("should handle manifest with many clouds", async () => {
|
||||
global.fetch = mock(async () => ({
|
||||
ok: true,
|
||||
json: async () => manyCloudManifest,
|
||||
text: async () => JSON.stringify(manyCloudManifest),
|
||||
})) as any;
|
||||
await loadManifest(true);
|
||||
|
||||
await cmdList();
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("Vultr");
|
||||
expect(output).toContain("Linode");
|
||||
expect(output).toContain("DigitalOcean");
|
||||
// 5 out of 5 (1 agent x 5 clouds, all implemented)
|
||||
expect(output).toContain("5/5");
|
||||
});
|
||||
});
|
||||
|
||||
// ── cmdAgentInfo with many clouds ──────────────────────────────────
|
||||
|
||||
describe("cmdAgentInfo - many clouds", () => {
|
||||
it("should list all implemented clouds for agent with many options", async () => {
|
||||
global.fetch = mock(async () => ({
|
||||
ok: true,
|
||||
json: async () => manyCloudManifest,
|
||||
text: async () => JSON.stringify(manyCloudManifest),
|
||||
})) as any;
|
||||
await loadManifest(true);
|
||||
|
||||
await cmdAgentInfo("claude");
|
||||
const output = consoleMocks.log.mock.calls
|
||||
.map((c: any[]) => c.join(" "))
|
||||
.join("\n");
|
||||
expect(output).toContain("spawn claude sprite");
|
||||
expect(output).toContain("spawn claude hetzner");
|
||||
expect(output).toContain("spawn claude vultr");
|
||||
expect(output).toContain("spawn claude linode");
|
||||
expect(output).toContain("spawn claude digitalocean");
|
||||
});
|
||||
});
|
||||
|
||||
// ── cmdAgents with no implementations ──────────────────────────────
|
||||
|
||||
describe("cmdAgents - zero implementations", () => {
|
||||
it("should show 0 clouds for all agents", async () => {
|
||||
global.fetch = mock(async () => ({
|
||||
ok: true,
|
||||
json: async () => noImplManifest,
|
||||
text: async () => JSON.stringify(noImplManifest),
|
||||
})) as any;
|
||||
await loadManifest(true);
|
||||
|
||||
await cmdAgents();
|
||||
const calls = consoleMocks.log.mock.calls.map((c: any[]) =>
|
||||
c.join(" ")
|
||||
);
|
||||
// Both agents should show 0 clouds
|
||||
const agentLines = calls.filter(
|
||||
(line: string) =>
|
||||
line.includes("claude") || line.includes("aider")
|
||||
);
|
||||
for (const line of agentLines) {
|
||||
if (line.includes("cloud")) {
|
||||
expect(line).toContain("0 clouds");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ── cmdClouds with no implementations ──────────────────────────────
|
||||
|
||||
describe("cmdClouds - zero implementations", () => {
|
||||
it("should show 0 agents for all clouds", async () => {
|
||||
global.fetch = mock(async () => ({
|
||||
ok: true,
|
||||
json: async () => noImplManifest,
|
||||
text: async () => JSON.stringify(noImplManifest),
|
||||
})) as any;
|
||||
await loadManifest(true);
|
||||
|
||||
await cmdClouds();
|
||||
const calls = consoleMocks.log.mock.calls.map((c: any[]) =>
|
||||
c.join(" ")
|
||||
);
|
||||
const cloudLines = calls.filter(
|
||||
(line: string) =>
|
||||
line.includes("sprite") || line.includes("hetzner")
|
||||
);
|
||||
for (const line of cloudLines) {
|
||||
if (line.includes("agent")) {
|
||||
expect(line).toContain("0 agents");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue