Files
cookie-consent-jurisdiction…/test-core.mjs
T

181 lines
7.7 KiB
JavaScript

import assert from 'node:assert/strict';
import {
createConsentEngine,
ENGINE_VERSION
} from './core/consent.js';
import { isStoragePayload } from './core/storage.js';
import { isValidCategoriesObject } from './core/categories.js';
let jar = new Map();
let blockWrites = false;
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) {
if (blockWrites) return;
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);
}
});
function clearCookie() { jar.clear(); blockWrites = false; }
function make() {
return createConsentEngine({
policyVersion: '2026-01',
jurisdiction: 'global',
storage: { name: 'cc_consent', duration: 180, path: '/', sameSite: 'Lax' }
});
}
let passed = 0;
function test(name, fn) {
try { fn(); passed++; console.log(`PASS ${name}`); }
catch (e) { console.error(`FAIL ${name}\n`, e); process.exitCode = 1; }
}
clearCookie();
test('default state is undecided', () => {
const e = make(); e.initialize(); const s = e.getState();
assert.equal(s.categories.necessary, 'granted');
assert.equal(s.categories.analytics, 'unknown');
assert.equal(s.hasDecision, false);
});
test('accept all', () => {
clearCookie();
const e = make(); e.initialize(); let changed = 0, accepted = 0;
e.on('consent:changed', () => changed++); e.on('consent:accepted', () => accepted++);
e.acceptAll();
assert.equal(e.getState().hasDecision, true);
assert.equal(e.getState().categories.analytics, 'granted');
assert.equal(changed, 1); assert.equal(accepted, 1);
});
test('accept all is idempotent', () => {
clearCookie();
const e = make(); e.initialize(); let events = 0; e.on('consent:changed', () => events++);
e.acceptAll(); const before = e.getState().timestamp; e.acceptAll();
assert.equal(events, 1); assert.equal(e.getState().timestamp, before);
});
test('persistence loads from storage', () => {
clearCookie();
const e = make(); e.initialize(); e.rejectAll();
const fresh = make(); fresh.initialize(); const s = fresh.getState();
assert.equal(s.source, 'storage'); assert.equal(s.categories.analytics, 'denied');
});
test('reject all is idempotent', () => {
clearCookie(); const e = make(); e.initialize(); let events = 0; e.on('consent:changed', () => events++);
e.rejectAll(); e.rejectAll(); assert.equal(events, 1);
});
test('selective consent', () => {
clearCookie(); const e = make(); e.initialize(); e.setConsent({ analytics: 'granted', marketing: 'denied', preferences: 'denied' });
assert.equal(e.getState().categories.analytics, 'granted'); assert.equal(e.isComplete(), true);
});
test('invalid consent value throws', () => {
clearCookie(); const e = make(); e.initialize(); assert.throws(() => e.setConsent({ analytics: 'banana' }), TypeError);
});
test('necessary cannot be denied', () => {
clearCookie(); const e = make(); e.initialize();
assert.throws(() => e.setConsent({ necessary: 'denied', analytics: 'granted' }), TypeError);
assert.equal(e.getState().categories.necessary, 'granted');
assert.equal(e.getState().categories.analytics, 'unknown');
});
test('revoke is idempotent', () => {
clearCookie(); const e = make(); e.initialize(); e.acceptAll(); let revoked = 0; e.on('consent:revoked', () => revoked++);
e.revoke(); e.revoke(); assert.equal(revoked, 1); assert.equal(e.getState().categories.analytics, 'denied');
});
test('reset removes persistence', () => {
clearCookie(); const e = make(); e.initialize(); e.acceptAll(); e.reset();
assert.equal(e.isComplete(), false); assert.equal(document.cookie, '');
const fresh = make(); fresh.initialize(); assert.equal(fresh.isComplete(), false); assert.equal(fresh.getState().source, 'default');
});
test('reset does not lie when storage removal fails', () => {
clearCookie(); const e = make(); e.initialize(); e.acceptAll(); blockWrites = true; let reset = 0, errors = 0;
e.on('consent:reset', () => reset++); e.on('storage:error', () => errors++);
const before = e.getState(); e.reset();
assert.equal(reset, 0); assert.equal(errors, 1); assert.equal(e.getState().categories.analytics, before.categories.analytics);
});
test('strict stored envelope validation', () => {
assert.equal(isStoragePayload({}), false);
assert.equal(isStoragePayload({ engineVersion: ENGINE_VERSION, policyVersion: '2026-01', timestamp: Date.now(), categories: {}, jurisdiction: 'global', gpc: false }), true);
});
test('strict category validation', () => {
assert.equal(isValidCategoriesObject({ necessary:'granted', preferences:'unknown', analytics:'granted', marketing:'denied' }), true);
assert.equal(isValidCategoriesObject({ necessary:'denied', preferences:'unknown', analytics:'granted', marketing:'denied' }), false);
assert.equal(isValidCategoriesObject({ necessary:'granted', preferences:'banana', analytics:'granted', marketing:'denied' }), false);
});
test('initialize is idempotent', () => {
clearCookie(); const e = make(); let initialized = 0; e.on('initialized', () => initialized++); e.initialize(); e.initialize(); assert.equal(initialized, 1);
});
test('policy mismatch removes valid cookie', () => {
clearCookie(); const e = make(); e.initialize(); e.acceptAll();
const fresh = createConsentEngine({ policyVersion:'2027-01', jurisdiction:'global', storage:{ name:'cc_consent', duration:180, path:'/', sameSite:'Lax' } });
fresh.initialize(); assert.equal(fresh.isComplete(), false); assert.equal(document.cookie, '');
});
test('expired cookie is rejected', () => {
clearCookie(); const e = make(); e.initialize(); e.acceptAll();
const raw = decodeURIComponent(document.cookie.split('=').slice(1).join('=')); const payload = JSON.parse(raw); payload.timestamp = Date.now() - 181*24*60*60*1000;
document.cookie = `cc_consent=${encodeURIComponent(JSON.stringify(payload))}; Path=/; SameSite=Lax`;
const fresh = make(); let expired = 0; fresh.on('consent:expired', () => expired++); fresh.initialize();
assert.equal(expired, 1); assert.equal(fresh.isComplete(), false); assert.equal(document.cookie, '');
});
test('necessary denial is explicitly rejected', () => {
clearCookie(); const e = make(); e.initialize();
assert.throws(() => e.setConsent({ necessary: 'denied' }), TypeError);
assert.equal(e.getState().categories.necessary, 'granted');
});
test('invalid persisted category payload is ignored and removed', () => {
clearCookie();
const bad = {
engineVersion: ENGINE_VERSION,
policyVersion: '2026-01',
timestamp: Date.now(),
categories: { necessary: 'granted', preferences: 'banana', analytics: 'granted', marketing: 'denied' },
jurisdiction: 'global',
gpc: false
};
document.cookie = `cc_consent=${encodeURIComponent(JSON.stringify(bad))}; Path=/; SameSite=Lax`;
const e = make(); e.initialize();
assert.equal(e.isComplete(), false); assert.equal(document.cookie, '');
});
test('future timestamp is rejected', () => {
clearCookie(); const e = make(); e.initialize(); e.acceptAll();
const raw = decodeURIComponent(document.cookie.split('=').slice(1).join('='));
const payload = JSON.parse(raw); payload.timestamp = Date.now() + 60 * 60 * 1000;
document.cookie = `cc_consent=${encodeURIComponent(JSON.stringify(payload))}; Path=/; SameSite=Lax`;
const fresh = make(); fresh.initialize();
assert.equal(fresh.isComplete(), false); assert.equal(document.cookie, '');
});
console.log(`\n${passed} tests passed.`);
if (process.exitCode) process.exit(1);