diff --git a/apps/api/src/api/middlewares/vortexAdminAuth.test.ts b/apps/api/src/api/middlewares/vortexAdminAuth.test.ts index 3dd092385..ddb7f50ec 100644 --- a/apps/api/src/api/middlewares/vortexAdminAuth.test.ts +++ b/apps/api/src/api/middlewares/vortexAdminAuth.test.ts @@ -1,4 +1,4 @@ -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, spyOn } from "bun:test"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, mock, spyOn } from "bun:test"; import express, { Request, Response } from "express"; import { config } from "../../config/vars"; import ProfileRole from "../../models/profileRole.model"; @@ -39,6 +39,7 @@ describe("requireVortexAdmin", () => { }); afterEach(() => { + mock.restore(); config.impersonationEnabled = originalImpersonationEnabled; }); diff --git a/apps/api/src/api/routes/v1/admin-console/admin-console.route.test.ts b/apps/api/src/api/routes/v1/admin-console/admin-console.route.test.ts index 27a59901d..3a82d92ba 100644 --- a/apps/api/src/api/routes/v1/admin-console/admin-console.route.test.ts +++ b/apps/api/src/api/routes/v1/admin-console/admin-console.route.test.ts @@ -1,4 +1,4 @@ -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, spyOn } from "bun:test"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, mock, spyOn } from "bun:test"; import express from "express"; import { config } from "../../../../config/vars"; import AdminImpersonationSession from "../../../../models/adminImpersonationSession.model"; @@ -42,6 +42,7 @@ describe("admin-console routes", () => { }); afterEach(() => { + mock.restore(); config.impersonationEnabled = originalImpersonationEnabled; }); diff --git a/apps/api/src/api/services/impersonation.service.test.ts b/apps/api/src/api/services/impersonation.service.test.ts index f7cce1fc3..34deb4e82 100644 --- a/apps/api/src/api/services/impersonation.service.test.ts +++ b/apps/api/src/api/services/impersonation.service.test.ts @@ -1,4 +1,4 @@ -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, spyOn } from "bun:test"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, mock, spyOn } from "bun:test"; import crypto from "crypto"; import { config } from "../../config/vars"; import AdminImpersonationSession from "../../models/adminImpersonationSession.model"; @@ -28,6 +28,7 @@ describe("impersonation.service", () => { }); afterEach(() => { + mock.restore(); config.impersonationEnabled = originalImpersonationEnabled; }); diff --git a/apps/api/src/tests/spy-restore.invariants.test.ts b/apps/api/src/tests/spy-restore.invariants.test.ts new file mode 100644 index 000000000..86adfaf03 --- /dev/null +++ b/apps/api/src/tests/spy-restore.invariants.test.ts @@ -0,0 +1,18 @@ +import { expect, it } from "bun:test"; +import { join } from "path"; + +// bun restores a spied method when its test file ends but keeps the spy's call history: a +// later file's spyOn() on the same method then starts with calls that file never made. +it("every test file that installs spyOn() spies restores them", async () => { + const root = join(import.meta.dir, ".."); + const offenders: string[] = []; + for await (const path of new Bun.Glob("**/*.test.ts").scan(root)) { + const file = join(root, path); + if (file === import.meta.path) continue; + const source = await Bun.file(file).text(); + if (source.includes("spyOn(") && !/mock\.restore\(\)|\.mockRestore\(\)/.test(source)) { + offenders.push(path); + } + } + expect(offenders).toEqual([]); +});