Files
safetynet/tests/agent-visual-diagnostics.test.mjs

7 lines
4.9 KiB
JavaScript

import test from 'node:test';import assert from 'node:assert/strict';import {createRequire} from 'node:module';import {createServer} from 'node:http';import {deflateSync} from 'node:zlib';import {DiagnosticsSchema} from '../packages/core/diagnostics.mjs';const require=createRequire(import.meta.url),{createDiagnostics}=require('../apps/browser/diagnostics.cjs'),{createAgentVisualExport}=require('../apps/browser/agent-visual-export.cjs');
function png(){const chunk=(type,data)=>{const name=Buffer.from(type),body=Buffer.concat([name,data]);let c=0xffffffff;for(const b of body){c^=b;for(let i=0;i<8;i++)c=(c&1)?0xedb88320^(c>>>1):c>>>1;}const length=Buffer.alloc(4),sum=Buffer.alloc(4);length.writeUInt32BE(data.length);sum.writeUInt32BE((c^0xffffffff)>>>0);return Buffer.concat([length,body,sum]);},header=Buffer.alloc(13);header.writeUInt32BE(1);header.writeUInt32BE(1,4);header[8]=8;header[9]=6;return Buffer.concat([Buffer.from([137,80,78,71,13,10,26,10]),chunk('IHDR',header),chunk('IDAT',deflateSync(Buffer.alloc(5))),chunk('IEND',Buffer.alloc(0))]);}
async function fixture(t){const payloads=[],server=createServer((req,res)=>{const chunks=[];req.on('data',c=>chunks.push(c));req.on('end',()=>{payloads.push(JSON.parse(Buffer.concat(chunks).toString()));res.setHeader('content-type','application/json');res.end('{}');});}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));const diagnostics=createDiagnostics();diagnostics.configure(DiagnosticsSchema.parse({enabled:true,mode:'debug',endpoint:`http://127.0.0.1:${server.address().port}`,sampleRate:1,debugSampleRate:1,flushIntervalMs:10000}));t.after(async()=>{await diagnostics.shutdown();server.closeAllConnections();await new Promise(r=>server.close(r));});const bytes=png(),context={ready:true,foreground:true,interactive:true,policy:{expiresAt:new Date(Date.now()+60000).toISOString(),agents:{enabled:true,scopes:['screenshot']}},sessionId:'00000000-0000-4000-8000-000000000001',token:{},actor:{agentId:'PRIVATE_ACTOR',generation:1},webContents:{getURL:()=> 'https://private.example/page',isDestroyed:()=>false},destinationAllowed:()=>true};let local={decision:'allow',contentType:'image',coverage:{complete:true}},safe=true;const exporter=createAgentVisualExport({getContext:()=>context,contextCurrent:()=>true,probeSensitive:async()=>({safe,revision:'PRIVATE_REVISION',width:1,height:1}),capture:async()=>bytes,inspectLocal:async()=>local,inspectEnterprise:async()=>({decision:'allow',coverage:{complete:true}}),audit:async()=>{},traceOperation:(operation,fn)=>diagnostics.span('browser.agent',{operation,url:'https://private.example/page',image:bytes.toString('base64'),actor:'PRIVATE_ACTOR'},fn)});return {bytes,diagnostics,payloads,run:()=>diagnostics.span('browser.agent',{operation:'screenshot'},()=>exporter()),block:()=>local={decision:'block',ruleId:'PRIVATE_RULE',contentType:'image',coverage:{complete:true}},unsafe:()=>safe=false,spans:()=>payloads.flatMap(p=>p.resourceSpans||[]).flatMap(p=>p.scopeSpans||[]).flatMap(p=>p.spans||[])};}
test('visual export produces real OTLP correlated named timings without image/page/actor data',async t=>{const f=await fixture(t),image=f.bytes.toString('base64');await f.run();await f.diagnostics.flush();const spans=f.spans(),op=s=>s.attributes.find(a=>a.key==='safetynet.operation')?.value.stringValue,parent=spans.find(s=>op(s)==='screenshot');assert.ok(parent);for(const operation of ['visual.probe','visual.capture','visual.validate','visual.inspect.local','visual.inspect.enterprise','visual.audit','visual.project']){const child=spans.find(s=>op(s)===operation);assert.ok(child,operation);assert.equal(child.parentSpanId,parent.spanId);assert.equal(child.traceId,parent.traceId);assert.ok(BigInt(child.endTimeUnixNano)>=BigInt(child.startTimeUnixNano));}for(const value of ['PRIVATE_ACTOR','PRIVATE_REVISION','private.example',image])assert.equal(JSON.stringify(f.payloads).includes(value),false);assert.equal(f.diagnostics.status().lifetimeFailedExports,0);});
test('blocked local OCR and unsafe probe export ERROR on the responsible named stage',async t=>{for(const mode of ['local','probe']){const f=await fixture(t);if(mode==='local')f.block();else f.unsafe();await assert.rejects(f.run(),e=>e.status===403&&e.message==='Managed visual export refused');await f.diagnostics.flush();const operation=mode==='local'?'visual.inspect.local':'visual.probe',stage=f.spans().find(s=>s.attributes.some(a=>a.key==='safetynet.operation'&&a.value.stringValue===operation));assert.ok(stage);assert.equal(stage.status.code,2);assert.equal(JSON.stringify(f.payloads).includes('PRIVATE_RULE'),false);if(mode==='local')assert.ok(f.bytes.every(b=>b===0));}});
test('admin diagnostics disable preserves screenshot inspection without any exported spans',async t=>{const f=await fixture(t);f.diagnostics.configure({enabled:false});assert.equal((await f.run()).mime,'image/png');await f.diagnostics.flush();assert.equal(f.spans().length,0);assert.ok(f.bytes.every(b=>b===0));});