13 lines
4.3 KiB
JavaScript
13 lines
4.3 KiB
JavaScript
import test from 'node:test';
|
|||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import {createRequire} from 'node:module';
|
||
|
|
const require=createRequire(import.meta.url);
|
||
|
|
const {buildInteractionEvidence,validateInteractionEvidence}=require('../apps/browser/evidence-capture.cjs');
|
||
|
|
const {collectionAllowed,stripPendingEvidence,deliverAuditRecord,validatedEvidence}=require('../apps/browser/evidence-delivery.cjs');
|
||
|
|
const sessionId='00000000-0000-4000-8000-000000000001';
|
||
|
|
function fixture(){const context={ready:true,deviceId:'device-a',policy:{expiresAt:new Date(Date.now()+60000).toISOString(),evidence:{enabled:true,captureDownloads:true,captureInteractions:true}}};const payload=buildInteractionEvidence({action:'copy',sessionId,url:'https://example.com/private/password=secret?q=secret#secret',sensitiveField:false,outcome:'completed',captureSource:'guarded-clipboard',bytes:12},context.policy);const record={eventId:sessionId,kind:'clipboard.copy',protectedEvidence:{deviceId:'device-a',type:'interaction',payload}};return {context,payload,record};}
|
||
|
|
test('automatic interaction capture requires observed completion and excludes path and sensitive data',()=>{const f=fixture();assert.equal(f.payload.pageUrl,'https://example.com/');assert.equal(validateInteractionEvidence(f.payload),true);for(const extra of [{content:'secret'},{contentSha256:'a'.repeat(64)},{target:{tag:'INPUT',label:'secret'}},{sensitiveField:true},{outcome:'authorized'},{captureSource:'page'},{bytes:16777217}])assert.equal(validateInteractionEvidence({...f.payload,...extra}),false);assert.equal(buildInteractionEvidence({action:'copy',sessionId,url:'https://example.com',sensitiveField:false,outcome:'authorized',captureSource:'guarded-clipboard'},f.context.policy),null);});
|
||
|
|
test('type binding rejects cross-type payloads without attempting evidence send',async()=>{const f=fixture();assert.throws(()=>validatedEvidence(f.payload,'download'),/Invalid/);f.record.protectedEvidence.type='download';const calls=[];await assert.rejects(deliverAuditRecord({record:f.record,getContext:()=>f.context,send:async route=>{calls.push(route);return {};}}),/Invalid/);assert.deepEqual(calls,['/api/device/events']);});
|
||
|
|
test('partial disable discards interaction tuples while preserving pending legacy downloads',()=>{const f=fixture();f.context.policy.evidence.captureInteractions=false;assert.equal(collectionAllowed({...f.context,type:'interaction'}),false);assert.equal(collectionAllowed({...f.context,type:'download'}),true);const download={eventId:'download',protectedEvidence:{deviceId:'device-a',payload:{}}};const result=stripPendingEvidence([f.record,download],f.context);assert.equal('protectedEvidence' in result[0],false);assert.equal(result[1],download);assert.ok(f.record.protectedEvidence);});
|
||
|
|
test('revocation during ACK never marks evidence delivered and replay preserves original tuple',async()=>{const f=fixture(),snapshot=structuredClone(f.record),bodies=[];const send=async(route,body)=>{bodies.push(body);if(route.endsWith('/evidence')){f.context.policy.evidence.captureInteractions=false;return {id:sessionId,sourceEventId:1};}return {};};assert.deepEqual(await deliverAuditRecord({record:f.record,getContext:()=>f.context,send}),{eventDelivered:true,evidenceDiscarded:true});assert.deepEqual(f.record,snapshot);assert.equal(bodies[1].type,'interaction');assert.equal('protectedEvidence' in bodies[0],false);});
|
||
|
|
test('native hook target tags are canonicalized without accepting labels or arbitrary DOM data',()=>{const f=fixture(),base={action:'click',sessionId,url:'https://example.com/page',sensitiveField:false,outcome:'completed',captureSource:'agent-adapter'};for(const tag of ['a','button','input','textarea','select','div','span','p','body','unknown']){const value=buildInteractionEvidence({...base,target:{tag,role:'menuitem',x:12,y:34}},f.context.policy);assert.equal(value.target.tag,tag.toUpperCase());assert.equal(value.target.role,'menuitem');assert.deepEqual([value.target.x,value.target.y],[12,34]);}for(const target of [{tag:'button',label:'secret'},{tag:'a',selector:'#secret'},{tag:'body',text:'secret'},{tag:'script'},{tag:'button',x:Infinity},{tag:'button',y:-1}])assert.equal(buildInteractionEvidence({...base,target},f.context.policy),null);assert.equal(buildInteractionEvidence({...base,target:{tag:'select',role:'option'}},f.context.policy).target.role,'option');});
|