Files
fusion/plugins/fusion-plugin-droid-runtime/src/__tests__/provider.test.ts
Fusion 1634dc786c feat(FN-3328): refactor droid-cli into compatibility shim backed by fusion-
Merged FN-3328: Refactored the droid integration by converting `droid-cli` into a lightweight compatibility shim that delegates to `fusion-plugin-droid-runtime`, reducing droid-cli by ~5,400 lines of code while moving the actual runtime logic into the plugin scaffold. Updated the plugin loader to al

Fusion-Task-Id: FN-3328
2026-05-04 17:22:21 -07:00

116 lines
3.9 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from "vitest";
import { EventEmitter } from "node:events";
import { PassThrough } from "node:stream";
const mocks = vi.hoisted(() => ({
spawnDroid: vi.fn(),
writeUserMessage: vi.fn(),
captureStderr: vi.fn(() => () => ""),
registerProcess: vi.fn(),
cleanupProcess: vi.fn(),
forceKillProcess: vi.fn(),
cleanupSystemPromptFile: vi.fn(),
buildDroidSpawnArgs: vi.fn(() => ["--model", "droid-pro"]),
parseLine: vi.fn(),
bridgeHandleEvent: vi.fn(),
bridgeGetOutput: vi.fn(() => ({
role: "assistant",
content: [],
api: "droid-cli",
provider: "droid-cli",
model: "droid-pro",
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
stopReason: "stop",
timestamp: 0,
})),
}));
vi.mock("../process-manager.js", () => ({
spawnDroid: mocks.spawnDroid,
writeUserMessage: mocks.writeUserMessage,
captureStderr: mocks.captureStderr,
registerProcess: mocks.registerProcess,
cleanupProcess: mocks.cleanupProcess,
forceKillProcess: mocks.forceKillProcess,
cleanupSystemPromptFile: mocks.cleanupSystemPromptFile,
buildDroidSpawnArgs: mocks.buildDroidSpawnArgs,
}));
vi.mock("../stream-parser.js", () => ({ parseLine: mocks.parseLine }));
vi.mock("../event-bridge.js", () => ({
createEventBridge: () => ({ handleEvent: mocks.bridgeHandleEvent, getOutput: mocks.bridgeGetOutput }),
}));
import { streamViaCli } from "../provider.js";
function makeProc() {
const proc = new EventEmitter() as any;
proc.stdin = { write: vi.fn(), end: vi.fn() };
proc.stdout = new PassThrough();
proc.stderr = new EventEmitter();
proc.killed = false;
proc.exitCode = null;
proc.kill = vi.fn();
proc.pid = 123;
return proc;
}
describe("streamViaCli", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("spawns droid and writes prompt", async () => {
const proc = makeProc();
mocks.spawnDroid.mockReturnValue(proc);
mocks.parseLine.mockReturnValueOnce({ type: "result", subtype: "success" });
const stream = streamViaCli({ id: "droid-pro", provider: "droid-cli" } as any, { messages: [{ role: "user", content: "hi" }] } as any);
expect(stream).toBeDefined();
await new Promise((r) => setTimeout(r, 0));
proc.stdout.write('{"type":"result","subtype":"success"}\n');
proc.emit("close", 0, null);
await new Promise((r) => setTimeout(r, 0));
expect(mocks.spawnDroid).toHaveBeenCalled();
expect(mocks.writeUserMessage).toHaveBeenCalled();
});
it("forwards stream events to bridge", async () => {
const proc = makeProc();
mocks.spawnDroid.mockReturnValue(proc);
mocks.parseLine
.mockReturnValueOnce({ type: "stream_event", event: { type: "message_start" } })
.mockReturnValueOnce({ type: "result", subtype: "success" });
streamViaCli({ id: "droid-pro", provider: "droid-cli" } as any, { messages: [{ role: "user", content: "hi" }] } as any);
await new Promise((r) => setTimeout(r, 0));
proc.stdout.write('a\n');
proc.stdout.write('b\n');
proc.emit("close", 0, null);
await new Promise((r) => setTimeout(r, 0));
expect(mocks.bridgeHandleEvent).toHaveBeenCalledWith({ type: "message_start" });
});
it("includes mcp config path in spawn args options", async () => {
const proc = makeProc();
mocks.spawnDroid.mockReturnValue(proc);
mocks.parseLine.mockReturnValueOnce({ type: "result", subtype: "success" });
streamViaCli(
{ id: "droid-pro", provider: "droid-cli" } as any,
{ messages: [{ role: "user", content: "hi" }] } as any,
{ mcpConfigPath: "/tmp/mcp.json" } as any,
);
await new Promise((r) => setTimeout(r, 0));
expect(mocks.spawnDroid).toHaveBeenCalledWith(
"droid-pro",
expect.anything(),
expect.objectContaining({ mcpConfigPath: "/tmp/mcp.json" }),
);
});
});