Files
opencode-multi-model/.opencode/tools/multi-model.test.ts
T

182 lines
6.3 KiB
TypeScript

import { expect, test, mock, spyOn, beforeEach, afterEach, describe } from "bun:test";
import * as fs from "node:fs";
import * as os from "node:os";
import multiModelTool from "./multi-model";
let originalBun$: typeof Bun.$;
let mockCommandResponses: Record<string, { ok: boolean; stdout: string; stderr: string }> = {};
function setupMockBun$() {
const mockFn = mock((strings: TemplateStringsArray, ...values: any[]) => {
const parts = values[0] as string[];
const commandSignature = parts.join(" ");
return {
quiet: () => ({
nothrow: async () => {
const response = mockCommandResponses[commandSignature] ?? { ok: true, stdout: "", stderr: "" };
return {
exitCode: response.ok ? 0 : 1,
stdout: { toString: () => response.stdout },
stderr: { toString: () => response.stderr },
};
},
}),
};
});
(globalThis as any).Bun.$ = mockFn;
return mockFn;
}
function restoreOriginalBun$() {
(globalThis as any).Bun.$ = originalBun$;
}
describe("multi-model tool", () => {
let existsSyncMock: ReturnType<typeof spyOn>;
let bunMock: ReturnType<typeof setupMockBun$>;
beforeEach(() => {
spyOn(os, "homedir").mockReturnValue("/mock/home");
existsSyncMock = spyOn(fs, "existsSync").mockReturnValue(false);
bunMock = setupMockBun$();
mockCommandResponses = {
"git rev-parse --is-inside-work-tree": { ok: true, stdout: "true", stderr: "" },
"command -v tmux": { ok: true, stdout: "/usr/bin/tmux", stderr: "" },
"command -v opencode": { ok: true, stdout: "/usr/bin/opencode", stderr: "" },
"opencode models": { ok: true, stdout: "openai/gpt-4o\nanthropic/claude-3-5-sonnet", stderr: "" },
"tmux has-session -t test-session": { ok: false, stdout: "", stderr: "session not found" },
"git show-ref --verify --quiet refs/heads/opencode/test-session/gpt-4o": { ok: false, stdout: "", stderr: "" },
};
});
afterEach(() => {
restoreOriginalBun$();
});
test("fails if session name is empty", async () => {
const result = await multiModelTool.execute(
{ sessionName: "", models: ["openai/gpt-4o"] },
{ metadata: mock() } as any
);
expect(result).toContain("Error: `sessionName` must not be empty.");
});
test("fails if no models are provided", async () => {
const result = await multiModelTool.execute(
{ sessionName: "test-session", models: [] },
{ metadata: mock() } as any
);
expect(result).toContain("Error: Model names must not be empty.");
});
test("fails if duplicate models are provided", async () => {
const result = await multiModelTool.execute(
{ sessionName: "test-session", models: ["openai/gpt-4o", "openai/gpt-4o"] },
{ metadata: mock() } as any
);
expect(result).toContain("Duplicate model names are not allowed");
});
test("fails if not inside a git repository", async () => {
mockCommandResponses["git rev-parse --is-inside-work-tree"] = { ok: false, stdout: "", stderr: "not a git repo" };
const result = await multiModelTool.execute(
{ sessionName: "test-session", models: ["openai/gpt-4o"] },
{ metadata: mock() } as any
);
expect(result).toContain("Error: `multi-model` requires a git repository.");
});
test("fails if tmux is not installed", async () => {
mockCommandResponses["command -v tmux"] = { ok: false, stdout: "", stderr: "" };
const result = await multiModelTool.execute(
{ sessionName: "test-session", models: ["openai/gpt-4o"] },
{ metadata: mock() } as any
);
expect(result).toContain("Error: `tmux` is not installed or not on `PATH`.");
});
test("fails if opencode is not installed", async () => {
mockCommandResponses["command -v opencode"] = { ok: false, stdout: "", stderr: "" };
const result = await multiModelTool.execute(
{ sessionName: "test-session", models: ["openai/gpt-4o"] },
{ metadata: mock() } as any
);
expect(result).toContain("Error: `opencode` is not installed or not on `PATH`.");
});
test("fails if model name is not in allowlist", async () => {
const result = await multiModelTool.execute(
{ sessionName: "test-session", models: ["invalid/model"] },
{ metadata: mock() } as any
);
expect(result).toContain("Error: Model name 'invalid/model' not found");
});
test("fails if session already exists", async () => {
mockCommandResponses["tmux has-session -t test-session"] = { ok: true, stdout: "", stderr: "" };
const result = await multiModelTool.execute(
{ sessionName: "test-session", models: ["openai/gpt-4o"] },
{ metadata: mock() } as any
);
expect(result).toContain("Error: Session already exists");
});
test("successfully plans and executes tmux launch with single model", async () => {
const result = await multiModelTool.execute(
{ sessionName: "test-session", models: ["openai/gpt-4o"] },
{ metadata: mock() } as any
);
expect(result).toContain("Use `tmux attach -t test-session` to join session");
});
test("successfully launches multiple models", async () => {
mockCommandResponses["git show-ref --verify --quiet refs/heads/opencode/test-session/claude-3-5-sonnet"] = { ok: false, stdout: "", stderr: "" };
const result = await multiModelTool.execute(
{ sessionName: "test-session", models: ["openai/gpt-4o", "anthropic/claude-3-5-sonnet"] },
{ metadata: mock() } as any
);
expect(result).toContain("Use `tmux attach -t test-session` to join session");
});
test("sanitizes session name", async () => {
mockCommandResponses["tmux has-session -t test session!"] = { ok: false, stdout: "", stderr: "" };
const result = await multiModelTool.execute(
{ sessionName: "test session!", models: ["openai/gpt-4o"] },
{ metadata: mock() } as any
);
expect(result).toContain("Use `tmux attach -t test session!` to join session");
});
});
describe("unit functions", () => {
test("shellQuote escapes single quotes", () => {
const value = "test'value";
const quoted = "'" + value.replace(/'/g, `'"'"'`) + "'";
expect(quoted).toBe("'test'\"'\"'value'");
});
test("normalizeModels trims and filters empty", () => {
expect([" openai/gpt-4o ", ""].map(m => m.trim()).filter(Boolean)).toEqual(["openai/gpt-4o"]);
});
});