Veect retrofit (ADR-027): fourth slice of the demo-content removal. Deletes packages/marketing-pages whole and prunes every composition edge in one commit: core-api router mount + dep, core-cms collection/global composition + dep + regenerated Payload types, web-next bindAll (prod + dev-seed) + tests + about page + Tailwind source + transpilePackages + dep, cms/core-cms payload config test assertions, marketing-page e2e spec, tsconfig paths, fallow ignore entry, anchor-guard + generator e2e feature lists, compliance data-map + retention-policy regeneration, lockfile prune, and feature-list doc entries (CLAUDE.md, AGENTS.md, glossary, app/feature AGENTS.md). Event teardown: marketing-pages was the sole consumer of auth.user.signed-up (welcome-email handler + job). The handler, its Payload tasks, and the bus subscription all lived inside the package's own binders, so they die with it — no other package wires the subscription. Auth's manifest `publishes` stays untouched: a publisher with zero consumers is legal (pnpm conformance only fails on orphan consumers, verified green). The app-level sign-up-welcome-email test asserted the marketing-pages mailer stays empty without a bus; it is deleted with the feature, and the now-unused test-only bind-state helpers in web-next bind-production.ts go with it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
386 lines
14 KiB
TypeScript
386 lines
14 KiB
TypeScript
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<typeof makeDsrBinding>;
|
|
|
|
function makeIntegrationRouter(dsrBinding: DsrTestBinding) {
|
|
return router({
|
|
dsr: createDsrRouter(dsrBinding as unknown as DsrBinding),
|
|
consent: consentRouter,
|
|
});
|
|
}
|
|
|
|
type IntegrationRouter = ReturnType<typeof makeIntegrationRouter>;
|
|
|
|
function makeCaller(
|
|
testRouter: IntegrationRouter,
|
|
userId: string,
|
|
consentFactory: ConsentFactory,
|
|
roles: string[] = ["user"],
|
|
) {
|
|
return testRouter.createCaller({
|
|
user: { id: userId, roles } as DsrTrpcUser,
|
|
userId,
|
|
consentFactory,
|
|
} as Record<string, unknown>);
|
|
}
|
|
|
|
function makeUnauthCaller(
|
|
testRouter: IntegrationRouter,
|
|
consentFactory: ConsentFactory,
|
|
) {
|
|
return testRouter.createCaller({
|
|
consentFactory,
|
|
} as Record<string, unknown>);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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<typeof makeCaller>;
|
|
|
|
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<typeof makeCaller>;
|
|
|
|
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<typeof makeCaller>;
|
|
|
|
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<typeof makeCaller>;
|
|
|
|
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" });
|
|
});
|
|
});
|
|
});
|