Files
ihaleist/tests/offer-assistant.test.mjs

52 lines
3.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {requestOfferAssistant} from '../src/services/offerAssistant.js';
const body={ikn:'2026/123',text:'Private source'};
test('account switch while inventory loads cannot send private context under the next account',async()=>{
let token='first', calls=0;
await assert.rejects(requestOfferAssistant({url:'/draft',body,readToken:()=>token,
listDocuments:async()=>{token='second';return {belgeler:[{ad:'Private document',tur:'firma'}]};},
fetcher:async()=>{calls++;return Response.json({success:true});}}),/Oturum veya ihale değişti/);
assert.equal(calls,0);
});
test('inventory errors stop generation and do not silently become empty documents',async()=>{
let calls=0;
for(const listDocuments of [async()=>{throw Error('Inventory unavailable');},async()=>({})]) {
await assert.rejects(requestOfferAssistant({url:'/draft',body,readToken:()=>'first',listDocuments,
fetcher:async()=>{calls++;return Response.json({});}}));
}
assert.equal(calls,0);
});
test('generation uses only company or matching tender documents and retains starting authorization',async()=>{
const result=await requestOfferAssistant({url:'/draft',body,readToken:()=>'first',
listDocuments:async()=>({belgeler:[{ad:'Company',tur:'firma'},{ad:'Source',tur:'ihale',ikn:'2026/123'},{ad:'Other tender',tur:'ihale',ikn:'2026/999'}]}),
fetcher:async(url,options)=>{
assert.equal(options.headers.Authorization,'Bearer first');
assert.deepEqual(JSON.parse(options.body).belgeler,[{ad:'Company'},{ad:'Source'}]);
return Response.json({success:true,bolumler:[]});
}});
assert.equal(result.success,true);
});
test('late answer is rejected after account or active tender changes',async()=>{
for (const change of ['account','view']) {
let token='first', mounted=true;
await assert.rejects(requestOfferAssistant({url:'/ask',body,readToken:()=>token,isCurrent:()=>mounted,
fetcher:async()=>{if(change==='account')token='second';else mounted=false;return Response.json({cevap:'Private answer'});}}),/Oturum veya ihale değişti/);
}
});
test('HTML, null and incomplete success responses cannot replace a valid existing result',async()=>{
for(const response of [new Response('<html>Gateway</html>'),Response.json(null),Response.json({}),Response.json({success:true,maddeler:[{durum:'unknown'}]})]) {
await assert.rejects(requestOfferAssistant({url:'/api/ai/teklif-denetim',body,readToken:()=>'first',fetcher:async()=>response}));
}
});
test('a stalled AI request times out instead of locking the assistant indefinitely',async()=>{
await assert.rejects(requestOfferAssistant({url:'/api/ai/belge-sor',body,readToken:()=>'first',timeoutMs:5,
fetcher:async(url,{signal})=>new Promise((resolve,reject)=>signal.addEventListener('abort',()=>reject(new DOMException('Aborted','AbortError')),{once:true}))}),/zaman aşımı/);
});
test('service unavailability gives ordinary users actionable feedback, not server setup instructions',async()=>{
await assert.rejects(requestOfferAssistant({url:'/api/ai/belge-sor',body,readToken:()=>'first',
fetcher:async()=>Response.json({success:false,error:'Internal provider setup details'},{status:503})}),error=>{
assert.match(error.message,/Çalışmanız korundu/);assert.doesNotMatch(error.message,/Internal provider/);return true;
});
});