Verify durable AI job result recovery through the browser

This commit is contained in:
2026-09-06 12:37:53 +03:00
parent f830625b60
commit 801d4fca8d
4 changed files with 39 additions and 2 deletions
+16 -1
View File
@@ -14,6 +14,11 @@ const token=signToken(user);
const keyUser={email:'keys-browser-qa@example.test',name:'API QA',isAdmin:true};
const keyToken=signToken(keyUser);
let apiKeyListFailure=false;
process.env.IHALEIST_USER_DATA_DIR=dir;
const qaJobs=await import('../server/ai-job-store.mjs');
const {qualifyOfferDraft}=await import('../server/offer-draft.mjs');
const pendingQaJobs=new Map();
const qaHeartbeat=setInterval(()=>{for(const job of pendingQaJobs.values())qaJobs.heartbeatAiJob(job.id,job.runId);},5000);qaHeartbeat.unref();
const probe=netServer();await new Promise(r=>probe.listen(0,'127.0.0.1',r));const port=probe.address().port;await new Promise(r=>probe.close(r));
const api=spawn(process.execPath,['server/index.js'],{cwd:new URL('..',import.meta.url),env:{PATH:process.env.PATH,NODE_ENV:'test',AUTH_SECRET:process.env.AUTH_SECRET,IHALEIST_USER_DATA_DIR:dir,IHALEIST_SHARED_DATA_DIR:join(dir,'shared'),PORT:String(port),UYUM_GOMME:'0'},stdio:'ignore'});
const entries=[['1582991','Giresun','10.09.2026','10:00'],['1534665','Kayseri','22.09.2026','11:00'],['1499061','Akdeniz','22.09.2026','10:30'],['1573235','Aksaray','09.09.2026','10:00'],['1523851','Tepecik','11.09.2026','10:00']];
@@ -24,6 +29,16 @@ const {observeTenders}=await import('../server/tender-history.mjs');
observeTenders(tenders);
for(let revision=1;revision<=22;revision++) observeTenders([{...tenders[0],saat:revision%2?'11:00':'10:00'}]);
const ui=await createServer({server:{host:'127.0.0.1',port:0,proxy:{'/api':{target:'http://127.0.0.1:'+port}}},plugins:[{name:'qa-fixtures',configureServer(server){server.middlewares.use((req,res,next)=>{
if(req.url==='/qa-ai-controls') {res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel AI işi testi</h1><p>Sağlayıcı taklidi kullanılır; ücretli AI çağrısı yapılmaz.</p><button id="finish">Bekleyen QA işini tamamla</button><p role="status"></p><script>document.querySelector('#finish').onclick=async()=>{const r=await fetch('/qa-ai-complete',{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
if(req.url==='/qa-ai-complete' && req.method==='POST') {for(const job of pendingQaJobs.values())qaJobs.finishAiJob(job.id,job.runId,{success:true,...qualifyOfferDraft({bolumler:[{baslik:'Teklif Mektubu (Kapak)',icerik:'QA kayıtlı sonuç: Laboratuvar teklif taslağı. Bu sentetik metin resmi teklif değildir.'}]},['Teklif Mektubu (Kapak)'])});pendingQaJobs.clear();res.end('QA işleri tamamlandı');return;}
if(req.url==='/api/me/ai-jobs' && req.method==='POST') {
if(req.headers.authorization!=='Bearer '+token){res.writeHead(401);res.end('{}');return;}
let body='';req.on('data',chunk=>body+=chunk);req.on('end',()=>{try{
const input=JSON.parse(body);const created=qaJobs.createAiJob(user.email,input.requestId,input.operation,input.input);
const claimed=qaJobs.claimAiJob();if(claimed)pendingQaJobs.set(claimed.id,claimed);
res.setHeader('Content-Type','application/json');res.end(JSON.stringify({success:true,job:qaJobs.getAiJob(user.email,created.id)}));
}catch(error){res.writeHead(400,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:error.message}));}});return;
}
if(req.url==='/qa-api-controls') {res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel API testi</h1><p>Yalnız geçici test deposunu etkiler.</p><button id="login">API test hesabıyla gir</button><button id="fail">Liste hatasını aç</button><button id="recover">Liste hatasını kapat</button><p role="status"></p><script type="module">import {setSession} from '/src/services/auth.js';document.querySelector('#login').onclick=()=>{setSession(${JSON.stringify(keyToken)},${JSON.stringify(keyUser)});location.href='/';};for(const [id,mode] of [['fail','on'],['recover','off']])document.getElementById(id).onclick=async()=>{await fetch('/qa-api-failure?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=mode==='on'?'Liste hatası açık':'Liste hatası kapalı';};</script></html>`);return;}
if(req.method==='POST' && req.url?.startsWith('/qa-api-failure?')) {apiKeyListFailure=new URL(req.url,'http://localhost').searchParams.get('mode')==='on';res.end('ok');return;}
if(apiKeyListFailure && req.method==='GET' && req.url==='/api/me/apikeys') {res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Anahtar deposuna erişilemiyor.'}));return;}
@@ -32,5 +47,5 @@ const ui=await createServer({server:{host:'127.0.0.1',port:0,proxy:{'/api':{targ
next();
});}}]});
await ui.listen();console.log('QA_URL='+ui.resolvedUrls.local[0]+'qa-signin');
async function stop(){await ui.close();api.kill();rmSync(dir,{recursive:true,force:true});process.exit();}
async function stop(){clearInterval(qaHeartbeat);await ui.close();api.kill();rmSync(dir,{recursive:true,force:true});process.exit();}
process.on('SIGINT',stop);process.on('SIGTERM',stop);