Files
opencode-multi-model/tests/open_error.test.ts
T

540 lines
17 KiB
TypeScript

import {
afterEach,
beforeEach,
describe,
expect,
mock,
spyOn,
test,
} from "bun:test";
import * as fs from "node:fs";
import * as os from "node:os";
import { openMultiModel } from "../src/core/open";
const originalBun$: typeof Bun.$ = (globalThis as any).Bun?.$;
let mockCommandResponses: Record<
string,
{ ok: boolean; stdout: string; stderr: string }
> = {};
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");
});
});