import test from 'node:test'; import assert from 'node:assert/strict'; import {createRequire} from 'node:module'; import {randomUUID} from 'node:crypto'; const require=createRequire(import.meta.url); const {createBrokerAuditPersistence}=require('../apps/browser/broker-audit-persistence.cjs'); const event=()=>({eventId:randomUUID(),kind:'upload.authorized',ruleId:'broker-approved',policyVersion:4,request:{bytes:6,detectedType:'text'}}); function fixture(overrides={}){const queue=[];let writes=0,failures=0;const sink=createBrokerAuditPersistence({policyVersion:4,current:()=>true,getQueue:()=>queue,persistQueue:()=>{writes++;},onFailure:()=>{failures++;},...overrides});return {sink,queue,writes:()=>writes,failures:()=>failures};} test('receipt preserves broker event identity only after synchronous parent commit',()=>{ const f=fixture(),input=event(),receipt=f.sink.persist(input); assert.deepEqual(receipt,{persisted:true,eventId:input.eventId});assert.equal(f.writes(),1); assert.notEqual(f.queue[0],input);assert.ok(Object.isFrozen(f.queue[0]));assert.equal(f.queue[0].eventId,input.eventId); }); test('write failure is sticky and never issues a receipt or retries uncertain commit',()=>{ let writes=0;const f=fixture({persistQueue:()=>{writes++;throw Error('private filesystem detail');}}); assert.throws(()=>f.sink.persist(event()),/^Error: broker-audit-parent-unavailable$/); assert.throws(()=>f.sink.persist(event()),/broker-audit-parent-unavailable/);assert.equal(writes,1); }); test('invalid policy, duplicate ID and queue capacity fail before write',()=>{ for(const mode of ['policy','duplicate','capacity']){const f=fixture(),input=event();if(mode==='policy')input.policyVersion=3;if(mode==='duplicate')f.queue.push({...input});if(mode==='capacity')f.queue.push(...Array.from({length:10000},()=>({eventId:randomUUID()}))); assert.throws(()=>f.sink.persist(input),/broker-audit-parent-unavailable/);assert.equal(f.writes(),0); } }); test('revocation during commit prevents acknowledgment and asynchronous enqueue is rejected',()=>{ let active=true;const f=fixture({current:()=>active,persistQueue:()=>{active=false;}}); assert.throws(()=>f.sink.persist(event()),/broker-audit-parent-unavailable/); const asyncFixture=fixture({persistQueue:()=>Promise.resolve()});assert.throws(()=>asyncFixture.sink.persist(event()),/broker-audit-parent-unavailable/); for(const result of [false,true,{queued:true}]){const uncertain=fixture({persistQueue:()=>result});assert.throws(()=>uncertain.sink.persist(event()),/broker-audit-parent-unavailable/);} }); test('raw extra fields and top-level getters cannot become an audit channel',()=>{ const f=fixture();let read=false;const input=event();Object.defineProperty(input,'secret',{enumerable:true,get(){read=true;return 'private';}}); assert.throws(()=>f.sink.persist(input),/broker-audit-parent-unavailable/);assert.equal(read,false);assert.equal(f.writes(),0); const raw=fixture();assert.throws(()=>raw.sink.persist({...event(),body:'private'}),/broker-audit-parent-unavailable/);assert.equal(raw.writes(),0); const nested=fixture(),nestedInput=event();Object.defineProperty(nestedInput.request,'bytes',{enumerable:true,get(){read=true;return 6;}}); assert.throws(()=>nested.sink.persist(nestedInput),/broker-audit-parent-unavailable/);assert.equal(read,false);assert.equal(nested.writes(),0); }); test('real download facts are immutable and linked without a generic request hash',()=>{ const input={eventId:randomUUID(),kind:'download.block',ruleId:'synthetic-malware',policyVersion:4,file:{bytes:128,detectedType:'pdf',sha256:'a'.repeat(64)},broker:{requestId:randomUUID(),phase:'response.block',reason:'broker-download-inspection-block',authorizedEventId:randomUUID()}}; const f=fixture();assert.equal(f.sink.persist(input).eventId,input.eventId);assert.ok(Object.isFrozen(f.queue[0].file));assert.equal(f.queue[0].request,undefined); });