diff --git a/test/helpers/global-sandbox.ts b/test/helpers/global-sandbox.ts index b763ba48..c0077cc3 100644 --- a/test/helpers/global-sandbox.ts +++ b/test/helpers/global-sandbox.ts @@ -28,3 +28,14 @@ process.env.CODEX_MULTI_AUTH_DIR = join(SANDBOX_ROOT, ".codex", "multi-auth"); // Expose for assertions / debugging. (globalThis as { __CMA_TEST_SANDBOX_ROOT__?: string }).__CMA_TEST_SANDBOX_ROOT__ = SANDBOX_ROOT; + +// Suite-wide spy hygiene (see PR #590): a test that fails before its inline +// mockRestore() leaks its spy, and a later vi.spyOn on the same method returns +// the SAME leaked spy — so passthrough bindings captured from it recurse into +// the new test's own mock. Restoring after every test contains the cascade. +// No suite creates spies in beforeAll, so an after-each restore is safe. +import { afterEach, vi } from "vitest"; + +afterEach(() => { + vi.restoreAllMocks(); +}); diff --git a/test/storage.test.ts b/test/storage.test.ts index 9778292e..894c3040 100644 --- a/test/storage.test.ts +++ b/test/storage.test.ts @@ -53,6 +53,14 @@ describe("storage", () => { if (_origCODEX_MULTI_AUTH_DIR !== undefined) process.env.CODEX_MULTI_AUTH_DIR = _origCODEX_MULTI_AUTH_DIR; else delete process.env.CODEX_MULTI_AUTH_DIR; + // A test that fails before its inline mockRestore() leaks its fs spy into + // every later test (a later vi.spyOn returns the SAME leaked spy, so a + // passthrough binding recurses into the new test's own mock). Restore all + // spies unconditionally so one failure cannot cascade. The global + // afterEach in test/helpers/global-sandbox.ts now does this for every + // suite; this local call stays as defense-in-depth for the suite that + // actually exhibited the cascade, in case the global hook ever moves. + vi.restoreAllMocks(); }); describe("account storage directory hardening", () => {