Files
safetynet/tests/ai-gateway-revalidation.test.mjs
T

33 lines
3.0 KiB
JavaScript
Raw Normal View History

import test from 'node:test';
import assert from 'node:assert/strict';
import net from 'node:net';
import {createServer} from 'node:http';
import {mkdtempSync,rmSync} from 'node:fs';
import {tmpdir} from 'node:os';
import {join} from 'node:path';
import {randomUUID} from 'node:crypto';
import {openStore} from '../apps/server/store.mjs';
import {runApprovedAI} from '../apps/server/ai-gateway.mjs';
for(const change of ['revoke','same-version-policy','output-revoke','client-abort'])test(`gateway suppresses release after ${change} during actual ICAP scan`,async t=>{
const directory=mkdtempSync(join(tmpdir(),'safetynet-ai-race-')),store=openStore(directory),deviceId=randomUUID();
store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash) VALUES(?,?,?,?,?)').run(deviceId,'Fixture','win32','Fixture',randomUUID());
let deliveries=0,scans=0;const sockets=new Set(),controller=new AbortController();
const provider=createServer((req,res)=>{deliveries++;req.resume();res.end(JSON.stringify({choices:[{message:{content:'Public answer'}}]}));}).listen(0,'127.0.0.1');
await new Promise(r=>provider.once('listening',r));
const scanner=net.createServer(socket=>{sockets.add(socket);socket.on('error',()=>{});socket.once('close',()=>sockets.delete(socket));let bytes=Buffer.alloc(0),done=false;
socket.on('data',chunk=>{bytes=Buffer.concat([bytes,chunk]);if(done||!bytes.subarray(-5).equals(Buffer.from('0\r\n\r\n')))return;done=true;
if(bytes.subarray(0,7).toString()==='OPTIONS'){socket.end('ICAP/1.0 200 OK\r\nMethods: RESPMOD\r\nAllow: 204\r\nISTag: "fixture"\r\nEncapsulated: null-body=0\r\n\r\n');return;}
scans++;
if(change==='client-abort')controller.abort();
if(change==='revoke'||change==='output-revoke'&&scans===2)store.db.prepare('UPDATE devices SET revoked=1 WHERE id=?').run(deviceId);
else if(change==='same-version-policy'){const p=store.get('policy');p.ai.enabled=false;store.put('policy',p);}
socket.end('ICAP/1.0 204 No Content\r\nISTag: "fixture"\r\n\r\n');
});
}).listen(0,'127.0.0.1');await new Promise(r=>scanner.once('listening',r));
t.after(async()=>{for(const socket of sockets)socket.destroy();provider.closeAllConnections();await Promise.all([provider,scanner].map(server=>new Promise(r=>server.close(r))));store.db.close();rmSync(directory,{recursive:true,force:true});});
const p=store.get('policy');p.ai={...p.ai,enabled:true,trustMode:'local',model:'fixture'};p.enterprise.icap={...p.enterprise.icap,enabled:true,url:`icap://127.0.0.1:${scanner.address().port}/scan`,timeoutMs:1000};store.put('policy',p);
const policy=store.effectivePolicy(deviceId),config={aiBaseUrl:`http://127.0.0.1:${provider.address().port}`,aiModel:'fixture',aiEndpointId:'default'};
await assert.rejects(runApprovedAI({store,config,deviceId,policy,prompt:'Public fixture prompt',signal:controller.signal}),error=>error.status===(change==='same-version-policy'?409:403)&&error.ruleId==='ai-authorization-changed');
assert.equal(deliveries,change==='output-revoke'?1:0);
});