import { randomUUID } from "node:crypto"; 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"; const roots: string[] = []; const services: RegistrationService[] = []; const now = Date.parse("2026-08-04T09:30:00.000Z"); const headers = { host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121" }; function harness() { const root = mkdtempSync(join(tmpdir(), "dada-wp6-02-api-")); roots.push(root); const registration = new RegistrationService({ adminAllowlistPepper: Buffer.alloc(32, 0xc1), challengePepper: Buffer.alloc(32, 0xc2), clock: () => now, currentPrivacyNoticeVersion: "p0a-private-content-v1", databasePath: join(root, "dada.sqlite3"), invitePepper: Buffer.alloc(32, 0xc3), resend: new MockResendAdapter(), sessionPepper: Buffer.alloc(32, 0xc4), }); services.push(registration); const adminId = randomUUID(); const ownerId = randomUUID(); const projectId = randomUUID(); const generationId = randomUUID(); registration.database.prepare(` INSERT INTO users (user_id, normalized_email, role, status, counts_toward_stage_limit, registration_id, created_at) VALUES (?, ?, 'super_admin', 'active', 0, ?, ?), (?, ?, 'user', 'active', 1, ?, ?) `).run(adminId, `admin-${adminId}@example.invalid`, randomUUID(), now, ownerId, `user-${ownerId}@example.invalid`, randomUUID(), now); registration.database.prepare("INSERT INTO admin_access (user_id, allowed) VALUES (?, 1)").run(adminId); registration.database.prepare("INSERT INTO user_profiles (user_id, creator_name, social_id) VALUES (?, 'Admin', '@admin')").run(adminId); registration.database.exec(` CREATE TABLE generation_jobs ( generation_id TEXT PRIMARY KEY, owner_id TEXT NOT NULL, project_id TEXT NOT NULL, prompt TEXT NOT NULL, ratio TEXT NOT NULL, status TEXT NOT NULL, model_id TEXT NOT NULL, model_config_version INTEGER NOT NULL, confirmed_credit_cost INTEGER NOT NULL, reserved_credits INTEGER NOT NULL, final_credit_state TEXT, error_category TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, submission_ready INTEGER NOT NULL DEFAULT 1 ); CREATE TABLE project_images (image_id TEXT PRIMARY KEY, project_id TEXT NOT NULL, generation_id TEXT NOT NULL, created_at INTEGER NOT NULL); `); registration.database.prepare(` INSERT INTO generation_jobs ( generation_id, owner_id, project_id, prompt, ratio, status, model_id, model_config_version, confirmed_credit_cost, reserved_credits, final_credit_state, error_category, created_at, updated_at, submission_ready ) VALUES (?, ?, ?, ?, '1:1', 'succeeded', 'demo.model', 1, 2, 0, 'committed', NULL, ?, ?, 1) `).run(generationId, ownerId, projectId, "secret prompt must never be listed", now - 1000, now); return { registration, adminId, generationId, adminSession: registration.issueAuthenticatedSession(adminId, "admin") }; } 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-WP6-PRIV-001/TDD-WP6-PRIV-002", () => { it("persists notice acknowledgement, returns metadata only, and audits every prompt open", async () => { const fixture = harness(); const app = await createApp({ browserGate: false, networkBoundary: { allowTestPort: true }, registration: fixture.registration }); const cookie = `dada_admin_session=${fixture.adminSession.sessionToken}`; const denied = await app.inject({ headers: { ...headers, cookie }, method: "GET", url: "/api/v1/admin/generations" }); expect(denied.statusCode).toBe(428); expect(denied.json()).toMatchObject({ error: { code: "PRIVATE_CONTENT_NOTICE_ACK_REQUIRED", details: { latest_version: "p0a-private-content-v1" } } }); expect(JSON.stringify(denied.json())).not.toContain("secret prompt"); const csrf = fixture.registration.issueAdminCsrfToken(fixture.adminSession.sessionToken); const ack = await app.inject({ headers: { ...headers, cookie, "x-csrf-token": csrf, "idempotency-key": "wp6-02-ack-000000000000000000000000000000" }, method: "POST", payload: { expected_notice_version: "p0a-private-content-v1" }, url: "/api/v1/admin/private-content-notice/ack", }); expect(ack.statusCode).toBe(200); expect(ack.json()).toMatchObject({ notice_version: "p0a-private-content-v1", status: "acknowledged" }); const stale = await app.inject({ headers: { ...headers, cookie, "x-csrf-token": fixture.registration.issueAdminCsrfToken(fixture.adminSession.sessionToken), "idempotency-key": "wp6-02-ack-stale-000000000000000000000000000" }, method: "POST", payload: { expected_notice_version: "old-private-content-v0" }, url: "/api/v1/admin/private-content-notice/ack", }); expect(stale.statusCode).toBe(428); expect(stale.json()).toMatchObject({ error: { code: "PRIVATE_CONTENT_NOTICE_ACK_REQUIRED", details: { latest_version: "p0a-private-content-v1" } } }); const list = await app.inject({ headers: { ...headers, cookie }, method: "GET", url: "/api/v1/admin/generations" }); expect(list.statusCode).toBe(200); expect(list.json().items[0]).toMatchObject({ generation_id: fixture.generationId, owner_ref: expect.any(String), status: "succeeded" }); expect(JSON.stringify(list.json())).not.toContain("secret prompt"); const opened = await app.inject({ headers: { ...headers, cookie }, method: "GET", url: `/api/v1/admin/private-content/generations/${fixture.generationId}/prompt` }); expect(opened.statusCode).toBe(200); expect(opened.json()).toEqual({ content_type: "prompt", generation_id: fixture.generationId, prompt: "secret prompt must never be listed" }); expect((fixture.registration.database.prepare("SELECT COUNT(*) AS count FROM private_content_access_logs").get() as { count: number }).count).toBe(1); const reopened = await app.inject({ headers: { ...headers, cookie }, method: "GET", url: `/api/v1/admin/private-content/generations/${fixture.generationId}/prompt` }); expect(reopened.statusCode).toBe(200); expect((fixture.registration.database.prepare("SELECT COUNT(*) AS count FROM private_content_access_logs").get() as { count: number }).count).toBe(2); await app.close(); }); });