Files
opencode-multi-model/.opencode/plans/1773330460043-shiny-wolf.md
T
2026-03-12 21:32:07 +05:30

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:

  1. Filesystem checks (node:fs): The tool only uses fs.existsSync. We will mock this using spyOn(fs, 'existsSync').
  2. OS details (node:os): We will mock os.homedir() using spyOn(os, 'homedir') to return a safe, fake path.
  3. Shell commands (Bun.$): The tool extensively executes git, tmux, and opencode commands via Bun.$${parts}.quiet().nothrow(). We can override globalThis.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