Files
ihaleist/tests/ai-cancellation.test.mjs

51 lines
2.8 KiB
JavaScript

import {test} from 'node:test';
import assert from 'node:assert/strict';
import http from 'node:http';
import {requestLifetime} from '../server/request-lifetime.mjs';
import {analyzeSpec} from '../server/ai.mjs';
import {auditOfferInParts} from '../server/offer-audit-parts.mjs';
import {requestOfferAssistant} from '../src/services/offerAssistant.js';
test('premature real HTTP response closure aborts work after the request body is complete', {timeout:3000},async()=>{
let ready,aborted;
const received=new Promise(resolve=>ready=resolve), cancelled=new Promise(resolve=>aborted=resolve);
const server=http.createServer((req,res)=>{
const lifetime=requestLifetime(req,res);
lifetime.signal.addEventListener('abort',()=>{lifetime.dispose();aborted();},{once:true});
req.resume();req.on('end',()=>ready(lifetime));
});
await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));
const client=http.request({hostname:'127.0.0.1',port:server.address().port,method:'POST'});
client.on('error',()=>{});client.end('complete request body');
try {
const lifetime=await received;assert.equal(lifetime.signal.aborted,false);
client.destroy();await cancelled;assert.equal(lifetime.signal.aborted,true);
} finally {client.destroy();await new Promise(resolve=>server.close(resolve));}
});
test('caller cancellation aborts provider fetch and never triggers fallback',async()=>{
const original=globalThis.fetch,old=process.env.GEMINI_API_KEY;
process.env.GEMINI_API_KEY='synthetic-test-only';let calls=0;
const c=new AbortController();
try {
globalThis.fetch=async(url,{signal})=>new Promise((resolve,reject)=>{
calls++;signal.addEventListener('abort',()=>reject(new DOMException('Aborted','AbortError')),{once:true});c.abort();
});
await assert.rejects(analyzeSpec({text:'Sentetik kaynak',chain:['gemini'],signal:c.signal}),{name:'AbortError'});
assert.equal(calls,1);
} finally {globalThis.fetch=original;if(old===undefined)delete process.env.GEMINI_API_KEY;else process.env.GEMINI_API_KEY=old;}
});
test('audit cancellation stops remaining batches and is not returned as a partial result',async()=>{
const c=new AbortController();let calls=0;
await assert.rejects(auditOfferInParts(['Numune'],'x'.repeat(50000),async()=>{
calls++;c.abort();throw new DOMException('Aborted','AbortError');
},{signal:c.signal}),{name:'AbortError'});
assert.ok(calls<=2);
});
test('closing assistant view aborts its pending HTTP fetch',async()=>{
const c=new AbortController();let aborted=false;
await assert.rejects(requestOfferAssistant({url:'/api/ai/belge-sor',body:{},readToken:()=>'test',signal:c.signal,
fetcher:async(url,{signal})=>new Promise((resolve,reject)=>{
signal.addEventListener('abort',()=>{aborted=true;reject(new DOMException('Aborted','AbortError'));},{once:true});c.abort();
})}),{name:'AbortError'});
assert.equal(aborted,true);
});