From 51a0a60a738ac717ed03624cf247a30eb7f5357d Mon Sep 17 00:00:00 2001 From: Zakaria Date: Sat, 24 Oct 2026 11:30:46 -0400 Subject: [PATCH] test(api): stop tests leaking module mocks and env into later files CI failed on a different set of tests each run because bun runs every test file in one process and the file order differs between machines: - the secretsmanager route test replaced the whole ../aws module, so files loaded after it lost awsClients, lambda and defaultAccountId (API Gateway, SES) - the Lightsail tests restored FLOCI_ENDPOINT by assigning undefined, which stores the string "undefined" (runtime diagnostics) - the auth tests used full-strength argon2 for ~30 sign-ins and timed out on slower runners; the OIDC cleanup test cached a discovery document without userinfo_endpoint for the shared issuer Checked with 20 runs under bun test --randomize. --- packages/api/src/adapter-aws/AwsLightsailAdapter.test.ts | 4 +++- packages/api/src/auth.test.ts | 7 +++++-- packages/api/src/routes/secretsmanager.test.ts | 7 +++++-- packages/api/src/services/lightsailDeployment.test.ts | 4 +++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/api/src/adapter-aws/AwsLightsailAdapter.test.ts b/packages/api/src/adapter-aws/AwsLightsailAdapter.test.ts index 86e7ce2..0f6e07c 100644 --- a/packages/api/src/adapter-aws/AwsLightsailAdapter.test.ts +++ b/packages/api/src/adapter-aws/AwsLightsailAdapter.test.ts @@ -16,7 +16,9 @@ import {AwsLightsailAdapter} from './AwsLightsailAdapter' const originalEndpoint = process.env.FLOCI_ENDPOINT afterEach(() => { - process.env.FLOCI_ENDPOINT = originalEndpoint + // Assigning undefined would store the string "undefined" for every later test file. + if (originalEndpoint === undefined) delete process.env.FLOCI_ENDPOINT + else process.env.FLOCI_ENDPOINT = originalEndpoint }) function stubLightsail() { diff --git a/packages/api/src/auth.test.ts b/packages/api/src/auth.test.ts index 2f6f566..2798fa6 100644 --- a/packages/api/src/auth.test.ts +++ b/packages/api/src/auth.test.ts @@ -13,7 +13,9 @@ const realFetch = globalThis.fetch let passwordHash: string beforeAll(async () => { - passwordHash = await Bun.password.hash('correct-horse-battery') + // Cheap parameters keep the many sign-in attempts below fast on slow CI runners; verification + // reads the parameters from the hash, so the code under test is unchanged. + passwordHash = await Bun.password.hash('correct-horse-battery', {algorithm: 'argon2id', memoryCost: 1024, timeCost: 1}) }) afterEach(() => { @@ -239,7 +241,8 @@ describe('auth state cleanup', () => { test('caps pending OIDC requests and sweeps expired state', async () => { const config = authConfig(oidcEnv({FLOCI_UI_OIDC_ALLOW_ALL_USERS: 'true'})) const hono = app(config) - globalThis.fetch = (async () => Response.json({authorization_endpoint: 'https://idp.test/authorize', token_endpoint: 'https://idp.test/token', jwks_uri: 'https://idp.test/jwks'})) as unknown as typeof fetch + // The same document as mockIdp: discovery is cached per issuer for the rest of the run. + globalThis.fetch = (async () => Response.json({authorization_endpoint: 'https://idp.test/authorize', token_endpoint: 'https://idp.test/token', jwks_uri: 'https://idp.test/jwks', userinfo_endpoint: 'https://idp.test/userinfo'})) as unknown as typeof fetch for (let index = 0; index < 1_050; index++) await request(hono, '/api/auth/oidc/login', `10.1.${Math.floor(index / 250)}.${index % 250}`) expect(authStateSizes().oidcRequests).toBe(1_000) diff --git a/packages/api/src/routes/secretsmanager.test.ts b/packages/api/src/routes/secretsmanager.test.ts index ba00bac..fad710d 100644 --- a/packages/api/src/routes/secretsmanager.test.ts +++ b/packages/api/src/routes/secretsmanager.test.ts @@ -1,13 +1,16 @@ import {beforeEach, describe, expect, mock, test} from 'bun:test' // The secretsmanager router imports the `secretsManager` singleton directly from -// `../aws`, so we replace that module with a stub whose `send()` is driven per -// test. `responder` returns the fake AWS SDK response and `lastCommand` captures +// `../aws`, so we replace that export with a stub whose `send()` is driven per +// test. Bun keeps a module mock for the rest of the run, so every other export +// stays real: test files that load `../aws` after this one still get it whole. `responder` returns the fake AWS SDK response and `lastCommand` captures // the command that was dispatched so we can assert on its input shape. let lastCommand: {constructor: {name: string}; input: Record} | null = null let responder: (commandName: string, command: {input: Record}) => unknown +const realAws = await import('../aws') mock.module('../aws', () => ({ + ...realAws, secretsManager: { async send(command: {constructor: {name: string}; input: Record}) { lastCommand = command diff --git a/packages/api/src/services/lightsailDeployment.test.ts b/packages/api/src/services/lightsailDeployment.test.ts index 0795567..1f4a13f 100644 --- a/packages/api/src/services/lightsailDeployment.test.ts +++ b/packages/api/src/services/lightsailDeployment.test.ts @@ -5,7 +5,9 @@ const originalEndpoint = process.env.FLOCI_ENDPOINT const originalFetch = globalThis.fetch afterEach(() => { - process.env.FLOCI_ENDPOINT = originalEndpoint + // Assigning undefined would store the string "undefined" for every later test file. + if (originalEndpoint === undefined) delete process.env.FLOCI_ENDPOINT + else process.env.FLOCI_ENDPOINT = originalEndpoint globalThis.fetch = originalFetch mock.restore() })