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 { afterEach, beforeEach, describe, expect, test } from "vitest";
import { execFileSync } from "node:child_process";
import { import {
createDaemonTestContext, createDaemonTestContext,
type DaemonTestContext, type DaemonTestContext,
} from "../test-utils/index.js"; } 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)", () => { describe("provider model catalogs (e2e)", () => {
let ctx: DaemonTestContext; let ctx: DaemonTestContext;
@@ -33,7 +46,7 @@ describe("provider model catalogs (e2e)", () => {
180_000 180_000
); );
test( test.runIf(hasCodex)(
"Codex catalog exposes gpt-5.1-codex", "Codex catalog exposes gpt-5.1-codex",
async () => { async () => {
const result = await ctx.client.listProviderModels("codex"); const result = await ctx.client.listProviderModels("codex");
@@ -45,7 +58,7 @@ describe("provider model catalogs (e2e)", () => {
180_000 180_000
); );
test( test.runIf(hasOpenCode)(
"OpenCode catalog returns models from multiple providers", "OpenCode catalog returns models from multiple providers",
async () => { async () => {
const result = await ctx.client.listProviderModels("opencode"); 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 { mkdtempSync, rmSync } from "fs";
import { tmpdir } from "os"; import { tmpdir } from "os";
import path from "path"; import path from "path";
import { execFileSync } from "node:child_process";
import { import {
createDaemonTestContext, createDaemonTestContext,
type DaemonTestContext, type DaemonTestContext,
@@ -49,6 +50,18 @@ function pickTwoDistinctModels(models: Array<{ id: string }>): [string, string]
return [ids[0]!, ids[1]!]; 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", () => { describe("daemon E2E", () => {
let ctx: DaemonTestContext; let ctx: DaemonTestContext;
let messages: SessionOutboundMessage[] = []; let messages: SessionOutboundMessage[] = [];
@@ -71,7 +84,12 @@ describe("daemon E2E", () => {
describe.each(["claude", "codex", "opencode"] as const)( describe.each(["claude", "codex", "opencode"] as const)(
"live model switching (%s)", "live model switching (%s)",
(provider) => { (provider) => {
test( const shouldRun =
provider === "claude" ||
(provider === "codex" && hasCodex) ||
(provider === "opencode" && hasOpenCode);
test.runIf(shouldRun)(
"updates agent model without restarting", "updates agent model without restarting",
async () => { async () => {
const cwd = tmpCwd(); const cwd = tmpCwd();
@@ -150,7 +168,7 @@ describe("daemon E2E", () => {
120000 120000
); );
test( test.runIf(hasCodex)(
"live thinking switching works for Codex (default -> non-default)", "live thinking switching works for Codex (default -> non-default)",
async () => { async () => {
const cwd = tmpCwd(); const cwd = tmpCwd();
@@ -198,7 +216,7 @@ describe("daemon E2E", () => {
120000 120000
); );
test( test.runIf(hasOpenCode)(
"live thinking switching works for OpenCode", "live thinking switching works for OpenCode",
async () => { async () => {
const cwd = tmpCwd(); 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 { mkdtempSync, writeFileSync, existsSync, rmSync, mkdirSync, readFileSync, readdirSync } from "fs";
import { tmpdir } from "os"; import { tmpdir } from "os";
import path from "path"; import path from "path";
import { execFileSync } from "node:child_process";
import { import {
createDaemonTestContext, createDaemonTestContext,
type DaemonTestContext, type DaemonTestContext,
@@ -15,6 +16,18 @@ function tmpCwd(): string {
const CODEX_TEST_MODEL = "gpt-5.1-codex-mini"; 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", () => { describe("daemon E2E", () => {
let ctx: DaemonTestContext; let ctx: DaemonTestContext;
@@ -27,7 +40,7 @@ describe("daemon E2E", () => {
}, 60000); }, 60000);
describe("listProviderModels", () => { describe("listProviderModels", () => {
test( test.runIf(hasCodex)(
"returns model list for Codex provider", "returns model list for Codex provider",
async () => { async () => {
// List models for Codex provider - no agent needed // List models for Codex provider - no agent needed
@@ -75,7 +88,7 @@ describe("daemon E2E", () => {
60000 // 1 minute timeout 60000 // 1 minute timeout
); );
test( test.runIf(hasOpenCode)(
"returns model list for OpenCode provider", "returns model list for OpenCode provider",
async () => { async () => {
const result = await ctx.client.listProviderModels("opencode"); const result = await ctx.client.listProviderModels("opencode");
@@ -11,7 +11,9 @@ function tmpCwd(): string {
return mkdtempSync(path.join(tmpdir(), "daemon-terminal-e2e-")); 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; let ctx: DaemonTestContext;
beforeEach(async () => { beforeEach(async () => {
@@ -393,7 +393,8 @@ export class TerminalManager {
paneId, paneId,
"#{pane_dead_status}", "#{pane_dead_status}",
]); ]);
exitCode = parseInt(exitCodeStr, 10); const parsed = parseInt(exitCodeStr, 10);
exitCode = Number.isFinite(parsed) ? parsed : null;
} }
commands.push({ commands.push({
@@ -448,7 +449,8 @@ export class TerminalManager {
paneId, paneId,
"#{pane_dead_status}", "#{pane_dead_status}",
]); ]);
exitCode = parseInt(exitCodeStr, 10); const parsed = parseInt(exitCodeStr, 10);
exitCode = Number.isFinite(parsed) ? parsed : null;
} }
return { return {