diff --git a/apps/api/tests/routes/setup.test.ts b/apps/api/tests/routes/setup.test.ts index 0942c6e..630f058 100644 --- a/apps/api/tests/routes/setup.test.ts +++ b/apps/api/tests/routes/setup.test.ts @@ -345,6 +345,12 @@ describe('POST /api/setup/validate/vapid', () => { it('returns 200 { ok: true } for a valid VAPID key pair from generate-secrets', async () => { process.env.VAPID_PUBLIC_KEY = VAPID_PUBLIC_KEY; process.env.VAPID_PRIVATE_KEY = VAPID_PRIVATE_KEY; + // Gap 2: the operator-submitted public key (app_config.vapid_public_key) must equal + // process.env.VAPID_PUBLIC_KEY for the happy path. Seed the matching row. + await db + .insert(appConfig) + .values({ key: 'vapid_public_key', value: VAPID_PUBLIC_KEY }) + .onDuplicateKeyUpdate({ set: { value: VAPID_PUBLIC_KEY } }); const app = await getApp(); const res = await app.fetch(jsonRequest('POST', '/api/setup/validate/vapid')); @@ -353,6 +359,43 @@ describe('POST /api/setup/validate/vapid', () => { expect(body.ok).toBe(true); }); + // Gap 2 (major): a wrong wizard-entered public key (e.g. "BH123") must fail the row. + // The env VAPID pair is valid, but the submitted key in app_config does not match + // process.env.VAPID_PUBLIC_KEY → 400. The response must NEVER contain VAPID_PRIVATE_KEY (T-12-06). + it('returns 400 when submitted vapid_public_key does not match process.env.VAPID_PUBLIC_KEY (gap 2)', async () => { + process.env.VAPID_PUBLIC_KEY = VAPID_PUBLIC_KEY; + process.env.VAPID_PRIVATE_KEY = VAPID_PRIVATE_KEY; + // Operator typed a clearly-wrong key — env pair is still structurally valid. + await db + .insert(appConfig) + .values({ key: 'vapid_public_key', value: 'BH123' }) + .onDuplicateKeyUpdate({ set: { value: 'BH123' } }); + + const app = await getApp(); + const res = await app.fetch(jsonRequest('POST', '/api/setup/validate/vapid')); + expect(res.status).toBe(400); + const bodyText = await res.text(); + const body = JSON.parse(bodyText) as { ok: boolean }; + expect(body.ok).toBe(false); + // T-12-06: VAPID_PRIVATE_KEY must never leak into any response. + expect(bodyText).not.toContain(VAPID_PRIVATE_KEY); + }); + + // Gap 2: without an operator-submitted key there is nothing to compare → 400. + it('returns 400 when app_config.vapid_public_key row is absent (no submitted key)', async () => { + process.env.VAPID_PUBLIC_KEY = VAPID_PUBLIC_KEY; + process.env.VAPID_PRIVATE_KEY = VAPID_PRIVATE_KEY; + // No vapid_public_key seeded in app_config (cleaned in afterEach). + + const app = await getApp(); + const res = await app.fetch(jsonRequest('POST', '/api/setup/validate/vapid')); + expect(res.status).toBe(400); + const bodyText = await res.text(); + const body = JSON.parse(bodyText) as { ok: boolean }; + expect(body.ok).toBe(false); + expect(bodyText).not.toContain(VAPID_PRIVATE_KEY); + }); + it('returns 400 { ok: false } when VAPID env vars are missing', async () => { // VAPID env vars not set (cleared in beforeEach) const app = await getApp();