import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { createApp } from "../../apps/api/src/app.js"; import { RegistrationService } from "../../apps/api/src/registration.js"; import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js"; import { registrationNotice } from "../../packages/shared-contracts/src/registration-notice.js"; const roots: string[] = []; const services: RegistrationService[] = []; const writeHeaders = { host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121" }; function createHarness() { const root = mkdtempSync(join(tmpdir(), "dada-wp1-03-notice-")); roots.push(root); const resend = new MockResendAdapter(); const registration = new RegistrationService({ challengePepper: Buffer.alloc(32, 0x74), clock: () => Date.parse("2026-07-28T09:30:00.000Z"), codeGenerator: () => "418205", currentPrivacyNoticeVersion: registrationNotice.version, databasePath: join(root, "dada.sqlite3"), inviteCodeGenerator: () => "DADA-WP1-03-NOTICE", invitePepper: Buffer.alloc(32, 0x75), resend, sessionPepper: Buffer.alloc(32, 0x76), }); services.push(registration); return { registration, resend }; } function snapshot(registration: RegistrationService) { return { consents: registration.database.prepare("SELECT COUNT(*) AS count FROM privacy_consents").get().count, credits: registration.database.prepare("SELECT COUNT(*) AS count FROM credit_accounts").get().count, invite_used: registration.database.prepare("SELECT COALESCE(SUM(used_count), 0) AS count FROM invite_codes").get().count, sessions: registration.database.prepare("SELECT COUNT(*) AS count FROM sessions").get().count, users: registration.database.prepare("SELECT COUNT(*) AS count FROM users").get().count, }; } function writeEvidence(file: string, value: unknown) { const directory = process.env.DADA_EVIDENCE_DIR_NOTICE; if (!directory) return; mkdirSync(directory, { recursive: true }); writeFileSync(resolve(directory, file), `${JSON.stringify(value, null, 2)}\n`); } afterEach(() => { for (const service of services.splice(0)) service.close(); for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true }); }); describe("TDD-WP1-NOTICE-001-registration-consent", () => { it("rejects missing/stale consent without side effects and records current consent atomically", async () => { const { registration, resend } = createHarness(); const invite = registration.createInvite({ expiresAt: Date.parse("2026-07-29T09:30:00.000Z"), maxUses: 1 }); const sent = await registration.sendRegistrationCode({ email: "notice-api@example.invalid", inviteCode: invite.code }); const code = resend.readLatestCode("notice-api@example.invalid"); const app = await createApp({ browserGate: false, networkBoundary: { allowTestPort: true }, registration }); const before = snapshot(registration); const basePayload = { creator_name: "Notice User", privacy_notice_version: registrationNotice.version, registration_id: sent.registrationId, social_id: "@notice_user", verification_code: code, }; const missingConsent = await app.inject({ headers: { ...writeHeaders, "idempotency-key": "wp1-03-consent-missing-0000000000001" }, method: "POST", payload: { ...basePayload, privacy_consent_accepted: false }, url: "/api/v1/auth/register/complete", }); expect(missingConsent.statusCode).toBe(400); expect(missingConsent.json()).toMatchObject({ error: { details: { field_errors: [{ message_key: "auth.privacy.consent_required" }] } }, }); expect(snapshot(registration)).toEqual(before); const staleVersion = await app.inject({ headers: { ...writeHeaders, "idempotency-key": "wp1-03-consent-stale-00000000000001" }, method: "POST", payload: { ...basePayload, privacy_consent_accepted: true, privacy_notice_version: "stale-notice" }, url: "/api/v1/auth/register/complete", }); expect(staleVersion.statusCode).toBe(400); expect(staleVersion.json()).toMatchObject({ error: { details: { field_errors: [{ message_key: "auth.privacy.notice_version_invalid" }] } }, }); expect(snapshot(registration)).toEqual(before); const completed = await app.inject({ headers: { ...writeHeaders, "idempotency-key": "wp1-03-consent-success-0000000000001" }, method: "POST", payload: { ...basePayload, privacy_consent_accepted: true }, url: "/api/v1/auth/register/complete", }); expect(completed.statusCode).toBe(200); const after = snapshot(registration); expect(after).toEqual({ consents: 1, credits: 1, invite_used: 1, sessions: 1, users: 1 }); const consent = registration.database.prepare("SELECT notice_version, consented_at FROM privacy_consents").get(); expect(consent).toEqual({ consented_at: Date.parse("2026-07-28T09:30:00.000Z"), notice_version: registrationNotice.version, }); writeEvidence("response.json", { accepted: completed.json().status, missing_consent: missingConsent.json().error.details.field_errors[0].message_key, stale_version: staleVersion.json().error.details.field_errors[0].message_key, }); writeEvidence("db-diff.json", { after, before, consent_recorded_at: "2026-07-28T09:30:00.000Z", notice_content_sha256: registrationNotice.contentSha256, notice_effective_at: registrationNotice.effectiveAt, notice_version: registrationNotice.version, }); await app.close(); }); });