// @requirements REQ-BR-COOKIE-UI REQ-BR-COOKIE-RIGHTS-LINK REQ-BR-COOKIE-BROWSER-HELP
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {JSDOM} from 'jsdom';
import {validateSiteConfig,toPublicConfig,mountControls,normalizeConsentNotice} from 'notabenda';
import {compileLegalDocuments,createConsentNotice,compileConsentRuntimeConfig,canonicalJson,sha256} from 'notabenda/content';
import {createConsentRuntime} from 'notabenda/consent';
import {normalizeConsentInformation} from '../dist/consent-information.js';
import {createSetupPlan} from 'notabenda/setup';
import {recordLegalRelease,registerConsentNotice,setActiveConsentNotice,recordConsentChoice,getConsentHead,createConsentEvidenceStore,createAdminWorker} from 'notabenda-cf';
import {createSyntheticStorage} from '../../notabenda-cf/examples/synthetic.mjs';
import {consentProfiles} from './consent-profiles.mjs';
import {fixture} from './helpers.mjs';
const information={rights:{label:'Your privacy rights',url:'/privacy#rights'},browserControls:{title:'Browser storage controls',description:'Use your browser’s site-data settings. Blocking cookies does not stop every tracker; use these purpose controls to stop the declared services.',links:[{label:'Browser help',url:'https://browser.example.test/help'}]}};
async function config(){
const c=await fixture({reviewed:true});c.consent={purposes:[...new Set(c.services.flatMap(s=>s.purposeIds))].map(id=>({id,scope:'one'})),services:c.services.map(s=>({id:s.id,scope:'one'})),grantLifetimeDays:30,refusalLifetimeDays:90,dismissalMinutes:30,information:structuredClone(information)};return c;
}
const env=()=>({storage:{getItem:()=>null,setItem(){},removeItem(){}},now:()=>Date.parse('2026-10-19T12:00:00Z')});
const release=c=>{const build=compileLegalDocuments(c);return {build,notice:createConsentNotice(build.artifact,{scopeId:'cookie-choices'})};};
const rehash=n=>{const {noticeId:_,...descriptor}=n;return {...n,noticeId:sha256(canonicalJson(descriptor))};};
test('information is explicit, public-only, bounded and stable across config/runtime/notice projections',async()=>{
const c=await config();c.private={secret:'PRIVATE-REPORT'};assert.equal(validateSiteConfig(c).valid,true);
const compiled=compileConsentRuntimeConfig(c,{evidence:'local'}),{notice}=release(c);
for(const value of [toPublicConfig(c).consent.information,compiled.inventory.consent.information,notice.information]){assert.deepEqual(value,information);assert.doesNotMatch(JSON.stringify(value),/PRIVATE-REPORT/u);}
const out=normalizeConsentInformation({browserControls:{links:information.browserControls.links,description:information.browserControls.description,title:information.browserControls.title},rights:{url:information.rights.url,label:information.rights.label}});assert.equal(JSON.stringify(out),JSON.stringify(information));
delete c.consent.information;c.jurisdictions=['BR'];assert.equal(createConsentRuntime(compileConsentRuntimeConfig(c,{evidence:'local'}),env()).getConfig().consent.information,undefined);
});
test('malformed, private, unsafe and oversized information fails in both input paths',async()=>{
const c=await config(),bad=[{},null,[],{rights:{label:'',url:'/rights'}},{rights:{label:'Rights',url:'javascript:alert(1)'}},{rights:{label:'Rights',url:'//elsewhere.test'}},{rights:{label:'Rights',url:'https://user:pass@example.test'}},{rights:{label:'Rights',url:'/a%0ab'}},{rights:{label:'Rights',url:'/a\\b'}},{rights:{label:'Rights',url:'/rights',private:'PRIVATE-MARKER'}},{...information,notes:'PRIVATE-MARKER'},{browserControls:{title:'Help',description:'Text',links:Array(5).fill(information.rights)}},{browserControls:{title:'Help',description:'x'.repeat(2001),links:[]}},{rights:{label:'\u0000',url:'/rights'}},{browserControls:{title:'Help',description:'界'.repeat(1500),links:[]}}];
for(const value of bad){const input=structuredClone(c);input.consent.information=value;const result=validateSiteConfig(input);assert.equal(result.valid,false);assert.ok(result.diagnostics.some(d=>d.code==='invalid-consent-information'));assert.doesNotMatch(JSON.stringify(result.diagnostics),/PRIVATE-MARKER/u);const compiled=compileConsentRuntimeConfig(c,{evidence:'local'});compiled.inventory.consent.information=value;assert.throws(()=>createConsentRuntime(compiled,env()));}
});
test('each information field can be supplied independently, without inherited data or invented defaults',async()=>{
const c=await config();
for(const value of [{rights:{label:'Contact',url:'mailto:privacy@example.test'}},{browserControls:{title:'Browser settings',description:'Open site-data settings and review the actual browser options.',links:[]}}]){
c.consent.information=value;assert.equal(validateSiteConfig(c).valid,true);assert.deepEqual(compileConsentRuntimeConfig(c,{evidence:'local'}).inventory.consent.information,value);
}
for(const value of [Object.create({rights:information.rights}),{rights:Object.create(information.rights)},{browserControls:Object.create(information.browserControls)}])assert.throws(()=>normalizeConsentInformation(value));
});
test('sparse arrays, accessors and inherited optional siblings reject without executing getters',()=>{
const bad=[{...information,browserControls:{...information.browserControls,links:Array(1)}},Object.assign(Object.create({browserControls:information.browserControls}),{rights:information.rights})];
let reads=0;
const changing={label:'Rights',get url(){reads++;return reads<3?'/rights':'javascript:alert(1)';}};
bad.push({rights:changing},{get rights(){reads++;return information.rights;}});
const links=[information.rights];Object.defineProperty(links,'0',{get(){reads++;return information.rights;},enumerable:true});bad.push({browserControls:{...information.browserControls,links}});
for(const value of bad)assert.throws(()=>normalizeConsentInformation(value));
assert.equal(reads,0);
});
for(const [name,create] of consentProfiles){
test(`${name}: rights and collapsed browser help leave choices unchanged and render host copy as text`,async()=>{
const c=await config();c.consent.information.rights.label='';const consent=create(c,env()),dom=new JSDOM('