import { expect, test, mock, beforeEach, afterEach, describe, spyOn } from "bun:test"; import * as os from "node:os"; import * as fs from "node:fs"; import { openMultiModel } from "../src/core/open"; let originalBun$: typeof Bun.$ = (globalThis as any).Bun?.$; let mockCommandResponses: Record = {}; let executedCommands: string[] = []; function setupMockBun$() { const mockFn = mock((strings: TemplateStringsArray, ...values: any[]) => { const parts = values[0] as string[]; const cmd = parts.join(" "); executedCommands.push(cmd); const response = mockCommandResponses[cmd] ?? { ok: true, stdout: "", stderr: "" }; return { quiet: () => ({ nothrow: async () => ({ 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("openMultiModel error paths", () => { beforeEach(() => { spyOn(os, "homedir").mockReturnValue("/mock/home"); mockCommandResponses = {}; executedCommands = []; setupMockBun$(); }); afterEach(() => { restoreOriginalBun$(); }); test("fails when model list command fails", async () => { const session = "sess"; mockCommandResponses["git rev-parse --is-inside-work-tree"] = { ok: true, stdout: "true", stderr: "" }; mockCommandResponses["command -v tmux"] = { ok: true, stdout: "tmux", stderr: "" }; mockCommandResponses["command -v opencode"] = { ok: true, stdout: "opencode", stderr: "" }; mockCommandResponses["opencode models"] = { ok: false, stdout: "", stderr: "model error" }; const result = await openMultiModel({ sessionName: session, models: ["openai/gpt-5.2"], binaryName: "opencode", mode: "cli" }); expect(result.success).toBe(false); expect(result.error).toContain("Failed to load valid models"); }); test("fails when duplicate models provided", async () => { const session = "dup"; mockCommandResponses["git rev-parse --is-inside-work-tree"] = { ok: true, stdout: "true", stderr: "" }; mockCommandResponses["command -v tmux"] = { ok: true, stdout: "tmux", stderr: "" }; mockCommandResponses["command -v opencode"] = { ok: true, stdout: "opencode", stderr: "" }; mockCommandResponses["opencode models"] = { ok: true, stdout: "openai/gpt-5.2", stderr: "" }; const result = await openMultiModel({ sessionName: session, models: ["openai/gpt-5.2", "openai/gpt-5.2"], binaryName: "opencode", mode: "cli" }); expect(result.success).toBe(false); expect(result.error).toContain("Duplicate model names are not allowed"); }); test("fails when session already exists", async () => { const session = "existing"; mockCommandResponses["git rev-parse --is-inside-work-tree"] = { ok: true, stdout: "true", stderr: "" }; mockCommandResponses["command -v tmux"] = { ok: true, stdout: "tmux", stderr: "" }; mockCommandResponses["command -v opencode"] = { ok: true, stdout: "opencode", stderr: "" }; mockCommandResponses["opencode models"] = { ok: true, stdout: "openai/gpt-5.2", stderr: "" }; mockCommandResponses[`tmux has-session -t ${session}`] = { ok: true, stdout: "", stderr: "" }; const result = await openMultiModel({ sessionName: session, models: ["openai/gpt-5.2"], binaryName: "opencode", mode: "cli" }); expect(result.success).toBe(false); expect(result.error).toContain("Session already exists"); }); test("second model fails because branch already exists", async () => { const session = "sess-add"; const binary = "opencode"; const models = ["openai/gpt-5.2", "anthropic/gpt-5-2"]; // common mocks mockCommandResponses["git rev-parse --is-inside-work-tree"] = { ok: true, stdout: "true", stderr: "" }; mockCommandResponses["command -v tmux"] = { ok: true, stdout: "tmux", stderr: "" }; mockCommandResponses["command -v opencode"] = { ok: true, stdout: "opencode", stderr: "" }; mockCommandResponses["opencode models"] = { ok: true, stdout: models.join("\n"), stderr: "" }; mockCommandResponses[`tmux has-session -t ${session}`] = { ok: false, stdout: "", stderr: "" }; // first model success const firstBranch = `opencode/${session}/gpt-5-2`; const firstPath = `/mock/home/.local/share/opencode/multi-model/${session}/gpt-5-2`; mockCommandResponses[`git show-ref --verify --quiet refs/heads/${firstBranch}`] = { ok: false, stdout: "", stderr: "" }; mockCommandResponses[`git worktree add -b ${firstBranch} ${firstPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux new-session -d -s ${session} -n gpt-5-2 -c ${firstPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux send-keys -t ${session}:gpt-5-2 ${binary} --model 'openai/gpt-5.2' C-m`] = { ok: true, stdout: "", stderr: "" }; // second model branch exists const secondBranch = `opencode/${session}/gpt-5-2-2`; mockCommandResponses[`git show-ref --verify --quiet refs/heads/${secondBranch}`] = { ok: true, stdout: "", stderr: "" }; const result = await openMultiModel({ sessionName: session, models, binaryName: binary, mode: "cli" }); expect(result.success).toBe(true); expect(result.instructions).toContain("Failed: anthropic/gpt-5-2 (Branch"); expect(result.windows).toContain("gpt-5-2"); expect(result.windows).toContain("gpt-5-2-2"); }); test("second model worktree creation fails", async () => { const session = "sess-wt"; const binary = "opencode"; const models = ["openai/gpt-5.2", "anthropic/gpt-5-2"]; mockCommandResponses["git rev-parse --is-inside-work-tree"] = { ok: true, stdout: "true", stderr: "" }; mockCommandResponses["command -v tmux"] = { ok: true, stdout: "tmux", stderr: "" }; mockCommandResponses["command -v opencode"] = { ok: true, stdout: "opencode", stderr: "" }; mockCommandResponses["opencode models"] = { ok: true, stdout: models.join("\n"), stderr: "" }; mockCommandResponses[`tmux has-session -t ${session}`] = { ok: false, stdout: "", stderr: "" }; // first model success const firstBranch = `opencode/${session}/gpt-5-2`; const firstPath = `/mock/home/.local/share/opencode/multi-model/${session}/gpt-5-2`; mockCommandResponses[`git show-ref --verify --quiet refs/heads/${firstBranch}`] = { ok: false, stdout: "", stderr: "" }; mockCommandResponses[`git worktree add -b ${firstBranch} ${firstPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux new-session -d -s ${session} -n gpt-5-2 -c ${firstPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux send-keys -t ${session}:gpt-5-2 ${binary} --model 'openai/gpt-5.2' C-m`] = { ok: true, stdout: "", stderr: "" }; // second model fails worktree const secondBranch = `opencode/${session}/gpt-5-2-2`; const secondPath = `/mock/home/.local/share/opencode/multi-model/${session}/gpt-5-2-2`; mockCommandResponses[`git show-ref --verify --quiet refs/heads/${secondBranch}`] = { ok: false, stdout: "", stderr: "" }; mockCommandResponses[`git worktree add -b ${secondBranch} ${secondPath}`] = { ok: false, stdout: "", stderr: "wt error" }; mockCommandResponses[`git worktree remove -f ${secondPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`git branch -D ${secondBranch}`] = { ok: true, stdout: "", stderr: "" }; const result = await openMultiModel({ sessionName: session, models, binaryName: binary, mode: "cli" }); expect(result.success).toBe(true); expect(result.instructions).toContain("Error: Created tmux session"); expect(result.instructions).toContain("Failed: anthropic/gpt-5-2"); expect(result.windows).toContain("gpt-5-2"); expect(result.windows).toContain("gpt-5-2-2"); }); test("second model window creation fails", async () => { const session = "sess-win"; const binary = "opencode"; const models = ["openai/gpt-5.2", "anthropic/gpt-5-2"]; mockCommandResponses["git rev-parse --is-inside-work-tree"] = { ok: true, stdout: "true", stderr: "" }; mockCommandResponses["command -v tmux"] = { ok: true, stdout: "tmux", stderr: "" }; mockCommandResponses["command -v opencode"] = { ok: true, stdout: "opencode", stderr: "" }; mockCommandResponses["opencode models"] = { ok: true, stdout: models.join("\n"), stderr: "" }; mockCommandResponses[`tmux has-session -t ${session}`] = { ok: false, stdout: "", stderr: "" }; // first model success const firstBranch = `opencode/${session}/gpt-5-2`; const firstPath = `/mock/home/.local/share/opencode/multi-model/${session}/gpt-5-2`; mockCommandResponses[`git show-ref --verify --quiet refs/heads/${firstBranch}`] = { ok: false, stdout: "", stderr: "" }; mockCommandResponses[`git worktree add -b ${firstBranch} ${firstPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux new-session -d -s ${session} -n gpt-5-2 -c ${firstPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux send-keys -t ${session}:gpt-5-2 ${binary} --model 'openai/gpt-5.2' C-m`] = { ok: true, stdout: "", stderr: "" }; // second model window failure const secondBranch = `opencode/${session}/gpt-5-2-2`; const secondPath = `/mock/home/.local/share/opencode/multi-model/${session}/gpt-5-2-2`; mockCommandResponses[`git show-ref --verify --quiet refs/heads/${secondBranch}`] = { ok: false, stdout: "", stderr: "" }; mockCommandResponses[`git worktree add -b ${secondBranch} ${secondPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux new-window -d -t ${session} -n gpt-5-2-2 -c ${secondPath}`] = { ok: false, stdout: "", stderr: "win err" }; mockCommandResponses[`git worktree remove -f ${secondPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`git branch -D ${secondBranch}`] = { ok: true, stdout: "", stderr: "" }; const result = await openMultiModel({ sessionName: session, models, binaryName: binary, mode: "cli" }); expect(result.success).toBe(true); expect(result.instructions).toContain("Error: Created tmux session"); expect(result.instructions).toContain("Failed: anthropic/gpt-5-2"); expect(result.windows).toContain("gpt-5-2"); expect(result.windows).toContain("gpt-5-2-2"); }); test("second model launch command fails", async () => { const session = "sess-launch"; const binary = "opencode"; const models = ["openai/gpt-5.2", "anthropic/gpt-5-2"]; mockCommandResponses["git rev-parse --is-inside-work-tree"] = { ok: true, stdout: "true", stderr: "" }; mockCommandResponses["command -v tmux"] = { ok: true, stdout: "tmux", stderr: "" }; mockCommandResponses["command -v opencode"] = { ok: true, stdout: "opencode", stderr: "" }; mockCommandResponses["opencode models"] = { ok: true, stdout: models.join("\n"), stderr: "" }; mockCommandResponses[`tmux has-session -t ${session}`] = { ok: false, stdout: "", stderr: "" }; // first model success const firstBranch = `opencode/${session}/gpt-5-2`; const firstPath = `/mock/home/.local/share/opencode/multi-model/${session}/gpt-5-2`; mockCommandResponses[`git show-ref --verify --quiet refs/heads/${firstBranch}`] = { ok: false, stdout: "", stderr: "" }; mockCommandResponses[`git worktree add -b ${firstBranch} ${firstPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux new-session -d -s ${session} -n gpt-5-2 -c ${firstPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux send-keys -t ${session}:gpt-5-2 ${binary} --model 'openai/gpt-5.2' C-m`] = { ok: true, stdout: "", stderr: "" }; // second model launch fails const secondBranch = `opencode/${session}/gpt-5-2-2`; const secondPath = `/mock/home/.local/share/opencode/multi-model/${session}/gpt-5-2-2`; mockCommandResponses[`git show-ref --verify --quiet refs/heads/${secondBranch}`] = { ok: false, stdout: "", stderr: "" }; mockCommandResponses[`git worktree add -b ${secondBranch} ${secondPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux new-window -d -t ${session} -n gpt-5-2-2 -c ${secondPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux send-keys -t ${session}:gpt-5-2-2 ${binary} --model 'anthropic/gpt-5-2' C-m`] = { ok: false, stdout: "", stderr: "launch err" }; mockCommandResponses[`git worktree remove -f ${secondPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`git branch -D ${secondBranch}`] = { ok: true, stdout: "", stderr: "" }; const result = await openMultiModel({ sessionName: session, models, binaryName: binary, mode: "cli" }); expect(result.success).toBe(true); expect(result.instructions).toContain("Failed: anthropic/gpt-5-2 (launch err"); expect(result.windows).toContain("gpt-5-2"); expect(result.windows).toContain("gpt-5-2-2"); }); test("second model worktree path already exists", async () => { const session = "sess-wtpath"; const binary = "opencode"; const models = ["openai/gpt-5.2", "anthropic/gpt-5-2"]; mockCommandResponses["git rev-parse --is-inside-work-tree"] = { ok: true, stdout: "true", stderr: "" }; mockCommandResponses["command -v tmux"] = { ok: true, stdout: "tmux", stderr: "" }; mockCommandResponses["command -v opencode"] = { ok: true, stdout: "opencode", stderr: "" }; mockCommandResponses["opencode models"] = { ok: true, stdout: models.join("\n"), stderr: "" }; mockCommandResponses[`tmux has-session -t ${session}`] = { ok: false, stdout: "", stderr: "" }; // first model success const firstBranch = `opencode/${session}/gpt-5-2`; const firstPath = `/mock/home/.local/share/opencode/multi-model/${session}/gpt-5-2`; mockCommandResponses[`git show-ref --verify --quiet refs/heads/${firstBranch}`] = { ok: false, stdout: "", stderr: "" }; mockCommandResponses[`git worktree add -b ${firstBranch} ${firstPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux new-session -d -s ${session} -n gpt-5-2 -c ${firstPath}`] = { ok: true, stdout: "", stderr: "" }; mockCommandResponses[`tmux send-keys -t ${session}:gpt-5-2 ${binary} --model 'openai/gpt-5.2' C-m`] = { ok: true, stdout: "", stderr: "" }; // second model path exists const secondBranch = `opencode/${session}/gpt-5-2-2`; const secondPath = `/mock/home/.local/share/opencode/multi-model/${session}/gpt-5-2-2`; mockCommandResponses[`git show-ref --verify --quiet refs/heads/${secondBranch}`] = { ok: false, stdout: "", stderr: "" }; // mock fs existsSync to return true for secondPath spyOn(fs, "existsSync").mockImplementation((p) => p === secondPath); const result = await openMultiModel({ sessionName: session, models, binaryName: binary, mode: "cli" }); expect(result.success).toBe(true); expect(result.instructions).toContain("Error: Created tmux session"); expect(result.instructions).toContain("Failed: anthropic/gpt-5-2 (Worktree path already exists at"); expect(result.windows).toContain("gpt-5-2"); expect(result.windows).toContain("gpt-5-2-2"); }); });