feat(FN-2997): merge fusion/fn-2997-2

Merges the FN-2997 research CLI feature (275-line `research` command with routing, error handling, and docs), a droid CLI provider card for onboarding, a consolidated mock helpers module shared across CLI/dashboard/engine, the SSE architecture reference documentation, and the droid CLI probe module

Fusion-Task-Id: FN-2997
This commit is contained in:
Fusion
2026-05-01 06:18:21 -07:00
committed by gsxdsm
parent 17d45b8fe7
commit 09235042d9
9 changed files with 675 additions and 4 deletions

View File

@@ -92,6 +92,13 @@ const commandMocks = vi.hoisted(() => ({
runPluginEnable: vi.fn(),
runPluginDisable: vi.fn(),
runPluginCreate: vi.fn(),
runResearchCreate: vi.fn(),
runResearchList: vi.fn(),
runResearchShow: vi.fn(),
runResearchExport: vi.fn(),
runResearchCancel: vi.fn(),
runResearchRetry: vi.fn(),
}));
vi.mock("../commands/dashboard.js", () => ({ runDashboard: commandMocks.runDashboard }));
@@ -210,6 +217,15 @@ vi.mock("../commands/plugin-scaffold.js", () => ({
runPluginCreate: commandMocks.runPluginCreate,
}));
vi.mock("../commands/research.js", () => ({
runResearchCreate: commandMocks.runResearchCreate,
runResearchList: commandMocks.runResearchList,
runResearchShow: commandMocks.runResearchShow,
runResearchExport: commandMocks.runResearchExport,
runResearchCancel: commandMocks.runResearchCancel,
runResearchRetry: commandMocks.runResearchRetry,
}));
const originalArgv = process.argv;
const originalExit = process.exit;
const originalPiPackageDir = process.env.PI_PACKAGE_DIR;
@@ -504,6 +520,66 @@ describe("bin command routing and fallbacks", () => {
});
});
it("routes research create with options", async () => {
await runBin(["research", "create", "--query", "hello world", "--wait", "--max-wait-ms", "1200", "--json", "--project", "alpha"]);
expect(commandMocks.runResearchCreate).toHaveBeenCalledWith({
query: "hello world",
waitForCompletion: true,
maxWaitMs: 1200,
json: true,
projectName: "alpha",
});
});
it("supports positional research query and rejects missing query", async () => {
await runBin(["research", "create", "hello", "world"]);
expect(commandMocks.runResearchCreate).toHaveBeenCalledWith({
query: "hello world",
waitForCompletion: false,
maxWaitMs: undefined,
json: false,
projectName: undefined,
});
await expect(runBin(["research", "create", "--wait"]))
.rejects.toThrow("process.exit:1");
expect(errorSpy).toHaveBeenCalledWith("Usage: fn research create --query <text> [--wait] [--max-wait-ms <ms>] [--json]");
});
it("routes research export", async () => {
await runBin(["research", "export", "RR-001", "--format", "json", "--output", "./out.json"]);
expect(commandMocks.runResearchExport).toHaveBeenCalledWith({
runId: "RR-001",
format: "json",
output: "./out.json",
json: false,
projectName: undefined,
});
});
it("routes research list/show/cancel/retry", async () => {
await runBin(["research", "ls", "--status", "failed", "--limit", "5", "--json"]);
await runBin(["research", "show", "RR-001", "--json"]);
await runBin(["research", "cancel", "RR-001"]);
await runBin(["research", "retry", "RR-002", "--json"]);
expect(commandMocks.runResearchList).toHaveBeenCalledWith({
status: "failed",
limit: 5,
json: true,
projectName: undefined,
});
expect(commandMocks.runResearchShow).toHaveBeenCalledWith("RR-001", { json: true, projectName: undefined });
expect(commandMocks.runResearchCancel).toHaveBeenCalledWith("RR-001", { json: false, projectName: undefined });
expect(commandMocks.runResearchRetry).toHaveBeenCalledWith("RR-002", { json: true, projectName: undefined });
});
it("shows research subcommand guidance on unknown subcommand", async () => {
await expect(runBin(["research", "oops"])).rejects.toThrow("process.exit:1");
expect(errorSpy).toHaveBeenCalledWith("Unknown subcommand: research oops");
expect(logSpy).toHaveBeenCalledWith("Try: fn research create | list | show | export | cancel | retry");
});
it("routes desktop flags to runDesktop", async () => {
await runBin(["desktop", "--dev", "--paused", "--interactive"]);
expect(commandMocks.runDesktop).toHaveBeenCalledWith({