Make CI independent of external CLIs

This commit is contained in:
Mohamed Boudra
2026-02-05 00:03:15 +07:00
parent 2f3cbe5e84
commit 0438a43cb0
5 changed files with 58 additions and 10 deletions
@@ -1,10 +1,23 @@
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { execFileSync } from "node:child_process";
import {
createDaemonTestContext,
type DaemonTestContext,
} from "../test-utils/index.js";
function isBinaryInstalled(binary: string): boolean {
try {
const out = execFileSync("which", [binary], { encoding: "utf8" }).trim();
return out.length > 0;
} catch {
return false;
}
}
const hasCodex = isBinaryInstalled("codex");
const hasOpenCode = isBinaryInstalled("opencode");
describe("provider model catalogs (e2e)", () => {
let ctx: DaemonTestContext;
@@ -33,7 +46,7 @@ describe("provider model catalogs (e2e)", () => {
180_000
);
test(
test.runIf(hasCodex)(
"Codex catalog exposes gpt-5.1-codex",
async () => {
const result = await ctx.client.listProviderModels("codex");
@@ -45,7 +58,7 @@ describe("provider model catalogs (e2e)", () => {
180_000
);
test(
test.runIf(hasOpenCode)(
"OpenCode catalog returns models from multiple providers",
async () => {
const result = await ctx.client.listProviderModels("opencode");
@@ -2,6 +2,7 @@ import { describe, test, expect, beforeEach, afterEach } from "vitest";
import { mkdtempSync, rmSync } from "fs";
import { tmpdir } from "os";
import path from "path";
import { execFileSync } from "node:child_process";
import {
createDaemonTestContext,
type DaemonTestContext,
@@ -49,6 +50,18 @@ function pickTwoDistinctModels(models: Array<{ id: string }>): [string, string]
return [ids[0]!, ids[1]!];
}
function isBinaryInstalled(binary: string): boolean {
try {
const out = execFileSync("which", [binary], { encoding: "utf8" }).trim();
return out.length > 0;
} catch {
return false;
}
}
const hasCodex = isBinaryInstalled("codex");
const hasOpenCode = isBinaryInstalled("opencode");
describe("daemon E2E", () => {
let ctx: DaemonTestContext;
let messages: SessionOutboundMessage[] = [];
@@ -71,7 +84,12 @@ describe("daemon E2E", () => {
describe.each(["claude", "codex", "opencode"] as const)(
"live model switching (%s)",
(provider) => {
test(
const shouldRun =
provider === "claude" ||
(provider === "codex" && hasCodex) ||
(provider === "opencode" && hasOpenCode);
test.runIf(shouldRun)(
"updates agent model without restarting",
async () => {
const cwd = tmpCwd();
@@ -150,7 +168,7 @@ describe("daemon E2E", () => {
120000
);
test(
test.runIf(hasCodex)(
"live thinking switching works for Codex (default -> non-default)",
async () => {
const cwd = tmpCwd();
@@ -198,7 +216,7 @@ describe("daemon E2E", () => {
120000
);
test(
test.runIf(hasOpenCode)(
"live thinking switching works for OpenCode",
async () => {
const cwd = tmpCwd();
@@ -2,6 +2,7 @@ import { describe, test, expect, beforeEach, afterEach } from "vitest";
import { mkdtempSync, writeFileSync, existsSync, rmSync, mkdirSync, readFileSync, readdirSync } from "fs";
import { tmpdir } from "os";
import path from "path";
import { execFileSync } from "node:child_process";
import {
createDaemonTestContext,
type DaemonTestContext,
@@ -15,6 +16,18 @@ function tmpCwd(): string {
const CODEX_TEST_MODEL = "gpt-5.1-codex-mini";
function isBinaryInstalled(binary: string): boolean {
try {
const out = execFileSync("which", [binary], { encoding: "utf8" }).trim();
return out.length > 0;
} catch {
return false;
}
}
const hasCodex = isBinaryInstalled("codex");
const hasOpenCode = isBinaryInstalled("opencode");
describe("daemon E2E", () => {
let ctx: DaemonTestContext;
@@ -27,7 +40,7 @@ describe("daemon E2E", () => {
}, 60000);
describe("listProviderModels", () => {
test(
test.runIf(hasCodex)(
"returns model list for Codex provider",
async () => {
// List models for Codex provider - no agent needed
@@ -75,7 +88,7 @@ describe("daemon E2E", () => {
60000 // 1 minute timeout
);
test(
test.runIf(hasOpenCode)(
"returns model list for OpenCode provider",
async () => {
const result = await ctx.client.listProviderModels("opencode");
@@ -11,7 +11,9 @@ function tmpCwd(): string {
return mkdtempSync(path.join(tmpdir(), "daemon-terminal-e2e-"));
}
describe("daemon E2E terminal", () => {
const shouldRun = !process.env.CI;
(shouldRun ? describe : describe.skip)("daemon E2E terminal", () => {
let ctx: DaemonTestContext;
beforeEach(async () => {
@@ -393,7 +393,8 @@ export class TerminalManager {
paneId,
"#{pane_dead_status}",
]);
exitCode = parseInt(exitCodeStr, 10);
const parsed = parseInt(exitCodeStr, 10);
exitCode = Number.isFinite(parsed) ? parsed : null;
}
commands.push({
@@ -448,7 +449,8 @@ export class TerminalManager {
paneId,
"#{pane_dead_status}",
]);
exitCode = parseInt(exitCodeStr, 10);
const parsed = parseInt(exitCodeStr, 10);
exitCode = Number.isFinite(parsed) ? parsed : null;
}
return {