Implements the Payload-backed IConsent that reads/writes users.consentState and emits CONSENT_GRANT/CONSENT_WITHDRAW audit entries via injected auditLog. Adds RecordingConsent test double in core-testing for unit-test injection. Adds bindProductionConsent/bindDevSeedConsent DI binders and InMemoryConsent for dev/seed contexts. Contract tests cover grant/withdraw/isGranted round-trip, audit entry shape, metadata persistence (bannerVersion/policyVersion/method), and getCategories reflection of state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { InMemoryConsent } from "@/in-memory-consent";
|
|
|
|
describe("InMemoryConsent.isGranted", () => {
|
|
it("returns false for an unknown category", async () => {
|
|
const consent = new InMemoryConsent();
|
|
expect(consent.isGranted("analytics")).toBe(false);
|
|
});
|
|
|
|
it("returns true after grant", async () => {
|
|
const consent = new InMemoryConsent();
|
|
await consent.grant("analytics");
|
|
expect(consent.isGranted("analytics")).toBe(true);
|
|
});
|
|
|
|
it("returns false after withdraw", async () => {
|
|
const consent = new InMemoryConsent();
|
|
await consent.grant("analytics");
|
|
await consent.withdraw("analytics");
|
|
expect(consent.isGranted("analytics")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("InMemoryConsent.grant", () => {
|
|
it("stores bannerVersion, policyVersion, method in state", async () => {
|
|
const consent = new InMemoryConsent();
|
|
await consent.grant("marketing", {
|
|
bannerVersion: "v2",
|
|
policyVersion: "2026-01",
|
|
method: "banner-accept",
|
|
});
|
|
const cats = consent.getCategories();
|
|
expect(cats).toHaveLength(1);
|
|
expect(cats[0]!.bannerVersion).toBe("v2");
|
|
expect(cats[0]!.policyVersion).toBe("2026-01");
|
|
expect(cats[0]!.method).toBe("banner-accept");
|
|
expect(cats[0]!.grantedAt).toBeInstanceOf(Date);
|
|
});
|
|
|
|
it("preserves withdrawnAt across regrant", async () => {
|
|
const consent = new InMemoryConsent();
|
|
await consent.grant("analytics");
|
|
await consent.withdraw("analytics");
|
|
const withdrawnAt = consent.getCategories()[0]!.withdrawnAt;
|
|
await consent.grant("analytics");
|
|
const cats = consent.getCategories();
|
|
expect(cats[0]!.withdrawnAt).toBe(withdrawnAt);
|
|
});
|
|
});
|
|
|
|
describe("InMemoryConsent.withdraw", () => {
|
|
it("sets withdrawnAt on an existing entry", async () => {
|
|
const consent = new InMemoryConsent();
|
|
await consent.grant("functional");
|
|
await consent.withdraw("functional");
|
|
const cats = consent.getCategories();
|
|
expect(cats[0]!.state).toBe("denied");
|
|
expect(cats[0]!.withdrawnAt).toBeInstanceOf(Date);
|
|
});
|
|
|
|
it("can withdraw a category that was never granted", async () => {
|
|
const consent = new InMemoryConsent();
|
|
await consent.withdraw("analytics");
|
|
expect(consent.isGranted("analytics")).toBe(false);
|
|
const cats = consent.getCategories();
|
|
expect(cats[0]!.state).toBe("denied");
|
|
});
|
|
});
|
|
|
|
describe("InMemoryConsent.getCategories", () => {
|
|
it("returns all granted and denied categories", async () => {
|
|
const consent = new InMemoryConsent();
|
|
await consent.grant("necessary");
|
|
await consent.grant("analytics");
|
|
await consent.withdraw("analytics");
|
|
|
|
const cats = consent.getCategories();
|
|
expect(cats).toHaveLength(2);
|
|
});
|
|
});
|