import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } 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 { isErrorEnvelope } from "../../packages/shared-contracts/src/index.js"; const fixedNow = Date.parse("2026-07-28T06:00:00.000Z"); const writeHeaders = { host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121" }; const roots: string[] = []; const services: RegistrationService[] = []; function createRegistrationService() { const root = mkdtempSync(join(tmpdir(), "dada-wp1-01-api-")); roots.push(root); const resend = new MockResendAdapter(); const registration = new RegistrationService({ challengePepper: Buffer.alloc(32, 0x41), clock: () => fixedNow, codeGenerator: () => "572914", currentPrivacyNoticeVersion: "p0a-notice-v1", databasePath: join(root, "dada.sqlite3"), inviteCodeGenerator: () => "DADA-WP1-API", invitePepper: Buffer.alloc(32, 0x42), resend, sessionPepper: Buffer.alloc(32, 0x43), }); services.push(registration); return { registration, resend }; } afterEach(() => { for (const service of services.splice(0)) { try { service.close(); } catch { /* already closed by the test */ } } for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true }); }); describe("TASK-WP1-01 registration API contract", () => { it("resolves REGISTER_SEND and REGISTER_COMPLETE to one OpenAPI operation each", async () => { const { registration, resend } = createRegistrationService(); const invite = registration.createInvite({ expiresAt: fixedNow + 86_400_000, maxUses: 2 }); const app = await createApp({ browserGate: false, networkBoundary: { allowTestPort: true }, registration }); const sent = await app.inject({ headers: writeHeaders, method: "POST", payload: { email: "api-user@example.invalid", invite_code: invite.code }, url: "/api/v1/auth/register/send", }); expect(sent.statusCode).toBe(200); expect(sent.json()).toMatchObject({ status: "verification_sent" }); const completed = await app.inject({ headers: { ...writeHeaders, "idempotency-key": "wp1-api-idempotency-key-000000000001" }, method: "POST", payload: { creator_name: "API Creator", privacy_consent_accepted: true, privacy_notice_version: "p0a-notice-v1", registration_id: sent.json().registration_id, social_id: "@@api_creator", verification_code: resend.readLatestCode("api-user@example.invalid"), }, url: "/api/v1/auth/register/complete", }); expect(completed.statusCode).toBe(200); expect(completed.json()).toMatchObject({ credits: { available_balance: 10, reserved_balance: 0 }, status: "registered", user: { role: "user", social_id: "@api_creator", status: "active" }, }); expect(completed.headers["set-cookie"]).toContain("dada_session="); expect(completed.headers["set-cookie"]).toContain("HttpOnly"); expect(completed.headers["set-cookie"]).toContain("SameSite=Strict"); const session = await app.inject({ headers: { cookie: completed.headers["set-cookie"], host: "127.0.0.1:43121" }, method: "GET", url: "/api/v1/auth/session", }); expect(session.statusCode).toBe(200); expect(session.json()).toMatchObject({ audience: "user", authenticated: true, credits: { available_balance: 10, reserved_balance: 0 }, }); const openapi = app.swagger() as { paths?: Record> }; const operationIds = Object.values(openapi.paths ?? {}).flatMap((path) => Object.values(path).map((operation) => operation.operationId), ); expect(operationIds.filter((id) => id === "sendRegistrationCode")).toHaveLength(1); expect(operationIds.filter((id) => id === "completeRegistration")).toHaveLength(1); await app.close(); }); it("returns a stable field error and does not send mail for an invalid invite", async () => { const { registration, resend } = createRegistrationService(); const app = await createApp({ browserGate: false, networkBoundary: { allowTestPort: true }, registration }); const response = await app.inject({ headers: writeHeaders, method: "POST", payload: { email: "api-rejected@example.invalid", invite_code: "missing-invite" }, url: "/api/v1/auth/register/send", }); expect(response.statusCode).toBe(409); expect(isErrorEnvelope(response.json())).toBe(true); expect(response.json()).toMatchObject({ error: { code: "REGISTRATION_REJECTED", details: { field_errors: [{ field: "invite_code", message_key: "auth.invite.not_found" }] }, message_key: "auth.registration.rejected", }, }); expect(resend.calls).toEqual([]); await app.close(); }); });