From c9374abfc7f262bd15709546cfbfdc01a943d8eb Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Mon, 26 Oct 2026 14:15:14 +0100 Subject: [PATCH] test: fix integration-run failures in provider, model and session-index tests (#1439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes the 23 failures seen with `RUN_INTEGRATION_TESTS=true`. All are test-side; no adapter changes. - **Provider tests (20 failures):** since #1340, `providers/set` lists the gateway's models with `fetch` in the test process. `vitest.config.ts` only loaded `setup-no-network.ts` outside integration runs, so tests that point at `https://gateway.example.com` made real requests and failed with `fetch failed`. The stub now loads in every run. `gateway-models.ts` is the only in-process `fetch` caller, and no integration test targets a real gateway. - **"the real SDK exposes one plain Opus row":** the CLI catalogue now also lists pinned older models (`claude-opus-5`, `claude-opus-4-8`, …), and the alias row's display name is "Opus 5.5". The check now covers only alias rows (`value` starting with `opus`) and still asserts there is a single row with no 1M variant. - **session-index `ENOTEMPTY` in teardown:** integration runs leave `CLAUDE_CODE_EXECUTABLE` unset, so initialize ran the real `claude auth status` in the background. It wrote into the temp `CLAUDE_CONFIG_DIR` while `afterEach` removed it. The file now stubs the executable to `/usr/bin/false`, as the scenario tests do. - **session-index relocation test:** the wait ended on any removal from the old path's subscription. That removal can name the other path's session, so the test now waits for the relocated session's own id. ## Testing - The six previously failing files pass with `RUN_INTEGRATION_TESTS=true` (454 tests). - `session-index.test.ts` passed 3× with integration on and 1× without (173 tests). Those failures were intermittent, so a few clean runs don't fully prove the fix. - `npx vitest run` (3360 tests) and `npm run check` pass. - The full integration suite was not rerun end to end. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 --- src/tests/model-presentation.test.ts | 11 +++++------ src/tests/session-index.test.ts | 8 +++++++- vitest.config.ts | 5 +++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/tests/model-presentation.test.ts b/src/tests/model-presentation.test.ts index ad6b403b..47c832e8 100644 --- a/src/tests/model-presentation.test.ts +++ b/src/tests/model-presentation.test.ts @@ -238,19 +238,18 @@ it.skipIf(!process.env.RUN_INTEGRATION_TESTS)( const q = query({ prompt: input(), options: { persistSession: false, abortController } }); try { const { models } = await q.initializationResult(); - const opusModels = models.filter( - (model) => - model.value !== "default" && - /\bopus\b/i.test(`${model.value} ${model.resolvedModel} ${model.displayName}`), - ); + // Only alias rows ("opus", "opus[1m]"). The catalogue also lists pinned + // older versions such as "claude-opus-4-8"; those are separate models. + const opusModels = models.filter((model) => /^opus\b/i.test(model.value)); expect(opusModels).toHaveLength(1); // CLI 2.1.283 dropped the "[1m]" / "(1M context)" spelling from the Opus // row: Opus is natively 1M, so the row names the plain model. expect(opusModels[0]).toMatchObject({ value: "opus", - displayName: "Opus", + displayName: expect.stringMatching(/^Opus\b/), resolvedModel: expect.stringMatching(/^claude-opus-[\d-]+$/), }); + expect(opusModels[0].displayName).not.toMatch(/\b1m\b/i); expect(opusModels[0].description).not.toMatch(/\b1m\b/i); } finally { clearTimeout(timeout); diff --git a/src/tests/session-index.test.ts b/src/tests/session-index.test.ts index 087a955a..c754eef7 100644 --- a/src/tests/session-index.test.ts +++ b/src/tests/session-index.test.ts @@ -69,10 +69,15 @@ beforeEach(async () => { configDir = fsSync.realpathSync(await fs.mkdtemp(path.join(os.tmpdir(), "session-index-cfg-"))); workspace = fsSync.realpathSync(await fs.mkdtemp(path.join(os.tmpdir(), "session-index-ws-"))); process.env.CLAUDE_CONFIG_DIR = configDir; + // Integration runs leave CLAUDE_CODE_EXECUTABLE unset, so initialize would run + // the real `claude auth status` in the background. It writes into configDir + // while afterEach removes it, and the rm fails with ENOTEMPTY. + vi.stubEnv("CLAUDE_CODE_EXECUTABLE", "/usr/bin/false"); vi.clearAllMocks(); }); afterEach(async () => { + vi.unstubAllEnvs(); if (previousConfigDir === undefined) delete process.env.CLAUDE_CONFIG_DIR; else process.env.CLAUDE_CONFIG_DIR = previousConfigDir; await fs.rm(configDir, { recursive: true, force: true }); @@ -3528,7 +3533,8 @@ describe("paths that share a project directory", () => { .filter((s) => s.sessionId === mine.id); const removed = () => changesFor(notifications, fromDotted.subscriptionId).flatMap((c) => c.removed); - expect(await waitFor(() => added().length > 0 && removed().length > 0)).toBe(true); + // Wait for mine.id itself: the other path's session can be removed first. + expect(await waitFor(() => added().length > 0 && removed().includes(mine.id))).toBe(true); expect(added().map((s) => [s.sessionId, s.cwd])).toEqual([[mine.id, dashed]]); // It may also name the other path's session, never listed here. expect(removed()).toContain(mine.id); diff --git a/vitest.config.ts b/vitest.config.ts index b877dab7..cafd3045 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -14,8 +14,9 @@ export default defineConfig({ // tests off the machine's real CLI and Claude config, and stops a run // from outliving its test (it wrote into temp config dirs being deleted). env: usesRealCli ? {} : { CLAUDE_CODE_EXECUTABLE: "/usr/bin/false" }, - // Real-CLI runs may want a real gateway listing. - setupFiles: usesRealCli ? [] : ["src/tests/setup-no-network.ts"], + // Applies to real-CLI runs too: the gateway listing runs in the test process, + // and tests point `providers/set` at fake URLs like gateway.example.com. + setupFiles: ["src/tests/setup-no-network.ts"], include: [ "src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}", // Release tooling lives in scripts/ rather than src/ so it stays outside tsc's