Initial reliable release of consent engine
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createIntegratedConsentEngine } from "../../integration/index.js";
|
||||
|
||||
// This suite exercises real persistence (acceptAll/revoke/reset expected to
|
||||
// take effect), unlike tests/integration/integration-test.mjs which
|
||||
// deliberately asserts the no-op behavior of a bare Node environment.
|
||||
// A document.cookie mock is required here for the same reason it is
|
||||
// required in test-core.mjs.
|
||||
let jar = new Map();
|
||||
|
||||
globalThis.location = { protocol: "http:" };
|
||||
globalThis.document = {};
|
||||
Object.defineProperty(globalThis.document, "cookie", {
|
||||
configurable: true,
|
||||
get() {
|
||||
return [...jar.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
|
||||
},
|
||||
set(value) {
|
||||
const parts = value.split(";").map((x) => x.trim());
|
||||
const [name, raw = ""] = parts[0].split("=");
|
||||
const maxAge = parts.find((x) => /^Max-Age=/i.test(x));
|
||||
if (maxAge && maxAge.toLowerCase() === "max-age=0") {
|
||||
jar.delete(name);
|
||||
return;
|
||||
}
|
||||
jar.set(name, raw);
|
||||
}
|
||||
});
|
||||
|
||||
let passed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
// Each test starts from a clean cookie jar so persisted consent from
|
||||
// one test cannot leak into the next (they share default jurisdiction
|
||||
// and policyVersion, so a stale cookie would otherwise be reloaded).
|
||||
jar.clear();
|
||||
|
||||
try {
|
||||
fn();
|
||||
console.log(`PASS ${name}`);
|
||||
passed += 1;
|
||||
} catch (error) {
|
||||
console.error(`FAIL ${name}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function createEngine(jurisdiction = "us-ma") {
|
||||
return createIntegratedConsentEngine({
|
||||
jurisdiction: {
|
||||
mode: "fixed",
|
||||
jurisdiction
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
test("dynamic jurisdiction is reflected in Core state", () => {
|
||||
const consent = createEngine("global");
|
||||
consent.initialize();
|
||||
const state = consent.setJurisdiction("us-ma");
|
||||
|
||||
assert.equal(state.jurisdiction, "us-ma");
|
||||
});
|
||||
|
||||
test("GPC state is represented without granting optional consent", () => {
|
||||
const consent = createEngine();
|
||||
consent.initialize();
|
||||
const state = consent.setGpc(true);
|
||||
|
||||
assert.equal(state.gpc, true);
|
||||
assert.equal(state.hasDecision, false);
|
||||
assert.equal(state.categories.preferences, "unknown");
|
||||
assert.equal(state.categories.analytics, "unknown");
|
||||
assert.equal(state.categories.marketing, "unknown");
|
||||
});
|
||||
|
||||
test("revoke produces the Core-defined denied state and is idempotent", () => {
|
||||
const consent = createEngine();
|
||||
consent.initialize();
|
||||
|
||||
consent.acceptAll();
|
||||
const accepted = consent.getState();
|
||||
assert.equal(accepted.hasDecision, true);
|
||||
|
||||
let revokedEvents = 0;
|
||||
consent.on("consent:revoked", () => revokedEvents++);
|
||||
|
||||
const revoked = consent.revoke();
|
||||
const secondRevoke = consent.revoke();
|
||||
|
||||
assert.equal(revoked.categories.necessary, "granted");
|
||||
assert.equal(revoked.categories.preferences, "denied");
|
||||
assert.equal(revoked.categories.analytics, "denied");
|
||||
assert.equal(revoked.categories.marketing, "denied");
|
||||
assert.equal(secondRevoke.categories.analytics, "denied");
|
||||
assert.equal(revokedEvents, 1);
|
||||
});
|
||||
|
||||
test("reset returns Core to an undecided state", () => {
|
||||
const consent = createEngine();
|
||||
consent.initialize();
|
||||
consent.acceptAll();
|
||||
|
||||
const reset = consent.reset();
|
||||
|
||||
assert.equal(reset.hasDecision, false);
|
||||
assert.equal(reset.categories.necessary, "granted");
|
||||
assert.equal(reset.categories.preferences, "unknown");
|
||||
assert.equal(reset.categories.analytics, "unknown");
|
||||
assert.equal(reset.categories.marketing, "unknown");
|
||||
});
|
||||
|
||||
test("policy version is part of the Core state", () => {
|
||||
const consent = createEngine();
|
||||
const config = consent.getConfig();
|
||||
|
||||
assert.equal(typeof config.policyVersion, "string");
|
||||
assert.ok(config.policyVersion.length > 0);
|
||||
});
|
||||
|
||||
test("persistence preserves the resolved jurisdiction after acceptance", () => {
|
||||
const consent = createEngine("us-ma");
|
||||
consent.initialize();
|
||||
|
||||
const saved = consent.acceptAll();
|
||||
|
||||
assert.equal(saved.hasDecision, true);
|
||||
assert.equal(saved.jurisdiction, "us-ma");
|
||||
});
|
||||
|
||||
test("event sequence reports the consent transition", () => {
|
||||
const consent = createEngine();
|
||||
const events = [];
|
||||
|
||||
consent.on("consent:changed", () => events.push("changed"));
|
||||
consent.on("consent:accepted", () => events.push("accepted"));
|
||||
|
||||
consent.initialize();
|
||||
consent.acceptAll();
|
||||
|
||||
assert.deepEqual(events, ["changed", "accepted"]);
|
||||
});
|
||||
|
||||
test("Core state remains isolated from returned object mutation", () => {
|
||||
const consent = createEngine();
|
||||
consent.initialize();
|
||||
|
||||
const state = consent.getState();
|
||||
state.categories.analytics = "granted";
|
||||
state.jurisdiction = "changed";
|
||||
|
||||
const current = consent.getState();
|
||||
|
||||
assert.equal(current.categories.analytics, "unknown");
|
||||
assert.equal(current.jurisdiction, "us-ma");
|
||||
});
|
||||
|
||||
|
||||
test("separate Core instances do not share in-memory state", () => {
|
||||
const first = createEngine("us-ma");
|
||||
const second = createEngine("eu");
|
||||
|
||||
first.initialize();
|
||||
second.initialize();
|
||||
|
||||
first.setConsent({ analytics: "granted" });
|
||||
|
||||
assert.equal(first.getState().categories.analytics, "granted");
|
||||
assert.equal(second.getState().categories.analytics, "unknown");
|
||||
assert.equal(first.getState().jurisdiction, "us-ma");
|
||||
assert.equal(second.getState().jurisdiction, "eu");
|
||||
});
|
||||
|
||||
test("partial consent remains undecided until all optional categories are resolved", () => {
|
||||
const consent = createEngine();
|
||||
consent.initialize();
|
||||
|
||||
const state = consent.setConsent({ analytics: "granted" });
|
||||
|
||||
assert.equal(state.categories.analytics, "granted");
|
||||
assert.equal(state.categories.preferences, "unknown");
|
||||
assert.equal(state.categories.marketing, "unknown");
|
||||
assert.equal(state.hasDecision, false);
|
||||
assert.equal(consent.isComplete(), false);
|
||||
});
|
||||
|
||||
test("necessary remains granted through valid consent changes", () => {
|
||||
const consent = createEngine();
|
||||
consent.initialize();
|
||||
|
||||
const state = consent.setConsent({
|
||||
preferences: "denied",
|
||||
analytics: "granted",
|
||||
marketing: "denied"
|
||||
});
|
||||
|
||||
assert.equal(state.categories.necessary, "granted");
|
||||
assert.equal(state.categories.preferences, "denied");
|
||||
assert.equal(state.categories.analytics, "granted");
|
||||
assert.equal(state.categories.marketing, "denied");
|
||||
});
|
||||
|
||||
test("acceptAll is idempotent through the integration boundary", () => {
|
||||
const consent = createEngine();
|
||||
consent.initialize();
|
||||
|
||||
let changed = 0;
|
||||
let accepted = 0;
|
||||
|
||||
consent.on("consent:changed", () => changed++);
|
||||
consent.on("consent:accepted", () => accepted++);
|
||||
|
||||
consent.acceptAll();
|
||||
const first = consent.getState();
|
||||
consent.acceptAll();
|
||||
const second = consent.getState();
|
||||
|
||||
assert.equal(first.hasDecision, true);
|
||||
assert.equal(second.hasDecision, true);
|
||||
assert.equal(changed, 1);
|
||||
assert.equal(accepted, 1);
|
||||
assert.equal(second.timestamp, first.timestamp);
|
||||
});
|
||||
|
||||
test("reset is idempotent through the integration boundary", () => {
|
||||
const consent = createEngine();
|
||||
consent.initialize();
|
||||
consent.acceptAll();
|
||||
|
||||
let reset = 0;
|
||||
consent.on("consent:reset", () => reset++);
|
||||
|
||||
consent.reset();
|
||||
const first = consent.getState();
|
||||
consent.reset();
|
||||
const second = consent.getState();
|
||||
|
||||
assert.equal(first.hasDecision, false);
|
||||
assert.equal(second.hasDecision, false);
|
||||
assert.equal(second.categories.analytics, "unknown");
|
||||
});
|
||||
|
||||
test("GPC never grants optional categories through the integration boundary", () => {
|
||||
const consent = createEngine();
|
||||
consent.initialize();
|
||||
|
||||
const state = consent.setGpc(true);
|
||||
|
||||
assert.equal(state.gpc, true);
|
||||
assert.equal(state.categories.preferences, "unknown");
|
||||
assert.equal(state.categories.analytics, "unknown");
|
||||
assert.equal(state.categories.marketing, "unknown");
|
||||
assert.equal(state.hasDecision, false);
|
||||
});
|
||||
|
||||
console.log(`${passed} audit tests passed.`);
|
||||
Reference in New Issue
Block a user