import { describe, it, expect, beforeEach } from "vitest"; import { router } from "@repo/core-shared/trpc/init"; import { appRouter } from "./root"; import { createDsrRouter } from "@repo/core-dsr"; import type { DsrBinding } from "@repo/core-dsr"; import type { DsrTrpcUser } from "@repo/core-dsr"; import { consentRouter } from "@repo/core-consent"; import type { ConsentFactory } from "@repo/core-consent"; import { RecordingDataExport, RecordingDataDelete, RecordingDataRectify, RecordingProcessingRestriction, RecordingConsent, } from "@repo/core-testing/instrumentation"; // --------------------------------------------------------------------------- // Test helpers // --------------------------------------------------------------------------- function makeDsrBinding() { return { dataExport: new RecordingDataExport(), dataDelete: new RecordingDataDelete(), dataRectify: new RecordingDataRectify(), processingRestriction: new RecordingProcessingRestriction(), }; } type DsrTestBinding = ReturnType; function makeIntegrationRouter(dsrBinding: DsrTestBinding) { return router({ dsr: createDsrRouter(dsrBinding as unknown as DsrBinding), consent: consentRouter, }); } type IntegrationRouter = ReturnType; function makeCaller( testRouter: IntegrationRouter, userId: string, consentFactory: ConsentFactory, roles: string[] = ["user"], ) { return testRouter.createCaller({ user: { id: userId, roles } as DsrTrpcUser, userId, consentFactory, } as Record); } function makeUnauthCaller( testRouter: IntegrationRouter, consentFactory: ConsentFactory, ) { return testRouter.createCaller({ consentFactory, } as Record); } // --------------------------------------------------------------------------- // Structure tests — appRouter composition // --------------------------------------------------------------------------- describe("appRouter composition", () => { it("exposes auth, navigation routers", () => { const procedures = appRouter._def.procedures; const keys = Object.keys(procedures); expect(keys.some((k) => k.startsWith("auth."))).toBe(true); expect(keys.some((k) => k.startsWith("navigation."))).toBe(true); }); it("exposes dsr and consent routers", () => { const keys = Object.keys(appRouter._def.procedures); expect(keys.some((k) => k.startsWith("dsr."))).toBe(true); expect(keys.some((k) => k.startsWith("consent."))).toBe(true); }); it("dsr router exposes all four procedures", () => { const procedures = appRouter._def.procedures; expect(procedures).toHaveProperty("dsr.export"); expect(procedures).toHaveProperty("dsr.delete"); expect(procedures).toHaveProperty("dsr.rectify"); expect(procedures).toHaveProperty("dsr.restrict"); }); it("consent router exposes all four procedures", () => { const procedures = appRouter._def.procedures; expect(procedures).toHaveProperty("consent.grant"); expect(procedures).toHaveProperty("consent.withdraw"); expect(procedures).toHaveProperty("consent.isGranted"); expect(procedures).toHaveProperty("consent.getCategories"); }); }); // --------------------------------------------------------------------------- // Integration tests — dsr procedures // --------------------------------------------------------------------------- describe("dsr.export — integration", () => { let binding: DsrTestBinding; let consent: RecordingConsent; let caller: ReturnType; beforeEach(() => { binding = makeDsrBinding(); consent = new RecordingConsent(); const factory: ConsentFactory = async () => consent; const testRouter = makeIntegrationRouter(binding); caller = makeCaller(testRouter, "alice", factory); }); it("resolves with UserDataBundle body for authenticated user", async () => { const result = await caller.dsr.export({ subjectId: "alice", format: "json", }); expect(result.subjectId).toBe("alice"); expect(result.format).toBe("json"); expect(binding.dataExport.calls).toHaveLength(1); }); it("resolves with json-ld format", async () => { const result = await caller.dsr.export({ subjectId: "alice", format: "json-ld", }); expect(result.format).toBe("json-ld"); }); it("throws UNAUTHORIZED when unauthenticated", async () => { const factory: ConsentFactory = async () => consent; const testRouter = makeIntegrationRouter(binding); const unauthCaller = makeUnauthCaller(testRouter, factory); await expect( unauthCaller.dsr.export({ subjectId: "alice", format: "json" }), ).rejects.toMatchObject({ code: "UNAUTHORIZED" }); }); }); describe("dsr.delete — integration", () => { let binding: DsrTestBinding; let consent: RecordingConsent; beforeEach(() => { binding = makeDsrBinding(); consent = new RecordingConsent(); }); it("resolves with DeletionCertificate for soft mode (authenticated user)", async () => { const factory: ConsentFactory = async () => consent; const testRouter = makeIntegrationRouter(binding); const caller = makeCaller(testRouter, "alice", factory); const result = await caller.dsr.delete({ subjectId: "alice", mode: "soft", }); expect(result.subjectId).toBe("alice"); expect(result.mode).toBe("soft"); expect(binding.dataDelete.calls).toHaveLength(1); }); it("resolves with DeletionCertificate for cascade-hard mode (admin)", async () => { const factory: ConsentFactory = async () => consent; const testRouter = makeIntegrationRouter(binding); const adminCaller = makeCaller(testRouter, "admin", factory, ["admin"]); const result = await adminCaller.dsr.delete({ subjectId: "alice", mode: "cascade-hard", }); expect(result.mode).toBe("cascade-hard"); }); it("throws FORBIDDEN for cascade-hard when user lacks admin role", async () => { const factory: ConsentFactory = async () => consent; const testRouter = makeIntegrationRouter(binding); const caller = makeCaller(testRouter, "alice", factory, ["user"]); await expect( caller.dsr.delete({ subjectId: "alice", mode: "cascade-hard" }), ).rejects.toMatchObject({ code: "FORBIDDEN" }); }); it("throws UNAUTHORIZED when unauthenticated", async () => { const factory: ConsentFactory = async () => consent; const testRouter = makeIntegrationRouter(binding); const unauthCaller = makeUnauthCaller(testRouter, factory); await expect( unauthCaller.dsr.delete({ subjectId: "alice", mode: "soft" }), ).rejects.toMatchObject({ code: "UNAUTHORIZED" }); }); }); describe("dsr.rectify — integration", () => { let binding: DsrTestBinding; let consent: RecordingConsent; let caller: ReturnType; beforeEach(() => { binding = makeDsrBinding(); consent = new RecordingConsent(); const factory: ConsentFactory = async () => consent; const testRouter = makeIntegrationRouter(binding); caller = makeCaller(testRouter, "alice", factory); }); it("resolves with { ok: true } for authenticated user", async () => { const result = await caller.dsr.rectify({ subjectId: "alice", collection: "users", field: "name", value: "Alice Updated", }); expect(result).toEqual({ ok: true }); expect(binding.dataRectify.calls[0]).toMatchObject({ subjectId: "alice", collection: "users", field: "name", }); }); it("throws UNAUTHORIZED when unauthenticated", async () => { const factory: ConsentFactory = async () => consent; const testRouter = makeIntegrationRouter(binding); const unauthCaller = makeUnauthCaller(testRouter, factory); await expect( unauthCaller.dsr.rectify({ subjectId: "alice", collection: "users", field: "name", value: "x", }), ).rejects.toMatchObject({ code: "UNAUTHORIZED" }); }); }); describe("dsr.restrict — integration", () => { let binding: DsrTestBinding; let consent: RecordingConsent; let caller: ReturnType; beforeEach(() => { binding = makeDsrBinding(); consent = new RecordingConsent(); const factory: ConsentFactory = async () => consent; const testRouter = makeIntegrationRouter(binding); caller = makeCaller(testRouter, "alice", factory); }); it("resolves with { ok: true } when granting restriction", async () => { const result = await caller.dsr.restrict({ subjectId: "alice", granted: true, }); expect(result).toEqual({ ok: true }); expect(binding.processingRestriction.sets[0]).toMatchObject({ subjectId: "alice", granted: true, }); }); it("resolves with { ok: true } when lifting restriction", async () => { const result = await caller.dsr.restrict({ subjectId: "alice", granted: false, }); expect(result).toEqual({ ok: true }); expect(binding.processingRestriction.sets[0]?.granted).toBe(false); }); it("throws UNAUTHORIZED when unauthenticated", async () => { const factory: ConsentFactory = async () => consent; const testRouter = makeIntegrationRouter(binding); const unauthCaller = makeUnauthCaller(testRouter, factory); await expect( unauthCaller.dsr.restrict({ subjectId: "alice", granted: true }), ).rejects.toMatchObject({ code: "UNAUTHORIZED" }); }); }); // --------------------------------------------------------------------------- // Integration tests — consent procedures // --------------------------------------------------------------------------- describe("consent — integration", () => { let consent: RecordingConsent; let caller: ReturnType; beforeEach(() => { const binding = makeDsrBinding(); consent = new RecordingConsent(); const factory: ConsentFactory = async () => consent; const testRouter = makeIntegrationRouter(binding); caller = makeCaller(testRouter, "user-1", factory); }); describe("consent.grant", () => { it("resolves with { success: true } and records the grant", async () => { const result = await caller.consent.grant({ category: "analytics" }); expect(result).toEqual({ success: true }); expect(consent.grants).toHaveLength(1); expect(consent.grants[0]!.category).toBe("analytics"); }); it("throws UNAUTHORIZED when userId is absent", async () => { const c = new RecordingConsent(); const factory: ConsentFactory = async () => c; const testRouter = makeIntegrationRouter(makeDsrBinding()); const unauthCaller = makeUnauthCaller(testRouter, factory); await expect( unauthCaller.consent.grant({ category: "analytics" }), ).rejects.toMatchObject({ code: "UNAUTHORIZED" }); }); }); describe("consent.withdraw", () => { it("resolves with { success: true } and records the withdrawal", async () => { await caller.consent.grant({ category: "marketing" }); const result = await caller.consent.withdraw({ category: "marketing" }); expect(result).toEqual({ success: true }); expect(consent.withdrawals).toHaveLength(1); expect(consent.withdrawals[0]).toBe("marketing"); }); it("throws UNAUTHORIZED when userId is absent", async () => { const c = new RecordingConsent(); const factory: ConsentFactory = async () => c; const testRouter = makeIntegrationRouter(makeDsrBinding()); const unauthCaller = makeUnauthCaller(testRouter, factory); await expect( unauthCaller.consent.withdraw({ category: "analytics" }), ).rejects.toMatchObject({ code: "UNAUTHORIZED" }); }); }); describe("consent.isGranted", () => { it("resolves with { granted: false } before any grant", async () => { const result = await caller.consent.isGranted({ category: "analytics" }); expect(result).toEqual({ granted: false }); }); it("resolves with { granted: true } after grant", async () => { await caller.consent.grant({ category: "analytics" }); const result = await caller.consent.isGranted({ category: "analytics" }); expect(result).toEqual({ granted: true }); }); it("throws UNAUTHORIZED when userId is absent", async () => { const c = new RecordingConsent(); const factory: ConsentFactory = async () => c; const testRouter = makeIntegrationRouter(makeDsrBinding()); const unauthCaller = makeUnauthCaller(testRouter, factory); await expect( unauthCaller.consent.isGranted({ category: "analytics" }), ).rejects.toMatchObject({ code: "UNAUTHORIZED" }); }); }); describe("consent.getCategories", () => { it("resolves with { categories: [] } initially", async () => { const result = await caller.consent.getCategories({}); expect(result).toEqual({ categories: [] }); }); it("resolves with all granted categories", async () => { await caller.consent.grant({ category: "necessary" }); await caller.consent.grant({ category: "analytics" }); const { categories } = await caller.consent.getCategories({}); expect(categories).toHaveLength(2); const names = categories.map((c) => c.category).sort(); expect(names).toEqual(["analytics", "necessary"]); }); it("throws UNAUTHORIZED when userId is absent", async () => { const c = new RecordingConsent(); const factory: ConsentFactory = async () => c; const testRouter = makeIntegrationRouter(makeDsrBinding()); const unauthCaller = makeUnauthCaller(testRouter, factory); await expect( unauthCaller.consent.getCategories({}), ).rejects.toMatchObject({ code: "UNAUTHORIZED" }); }); }); });