Initial reliable release of consent engine
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Cookie Consent Core — Hardening Test Lab</title>
|
||||
<style>
|
||||
body{font-family:system-ui,sans-serif;margin:0;background:#f5f7fa;color:#1f2937}.wrap{max-width:1000px;margin:32px auto;padding:0 16px}.card{background:#fff;border:1px solid #dfe4ea;border-radius:12px;padding:20px;margin-bottom:18px}button{padding:9px 13px;border:1px solid #cbd2da;border-radius:7px;background:#fff;cursor:pointer}button.primary{background:#1f2937;color:#fff}.actions{display:flex;gap:8px;flex-wrap:wrap}.pass{color:#137333}.fail{color:#b42318}.neutral{color:#697386}.test{display:flex;justify-content:space-between;border-bottom:1px solid #eee;padding:10px 0;gap:16px}.test:last-child{border:0}pre{background:#111827;color:#e5e7eb;padding:16px;border-radius:8px;overflow:auto}code{font-family:ui-monospace,monospace}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header><h1>Cookie Consent Core — Hardening Test Lab</h1><p>Regression and robustness tests for Core v1.0.1.</p></header>
|
||||
<section class="card"><h2>Controls</h2><div class="actions"><button id="run" class="primary">Run tests</button><button id="accept">Accept All</button><button id="reject">Reject All</button><button id="revoke">Revoke</button><button id="reset">Reset</button><button id="refresh">Refresh</button></div></section>
|
||||
<section class="card"><h2>State</h2><pre id="state">—</pre></section>
|
||||
<section class="card"><h2>Tests</h2><div id="tests"></div><p id="summary" class="neutral">Not run.</p></section>
|
||||
<section class="card"><h2>Cookie</h2><pre id="cookie">—</pre></section>
|
||||
<section class="card"><h2>Event log</h2><pre id="log">—</pre></section>
|
||||
</div>
|
||||
<script type="module">
|
||||
import { createConsentEngine, ENGINE_VERSION } from "../core/consent.js";
|
||||
import { isStoragePayload } from "../core/storage.js";
|
||||
import { isValidCategoriesObject } from "../core/categories.js";
|
||||
|
||||
const config={policyVersion:"2026-01",jurisdiction:"global",storage:{name:"cc_consent",duration:180,path:"/",sameSite:"Lax"}};
|
||||
const engine=createConsentEngine(config);
|
||||
const log=[];
|
||||
const events=["initialized","consent:loaded","consent:accepted","consent:rejected","consent:changed","consent:revoked","consent:expired","consent:reset","storage:error"];
|
||||
for(const name of events) engine.on(name,payload=>{log.push({time:new Date().toLocaleTimeString(),event:name,payload});render();});
|
||||
|
||||
function render(){document.querySelector("#state").textContent=JSON.stringify(engine.getState(),null,2);document.querySelector("#cookie").textContent=document.cookie||"(no cookie)";document.querySelector("#log").textContent=JSON.stringify(log,null,2)}
|
||||
function assert(c,m){if(!c)throw new Error(m)}
|
||||
function clean(){engine.reset();document.cookie="cc_consent=; Max-Age=0; Path=/; SameSite=Lax"}
|
||||
function test(name,fn){try{fn();return [name,true] }catch(e){return [name,false,e.message]}}
|
||||
function run(){
|
||||
const results=[];
|
||||
function setCookiePayload(payload){
|
||||
document.cookie="cc_consent="+encodeURIComponent(JSON.stringify(payload))+"; Path=/; SameSite=Lax";
|
||||
}
|
||||
function clearDirect(){ document.cookie="cc_consent=; Max-Age=0; Path=/; SameSite=Lax"; }
|
||||
clean(); engine.initialize();
|
||||
results.push(test("Default / unknown",()=>{const s=engine.getState();assert(s.categories.necessary==="granted","necessary");assert(s.categories.analytics==="unknown","analytics");assert(!s.hasDecision,"decision") }));
|
||||
results.push(test("Accept All",()=>{engine.acceptAll();const s=engine.getState();assert(s.categories.analytics==="granted","analytics");assert(s.hasDecision,"decision")}));
|
||||
results.push(test("Accept All idempotent",()=>{const t=engine.getState().timestamp;let n=0;const off=engine.on("consent:changed",()=>n++);engine.acceptAll();off();assert(n===0,"duplicate changed event");assert(engine.getState().timestamp===t,"timestamp changed")}));
|
||||
results.push(test("Persistence",()=>{const e=createConsentEngine(config);e.initialize();assert(e.getState().source==="storage","source");assert(e.hasConsent("analytics"),"analytics")}));
|
||||
results.push(test("Reject All",()=>{engine.rejectAll();const s=engine.getState();assert(s.categories.analytics==="denied","analytics");assert(s.categories.necessary==="granted","necessary")}));
|
||||
results.push(test("Reject All idempotent",()=>{let n=0;const off=engine.on("consent:changed",()=>n++);engine.rejectAll();off();assert(n===0,"duplicate changed event")}));
|
||||
results.push(test("Selective",()=>{engine.setConsent({preferences:"denied",analytics:"granted",marketing:"denied"});assert(engine.isComplete(),"complete");assert(engine.hasConsent("analytics"),"analytics")}));
|
||||
results.push(test("Invalid value rejected",()=>{let ok=false;try{engine.setConsent({analytics:"banana"})}catch{ok=true}assert(ok,"must throw")}));
|
||||
results.push(test("Necessary cannot be denied",()=>{let ok=false;try{engine.setConsent({necessary:"denied"})}catch{ok=true}assert(ok,"must throw")}));
|
||||
results.push(test("Revoke",()=>{engine.acceptAll();engine.revoke();assert(engine.getState().categories.analytics==="denied","analytics")}));
|
||||
results.push(test("Revoke idempotent",()=>{let n=0;const off=engine.on("consent:revoked",()=>n++);engine.revoke();off();assert(n===0,"duplicate revoke")}));
|
||||
results.push(test("Reset",()=>{engine.acceptAll();engine.reset();assert(!engine.isComplete(),"decision");assert(!document.cookie,"cookie remains")}));
|
||||
results.push(test("Initialize idempotent",()=>{let n=0;const e=createConsentEngine(config);e.on("initialized",()=>n++);e.initialize();e.initialize();assert(n===1,"initialized twice")}));
|
||||
results.push(test("Strict stored envelope validation",()=>{assert(!isStoragePayload({}),"empty payload accepted");assert(isStoragePayload({engineVersion:ENGINE_VERSION,policyVersion:"2026-01",timestamp:Date.now(),categories:{},jurisdiction:"global",gpc:false}),"valid envelope rejected")}));
|
||||
results.push(test("Strict category validation",()=>{assert(isValidCategoriesObject({necessary:"granted",preferences:"unknown",analytics:"granted",marketing:"denied"}),"valid categories rejected");assert(!isValidCategoriesObject({necessary:"denied",preferences:"unknown",analytics:"granted",marketing:"denied"}),"necessary denied accepted");assert(!isValidCategoriesObject({necessary:"granted",preferences:"banana",analytics:"granted",marketing:"denied"}),"invalid category accepted")}));
|
||||
results.push(test("Policy version mismatch",()=>{clean();const e=createConsentEngine(config);e.initialize();e.acceptAll();const fresh=createConsentEngine({...config,policyVersion:"2027-01"});fresh.initialize();assert(!fresh.isComplete(),"old policy accepted");assert(!document.cookie,"old cookie remains")}));
|
||||
results.push(test("Expired cookie",()=>{clean();const e=createConsentEngine(config);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;setCookiePayload(payload);const fresh=createConsentEngine(config);let expired=0;fresh.on("consent:expired",()=>expired++);fresh.initialize();assert(expired===1,"expired event");assert(!fresh.isComplete(),"expired consent accepted");assert(!document.cookie,"expired cookie remains")}));
|
||||
results.push(test("Invalid persisted category payload",()=>{clean();const bad={engineVersion:ENGINE_VERSION,policyVersion:"2026-01",timestamp:Date.now(),categories:{necessary:"granted",preferences:"banana",analytics:"granted",marketing:"denied"},jurisdiction:"global",gpc:false};setCookiePayload(bad);const e=createConsentEngine(config);e.initialize();assert(!e.isComplete(),"invalid payload accepted");assert(!document.cookie,"invalid cookie remains")}));
|
||||
results.push(test("Future timestamp",()=>{clean();const e=createConsentEngine(config);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;setCookiePayload(payload);const fresh=createConsentEngine(config);fresh.initialize();assert(!fresh.isComplete(),"future timestamp accepted");assert(!document.cookie,"future cookie remains")}));
|
||||
const failed=results.filter(x=>!x[1]);
|
||||
document.querySelector("#tests").innerHTML=results.map(x=>`<div class="test"><span>${x[0]}${x[2]?` — ${x[2]}`:""}</span><strong class="${x[1]?"pass":"fail"}">${x[1]?"PASS":"FAIL"}</strong></div>`).join("");
|
||||
document.querySelector("#summary").textContent=`${results.length-failed.length}/${results.length} tests passed`;
|
||||
document.querySelector("#summary").className=failed.length?"fail":"pass";
|
||||
render();
|
||||
}
|
||||
document.querySelector("#run").onclick=run;document.querySelector("#accept").onclick=()=>{engine.acceptAll();render()};document.querySelector("#reject").onclick=()=>{engine.rejectAll();render()};document.querySelector("#revoke").onclick=()=>{engine.revoke();render()};document.querySelector("#reset").onclick=()=>{engine.reset();render()};document.querySelector("#refresh").onclick=render;engine.initialize();render();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user