26 lines
3.8 KiB
JavaScript
26 lines
3.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createServer } from 'node:http';
|
|
import express from 'express';
|
|
import { startTelemetry,tracer,recordSecurityEvent,installRequestTelemetry,traceOperation,importInspectionStages } from '../apps/server/telemetry.mjs';
|
|
test('OpenTelemetry exports traces, metrics and safe logs through OTLP HTTP',{timeout:20000},async t=>{
|
|
const requests=[];
|
|
const server=createServer((req,res)=>{const chunks=[];req.on('data',c=>chunks.push(c));req.on('end',()=>{requests.push({url:req.url,data:Buffer.concat(chunks)});res.setHeader('content-type','application/json');res.end('{}');});}).listen(0,'127.0.0.1');
|
|
await new Promise(r=>server.on('listening',r));process.env.OTEL_EXPORTER_OTLP_ENDPOINT=`http://127.0.0.1:${server.address().port}`;
|
|
t.after(async()=>{delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT;server.closeAllConnections();await new Promise(r=>server.close(r));});
|
|
const sdk=startTelemetry();t.after(()=>sdk.shutdown());tracer.startActiveSpan('inspection.test',span=>{span.setAttribute('dlp.decision','block');span.end();});
|
|
const app=express();installRequestTelemetry(app);app.post('/api/inspect/:id',async(req,res)=>{await traceOperation('ai.inspect.input',{bytes:17,prompt:'HTTP_SECRET_MUST_NOT_APPEAR'},async()=>new Promise(r=>setTimeout(r,15)));importInspectionStages({baseUnixMs:Date.now()-5,records:[{id:1,parentId:null,stage:'inspect',startOffsetMs:0,durationMs:4,status:'ok'},{id:2,parentId:1,stage:'parse',startOffsetMs:1,durationMs:2,status:'ok'}]},{diagnostics:{enabled:true,mode:'debug'}});res.json({ok:true});});
|
|
const application=app.listen(0,'127.0.0.1');await new Promise(r=>application.once('listening',r));t.after(async()=>{application.closeAllConnections();await new Promise(r=>application.close(r));});
|
|
const remoteTrace='1234567890abcdef1234567890abcdef',remoteParent='abcdef1234567890';
|
|
await fetch(`http://127.0.0.1:${application.address().port}/api/inspect/HTTP_SECRET_MUST_NOT_APPEAR?token=HTTP_SECRET_MUST_NOT_APPEAR`,{method:'POST',headers:{traceparent:`00-${remoteTrace}-${remoteParent}-01`,authorization:'Bearer HTTP_SECRET_MUST_NOT_APPEAR'},body:'HTTP_SECRET_MUST_NOT_APPEAR'});
|
|
await fetch(`http://127.0.0.1:${application.address().port}/api/inspect/invalid`,{method:'POST',headers:{traceparent:'00-'+ '0'.repeat(32)+'-'+ '0'.repeat(16)+'-01'}});
|
|
recordSecurityEvent('dlp.block',{ruleId:'test',policyVersion:1,prompt:'THIS_SECRET_MUST_NOT_APPEAR'},'event-1');await sdk.shutdown();
|
|
for(const path of ['/v1/traces','/v1/metrics','/v1/logs'])assert.ok(requests.some(r=>r.url===path),`Missing ${path}`);
|
|
for(const secret of ['THIS_SECRET_MUST_NOT_APPEAR','HTTP_SECRET_MUST_NOT_APPEAR'])assert.equal(requests.some(r=>r.data.includes(Buffer.from(secret))),false);
|
|
const spans=requests.filter(r=>r.url==='/v1/traces').flatMap(r=>JSON.parse(r.data).resourceSpans||[]).flatMap(r=>r.scopeSpans||[]).flatMap(r=>r.spans||[]);
|
|
const request=spans.find(s=>s.name==='server.request'&&s.traceId===remoteTrace);assert.ok(request);assert.equal(request.parentSpanId,remoteParent);assert.ok(request.attributes.some(a=>a.key==='http.route'&&a.value.stringValue==='/api/inspect/:id'));
|
|
const stage=spans.find(s=>s.name==='ai.inspect.input'&&s.traceId===remoteTrace);assert.ok(stage);assert.equal(stage.parentSpanId,request.spanId);assert.ok(stage.attributes.some(a=>a.key==='safetynet.duration.ms'&&a.value.doubleValue>=10));
|
|
const internal=spans.find(s=>s.name==='inspector.inspect'&&s.traceId===remoteTrace),parse=spans.find(s=>s.name==='inspector.parse'&&s.traceId===remoteTrace);assert.ok(internal&&parse);assert.equal(internal.parentSpanId,request.spanId);assert.equal(parse.parentSpanId,internal.spanId);
|
|
assert.ok(spans.some(s=>s.name==='server.request'&&s.traceId!==remoteTrace&&!s.parentSpanId),'Invalid remote context must start a new root trace');
|
|
});
|