mirror of
https://github.com/bendtherules/opencode-multi-model.git
synced 2026-08-18 13:42:21 +00:00
4.0 KiB
4.0 KiB
model
| model |
|---|
| google/gemini-3.1-pro-preview |
Plan: Lightweight Testing for multi-model.ts
Overview
To test .opencode/tools/multi-model.ts without executing actual commands on your filesystem or Git repository, we will use bun:test.
Since your project is already utilizing Bun (evident from Bun.$), bun:test is the perfect built-in, zero-dependency, and lightweight testing framework. It comes with Jest-compatible mocking out of the box.
Mocking Strategy
The tool interacts with the system via three main avenues, all of which we will mock:
- Filesystem checks (
node:fs): The tool only usesfs.existsSync. We will mock this usingspyOn(fs, 'existsSync'). - OS details (
node:os): We will mockos.homedir()usingspyOn(os, 'homedir')to return a safe, fake path. - Shell commands (
Bun.$): The tool extensively executesgit,tmux, andopencodecommands viaBun.$${parts}.quiet().nothrow(). We can overrideglobalThis.Bun.$with a custom mock function that inspects the command parts and returns simulated deterministic outputs (exit code, stdout, stderr).
Implementation Details
Create a new file .opencode/tools/multi-model.test.ts and set up the mocks as follows:
import { expect, test, mock, spyOn, beforeEach, afterEach } from "bun:test";
import * as fs from "node:fs";
import * as os from "node:os";
import multiModelTool from "./multi-model";
// 1. Mock OS and FS
spyOn(os, "homedir").mockReturnValue("/mock/home");
const existsSyncMock = spyOn(fs, "existsSync").mockReturnValue(false);
// 2. Mock Bun.$
const originalBun$ = Bun.$;
let mockCommandResponses: Record<string, any> = {};
function setupMockBun$() {
(globalThis as any).Bun.$ = mock((strings: TemplateStringsArray, ...values: any[]) => {
// In Bun.$`${parts}`, parts is an array passed as the first value
const parts = values[0] as string[];
const commandSignature = parts.join(" ");
return {
quiet: () => ({
nothrow: async () => {
// You can define specific responses for specific commands
if (mockCommandResponses[commandSignature]) {
return mockCommandResponses[commandSignature];
}
// Default successful response
return {
exitCode: 0,
stdout: { toString: () => "" },
stderr: { toString: () => "" },
};
},
}),
};
});
}
beforeEach(() => {
setupMockBun$();
existsSyncMock.mockClear();
mockCommandResponses = {
// Provide a default opencode models list for tests
"opencode models": {
exitCode: 0,
stdout: { toString: () => "openai/gpt-4\nanthropic/claude-3" },
stderr: { toString: () => "" }
},
// Mock that session does not exist by default
"tmux has-session -t test-session": {
exitCode: 1, // 1 means session does not exist in tmux
stdout: { toString: () => "" },
stderr: { toString: () => "session not found" }
}
};
});
afterEach(() => {
// Restore original Bun.$
(globalThis as any).Bun.$ = originalBun$;
});
// 3. Write Tests
test("fails if duplicate models are provided", async () => {
const result = await multiModelTool.execute(
{ sessionName: "test-session", models: ["openai/gpt-4", "openai/gpt-4"] },
{ metadata: mock() } as any
);
expect(result).toContain("Duplicate model names are not allowed");
});
test("successfully plans and executes tmux launch", async () => {
const result = await multiModelTool.execute(
{ sessionName: "test-session", models: ["openai/gpt-4", "anthropic/claude-3"] },
{ metadata: mock() } as any
);
expect(result).toContain("Use `tmux attach -t test-session` to join session");
// You can also assert that Bun.$ was called with specific commands
// by inspecting (globalThis as any).Bun$.mock.calls
});
Verification
You can verify and run these tests safely without any system side-effects by running:
bun test .opencode/tools/multi-model.test.ts