diff --git a/cli/TESTING.md b/cli/TESTING.md index 235a95c4b1..b7a1e701b8 100644 --- a/cli/TESTING.md +++ b/cli/TESTING.md @@ -22,6 +22,12 @@ Pure local tests — no backend, no database. Uses `bunfig.unit.toml` (no preloa Examples: `git_unit`, `lint_command_unit`, `tar_creation_unit`, `workspace_conflicts_unit` +`mock.module()` mocks the module for the whole `bun test` process, not for the file +that installs it, and `mock.restore()` does not undo it. A file that mocks a module +must hand back its real exports in `afterAll` (see +`schedule_push_permissioned_as_unit`), or it silently rewires whichever file runs +next — and the run order is the directory's, so it differs between Linux and Windows. + ### Integration tests Require a running backend and PostgreSQL. The `setup.ts` preload builds the backend diff --git a/cli/test/schedule_push_permissioned_as_unit.test.ts b/cli/test/schedule_push_permissioned_as_unit.test.ts index 941415409d..17a1731494 100644 --- a/cli/test/schedule_push_permissioned_as_unit.test.ts +++ b/cli/test/schedule_push_permissioned_as_unit.test.ts @@ -5,7 +5,7 @@ * that builds none silently reassigns the schedule to whoever ran it. */ -import { expect, test, describe, beforeEach, mock } from "bun:test"; +import { expect, test, describe, afterAll, beforeEach, mock } from "bun:test"; import { mkdtemp, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -25,7 +25,27 @@ const REMOTE_SCHEDULE = () => ({ permissioned_as: remotePermissionedAs, }); -mock.module("../gen/services.gen.ts", () => ({ +// A module mock is process-global and outlives the file that installs it, and +// `mock.restore()` does not undo one: every mocked module has to be handed back +// its real exports here, or whichever file `bun test` happens to run next gets +// this file's stubs. +const realModules: [string, Record][] = []; +async function mockModule( + specifier: string, + factory: (real: Record) => Record +): Promise { + const real = { ...((await import(specifier)) as Record) }; + realModules.push([specifier, real]); + mock.module(specifier, () => factory(real)); +} + +afterAll(() => { + for (const [specifier, real] of realModules) { + mock.module(specifier, () => real); + } +}); + +await mockModule("../gen/services.gen.ts", () => ({ getSchedule: async () => REMOTE_SCHEDULE(), updateSchedule: async (a: unknown) => { updateScheduleCalls.push(a); @@ -38,9 +58,8 @@ mock.module("../gen/services.gen.ts", () => ({ }), })); -const realContext = await import("../src/core/context.ts"); -mock.module("../src/core/context.ts", () => ({ - ...realContext, +await mockModule("../src/core/context.ts", (real) => ({ + ...real, resolveWorkspace: async () => ({ workspaceId: "w", name: "w", @@ -49,9 +68,8 @@ mock.module("../src/core/context.ts", () => ({ }), })); -const realAuth = await import("../src/core/auth.ts"); -mock.module("../src/core/auth.ts", () => ({ - ...realAuth, +await mockModule("../src/core/auth.ts", (real) => ({ + ...real, requireLogin: async () => ({}), }));