test(core-consent): add missing test coverage and fix diff allowlist
Add symbols.test.ts to cover CONSENT_SYMBOLS constant. Add recording-consent.test.ts to core-testing (follows the pattern of all other recording doubles which each have a sibling test file). Refactor payload-consent.test.ts to extract a makeConsent() helper, reducing clone groups from 7 to 2. Add new test to cover grantedAt=null branch in deserializeEntry (withdraw before any grant → persisted as null → loaded as undefined). Extend coverage/diff.mjs allowlist for packages/core-testing/ since that tooling package does not install @vitest/coverage-v8 and therefore produces no lcov data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
14
packages/core-consent/src/di/symbols.test.ts
Normal file
14
packages/core-consent/src/di/symbols.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { CONSENT_SYMBOLS } from "@/di/symbols";
|
||||
|
||||
describe("CONSENT_SYMBOLS", () => {
|
||||
it("IConsentFactory is a unique Symbol", () => {
|
||||
expect(typeof CONSENT_SYMBOLS.IConsentFactory).toBe("symbol");
|
||||
});
|
||||
|
||||
it("IConsentFactory uses Symbol.for (global registry)", () => {
|
||||
expect(CONSENT_SYMBOLS.IConsentFactory).toBe(
|
||||
Symbol.for("core-consent:IConsentFactory"),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -19,34 +19,32 @@ function makePayloadMock(initialConsentState: unknown[] = []) {
|
||||
return { getPayload, findByID, update, db };
|
||||
}
|
||||
|
||||
async function makeConsent(opts?: {
|
||||
initial?: unknown[];
|
||||
auditLog?: RecordingAuditLog;
|
||||
}) {
|
||||
const mock = makePayloadMock(opts?.initial);
|
||||
const auditLog = opts?.auditLog ?? new RecordingAuditLog();
|
||||
const consent = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
auditLog,
|
||||
mock.getPayload,
|
||||
);
|
||||
await consent.load();
|
||||
return { consent, auditLog, ...mock };
|
||||
}
|
||||
|
||||
describe("PayloadConsent.isGranted", () => {
|
||||
it("returns false for an unknown category before any grant", async () => {
|
||||
const { getPayload } = makePayloadMock();
|
||||
const auditLog = new RecordingAuditLog();
|
||||
const consent = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
auditLog,
|
||||
getPayload,
|
||||
);
|
||||
await consent.load();
|
||||
const { consent } = await makeConsent();
|
||||
expect(consent.isGranted("analytics")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true after grant and false after withdraw", async () => {
|
||||
const { getPayload } = makePayloadMock();
|
||||
const auditLog = new RecordingAuditLog();
|
||||
const consent = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
auditLog,
|
||||
getPayload,
|
||||
);
|
||||
await consent.load();
|
||||
|
||||
const { consent } = await makeConsent();
|
||||
await consent.grant("analytics");
|
||||
expect(consent.isGranted("analytics")).toBe(true);
|
||||
|
||||
await consent.withdraw("analytics");
|
||||
expect(consent.isGranted("analytics")).toBe(false);
|
||||
});
|
||||
@@ -54,16 +52,7 @@ describe("PayloadConsent.isGranted", () => {
|
||||
|
||||
describe("PayloadConsent.grant", () => {
|
||||
it("writes state to Payload via update", async () => {
|
||||
const { getPayload, update } = makePayloadMock();
|
||||
const auditLog = new RecordingAuditLog();
|
||||
const consent = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
auditLog,
|
||||
getPayload,
|
||||
);
|
||||
await consent.load();
|
||||
|
||||
const { consent, update } = await makeConsent();
|
||||
await consent.grant("analytics");
|
||||
|
||||
expect(update).toHaveBeenCalledOnce();
|
||||
@@ -84,16 +73,7 @@ describe("PayloadConsent.grant", () => {
|
||||
});
|
||||
|
||||
it("emits a CONSENT_GRANT audit entry with correct shape", async () => {
|
||||
const { getPayload } = makePayloadMock();
|
||||
const auditLog = new RecordingAuditLog();
|
||||
const consent = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
auditLog,
|
||||
getPayload,
|
||||
);
|
||||
await consent.load();
|
||||
|
||||
const { consent, auditLog } = await makeConsent();
|
||||
await consent.grant("functional");
|
||||
|
||||
expect(auditLog.recorded).toHaveLength(1);
|
||||
@@ -107,16 +87,7 @@ describe("PayloadConsent.grant", () => {
|
||||
});
|
||||
|
||||
it("preserves bannerVersion, policyVersion, method in state round-trip", async () => {
|
||||
const mock = makePayloadMock();
|
||||
const auditLog = new RecordingAuditLog();
|
||||
const consent1 = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
auditLog,
|
||||
mock.getPayload,
|
||||
);
|
||||
await consent1.load();
|
||||
|
||||
const { consent: consent1, getPayload } = await makeConsent();
|
||||
await consent1.grant("marketing", {
|
||||
bannerVersion: "v2",
|
||||
policyVersion: "2026-01",
|
||||
@@ -128,7 +99,7 @@ describe("PayloadConsent.grant", () => {
|
||||
"user_1",
|
||||
{} as never,
|
||||
new RecordingAuditLog(),
|
||||
mock.getPayload,
|
||||
getPayload,
|
||||
);
|
||||
await consent2.load();
|
||||
|
||||
@@ -146,16 +117,7 @@ describe("PayloadConsent.grant", () => {
|
||||
|
||||
describe("PayloadConsent.withdraw", () => {
|
||||
it("sets state to denied and persists to Payload", async () => {
|
||||
const { getPayload, update } = makePayloadMock();
|
||||
const auditLog = new RecordingAuditLog();
|
||||
const consent = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
auditLog,
|
||||
getPayload,
|
||||
);
|
||||
await consent.load();
|
||||
|
||||
const { consent, update } = await makeConsent();
|
||||
await consent.grant("analytics");
|
||||
update.mockClear();
|
||||
await consent.withdraw("analytics");
|
||||
@@ -175,16 +137,7 @@ describe("PayloadConsent.withdraw", () => {
|
||||
});
|
||||
|
||||
it("emits a CONSENT_WITHDRAW audit entry", async () => {
|
||||
const { getPayload } = makePayloadMock();
|
||||
const auditLog = new RecordingAuditLog();
|
||||
const consent = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
auditLog,
|
||||
getPayload,
|
||||
);
|
||||
await consent.load();
|
||||
|
||||
const { consent, auditLog } = await makeConsent();
|
||||
await consent.grant("analytics");
|
||||
auditLog.reset();
|
||||
await consent.withdraw("analytics");
|
||||
@@ -197,16 +150,7 @@ describe("PayloadConsent.withdraw", () => {
|
||||
|
||||
describe("PayloadConsent.getCategories", () => {
|
||||
it("returns all categories after multiple grants", async () => {
|
||||
const { getPayload } = makePayloadMock();
|
||||
const auditLog = new RecordingAuditLog();
|
||||
const consent = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
auditLog,
|
||||
getPayload,
|
||||
);
|
||||
await consent.load();
|
||||
|
||||
const { consent } = await makeConsent();
|
||||
await consent.grant("necessary");
|
||||
await consent.grant("analytics");
|
||||
await consent.grant("marketing");
|
||||
@@ -218,16 +162,7 @@ describe("PayloadConsent.getCategories", () => {
|
||||
});
|
||||
|
||||
it("reflects withdrawn state in category list", async () => {
|
||||
const { getPayload } = makePayloadMock();
|
||||
const auditLog = new RecordingAuditLog();
|
||||
const consent = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
auditLog,
|
||||
getPayload,
|
||||
);
|
||||
await consent.load();
|
||||
|
||||
const { consent } = await makeConsent();
|
||||
await consent.grant("analytics");
|
||||
await consent.withdraw("analytics");
|
||||
|
||||
@@ -250,15 +185,7 @@ describe("PayloadConsent.load", () => {
|
||||
method: "banner-accept",
|
||||
},
|
||||
];
|
||||
const { getPayload } = makePayloadMock(existingState);
|
||||
const auditLog = new RecordingAuditLog();
|
||||
const consent = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
auditLog,
|
||||
getPayload,
|
||||
);
|
||||
await consent.load();
|
||||
const { consent } = await makeConsent({ initial: existingState });
|
||||
|
||||
expect(consent.isGranted("functional")).toBe(true);
|
||||
const cats = consent.getCategories();
|
||||
@@ -306,18 +233,29 @@ describe("PayloadConsent.load — deserializeEntry branches", () => {
|
||||
withdrawnAt: new Date("2026-01-15T00:00:00.000Z").toISOString(),
|
||||
},
|
||||
];
|
||||
const { getPayload } = makePayloadMock(existingState);
|
||||
const consent = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
new RecordingAuditLog(),
|
||||
getPayload,
|
||||
);
|
||||
await consent.load();
|
||||
const { consent } = await makeConsent({ initial: existingState });
|
||||
|
||||
expect(consent.isGranted("analytics")).toBe(false);
|
||||
const cats = consent.getCategories();
|
||||
expect(cats[0]!.withdrawnAt).toBeInstanceOf(Date);
|
||||
expect(cats[0]!.grantedAt).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
it("handles an entry with null grantedAt (withdraw before any grant)", async () => {
|
||||
const { consent: consent1, getPayload } = await makeConsent();
|
||||
// Withdraw without a prior grant — grantedAt stays undefined → persisted as null
|
||||
await consent1.withdraw("analytics");
|
||||
|
||||
const consent2 = new PayloadConsent(
|
||||
"user_1",
|
||||
{} as never,
|
||||
new RecordingAuditLog(),
|
||||
getPayload,
|
||||
);
|
||||
await consent2.load();
|
||||
const cats = consent2.getCategories();
|
||||
expect(cats[0]!.state).toBe("denied");
|
||||
expect(cats[0]!.grantedAt).toBeUndefined();
|
||||
expect(cats[0]!.withdrawnAt).toBeInstanceOf(Date);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user