Files

215 lines
25 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.
// Isolated, localhost-only UI fixture. It is never imported by production.
import {createServer} from 'vite';
import {spawn} from 'node:child_process';
import {mkdtempSync,rmSync} from 'node:fs';
import {tmpdir} from 'node:os';
import {join} from 'node:path';
import {randomBytes} from 'node:crypto';
import {createServer as netServer} from 'node:net';
import {readyBid} from './fixtures/ready-bid.mjs';
import {printBidFixture} from './fixtures/print-bid.mjs';
import {healthAcceptanceCases} from './fixtures/health-acceptance.mjs';
import {preparationSummary} from '../src/services/preparationSummary.js';
const dir=mkdtempSync(join(tmpdir(),'ihaleist-browser-'));
process.env.AUTH_SECRET=randomBytes(32).toString('hex');
const {signToken}=await import('../server/auth.mjs');
const user={email:'browser-qa@example.test',name:'Laboratuvar QA',isAdmin:false};
const token=signToken(user);
const otherUser={email:'browser-qa-other@example.test',name:'İkinci Laboratuvar QA',isAdmin:false};
const otherToken=signToken(otherUser);
const keyUser={email:'keys-browser-qa@example.test',name:'API QA',isAdmin:true};
const keyToken=signToken(keyUser);
let apiKeyListFailure=false;
let briefFailure='none';
let holdDocuments=false;
let skipDocumentReads=0,qaFiles=null,outputDocumentReads=0;
const qaFile={id:'qa-owned-file',ad:'QA zorunlu firma belgesi.pdf',tur:'firma',boyut:100,olusturma:'2026-09-06T00:00:00Z',erisilebilir:true};
const pendingDocuments=new Set();
process.env.IHALEIST_USER_DATA_DIR=dir;
const {createBriefOutbox}=await import('../server/brief-outbox.mjs');
const qaBriefOutbox=createBriefOutbox(dir);
const qaBriefBatch=qaBriefOutbox.prepare('2026-09-07','weekly-browser@example.test',[{channel:'email',to:'weekly-browser@example.test'},{channel:'telegram',chatId:'synthetic-chat'}]);
for(const position of [0,1])qaBriefOutbox.hold(qaBriefOutbox.claim(qaBriefBatch.id,position),'QA: Sağlayıcı sonucu belirsiz.');
const qaJobs=await import('../server/ai-job-store.mjs');
const {qualifyOfferDraft}=await import('../server/offer-draft.mjs');
const {executeAnalysisJob}=await import('../server/analysis-job.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 tenders=healthAcceptanceCases.map(({ikn: id,city,date,time})=>({id:'qa-'+id,ikn:'2026/'+id,baslik:'QA — '+city+' laboratuvar',kurum:city+' TEST KURUMU',sehir:city,tarih:date,saat:time,alimTuru:'Mal Alımı',usul:id==='1534665'?'Açık İhale':'Pazarlık Usulü',kurumTipi:'Eğitim ve Araştırma Hastaneleri',sektor:'Laboratuvar & Ölçüm',sektorler:['Laboratuvar & Ölçüm'],durum:'Açık',takipte:false,kanbanStatus:'incelenecek',items:[{id:'1',ad:'Sentetik test kalemi',miktar:2,birim:'adet'}],aciklama:'Bu yalnızca yerel arayüz test verisidir. Teklif geçerlilik süresi 120 takvim günü. Birim fiyat teklif. '.repeat(3)}));
// Seed actual persistent history only in this disposable QA volume.
process.env.IHALEIST_SHARED_DATA_DIR=join(dir,'shared');
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 qaNotifications=await import('../server/zeyilname.mjs');
qaNotifications.bildirimYaz({email:user.email,tur:'zeyilname',ikn:'2026/1582991',baslik:'QA tarih değişikliği',metin:'Sentetik bildirim: ihale tarihi güncellendi.'});
qaNotifications.bildirimYaz({email:user.email,tur:'sonuc',ikn:'2026/1534665',baslik:'QA sonuç bildirimi',metin:'Sentetik sonuç inceleme bildirimi.'});
let notificationFailure='read';
const qaSubscriptionStore=await import('../server/notify-store.mjs');
const qaReviewSub=qaSubscriptionStore.subscribe(user.email,{konular:['QA gönderim incelemesi']})[0];
const qaDelivery=qaSubscriptionStore.reserveDelivery(qaReviewSub.id,{to:user.email,subject:'QA belirsiz laboratuvar e-postası',html:'Sentetik içerik; gönderilmedi.'},['qa-review-tender'],{clock:Date.now()-24*3600000});
qaSubscriptionStore.failDelivery(qaDelivery,'Sentetik belirsiz sağlayıcı sonucu.');
qaSubscriptionStore.reserveDelivery(qaReviewSub.id,null,[]);
let subscriptionFailure='read';
let telegramFailure=true,telegramFixture=false,telegramInbox=null,telegramLatestCode=null,telegramClockOffset=0;
const qaTelegramStore=await import('../server/user-store.mjs');
const {createTelegramLinker}=await import('../server/telegram-link.mjs');
const qaTelegramLinker=createTelegramLinker(qaTelegramStore,()=>Date.now()+telegramClockOffset);
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(async(req,res,next)=>{
if(req.url==='/qa-print-cetvel'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(printBidFixture().html);return;}
if(req.url?.startsWith('/api/urun-ara?')&&new URL(req.url,'http://localhost').searchParams.get('q')==='QA pencere testi'){
res.writeHead(200,{'Content-Type':'application/json'});res.end(JSON.stringify({success:true,totalCount:1,list:[{...tenders[0],ihaleTarihSaat:tenders[0].tarih+' '+tenders[0].saat,tur:'QA'}]}));return;
}
if(req.url==='/qa-responsive'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel ekran boyutu testi</h1><p>Gerçek uygulama ayrı bir görünüm alanında yüklenir.</p><button data-width="390">390 piksel</button><button data-width="768">768 piksel</button><button data-width="1440">1440 piksel</button><pre id="metrics" aria-label="Görünüm ölçümleri"></pre><iframe title="İhaleist test görünümü" src="/" style="display:block;width:390px;height:900px;border:0"></iframe><script>document.querySelectorAll('button').forEach(button=>button.onclick=()=>document.querySelector('iframe').style.width=button.dataset.width+'px');setInterval(()=>{const doc=document.querySelector('iframe').contentDocument;if(!doc?.body)return;const width=doc.documentElement.clientWidth;const groups=[...doc.querySelectorAll('nav[aria-label="Çalışma alanları"] button')].map(el=>({label:el.textContent.trim(),left:Math.round(el.getBoundingClientRect().left),right:Math.round(el.getBoundingClientRect().right)}));document.getElementById('metrics').textContent=JSON.stringify({width,scrollWidth:doc.documentElement.scrollWidth,groups},null,2);},400);</script></html>`);return;}
if(req.url==='/qa-telegram-controls'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel Telegram testi</h1><p>Gerçek Telegram mesajı gönderilmez.</p><button id="recover">Telegram durum hatasını kapat</button><button id="enable">Sentetik bağlamayı aç</button><button id="receive">Son kodu sentetik sohbete ulaştır</button><button id="expire">Kod süresini bitir</button><p role="status"></p><script>for(const mode of ['recover','enable','receive','expire'])document.getElementById(mode).onclick=async()=>{const r=await fetch('/qa-telegram-fixture?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
if(req.url?.startsWith('/qa-telegram-fixture?')&&req.method==='POST'){
const mode=new URL(req.url,'http://localhost').searchParams.get('mode');
if(mode==='recover')telegramFailure=false;
if(mode==='enable'){telegramFixture=true;telegramFailure=false;}
if(mode==='receive')telegramInbox=telegramLatestCode;
if(mode==='expire')telegramClockOffset+=11*60000;
res.end('Sentetik Telegram durumu: '+mode);return;
}
if(req.url==='/api/me/telegram'&&telegramFixture&&['GET','POST'].includes(req.method)){
const reply=(status,data)=>{res.writeHead(status,{'Content-Type':'application/json'});res.end(JSON.stringify(data));};
if(req.headers.authorization!=='Bearer '+token){reply(401,{success:false,error:'Test hesabı gerekli.'});return;}
try {
if(req.method==='GET'){const tg=qaTelegramStore.getData(user.email,'telegram');reply(200,{success:true,yapilandirilmis:true,bagli:!!tg?.chatId,bot:null,ad:tg?.ad||null});return;}
let raw='';for await(const chunk of req)raw+=chunk;const body=JSON.parse(raw);
if(body.islem==='kod'){const challenge=qaTelegramLinker.issue(user.email);telegramLatestCode=challenge.kod;reply(200,{success:true,...challenge});return;}
if(body.islem==='test'){reply(400,{success:false,error:'Bu test gerçek mesaj göndermez.'});return;}
const found=await qaTelegramLinker.link(user.email,body.kod,async code=>code===telegramInbox?{chatId:'123456789',ad:'QA Sentetik Sohbet'}:null);
reply(200,{success:true,ad:found.ad});
}catch(error){reply(error.status||400,{success:false,error:error.message,kod:error.kod});}return;
}
if(req.url==='/qa-telegram-recover'&&req.method==='POST'){telegramFailure=false;res.end('ok');return;}
if(req.url==='/api/me/telegram'&&req.method==='GET'&&telegramFailure){res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Telegram durumu alınamadı.'}));return;}
if(req.url==='/qa-subscription-controls'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel abonelik testi</h1><button id="read">Abonelik liste hatası</button><button id="delete">Abonelik silme hatası</button><button id="none">Abonelik hatalarını kapat</button><p role="status"></p><script>for(const mode of ['read','delete','none'])document.getElementById(mode).onclick=async()=>{const r=await fetch('/qa-subscription-failure?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
if(req.url?.startsWith('/qa-subscription-failure?')&&req.method==='POST'){subscriptionFailure=new URL(req.url,'http://localhost').searchParams.get('mode');res.end('Abonelik test durumu: '+subscriptionFailure);return;}
if(req.url?.startsWith('/api/notify/subscriptions')&&((subscriptionFailure==='read'&&req.method==='GET')||(subscriptionFailure==='delete'&&req.method==='DELETE'))){res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Abonelik deposuna erişilemiyor.'}));return;}
if(req.url==='/qa-notification-controls') {res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel bildirim testi</h1><button id="read">Liste hatası</button><button id="write">Okundu kayıt hatası</button><button id="none">Hataları kapat</button><p role="status"></p><script>for(const mode of ['read','write','none'])document.getElementById(mode).onclick=async()=>{const r=await fetch('/qa-notification-failure?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
if(req.url?.startsWith('/qa-notification-failure?')&&req.method==='POST'){notificationFailure=new URL(req.url,'http://localhost').searchParams.get('mode');res.end('Bildirim test durumu: '+notificationFailure);return;}
if(req.url==='/api/me/bildirimler'&&((notificationFailure==='read'&&req.method==='GET')||(notificationFailure==='write'&&req.method==='POST'))){res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Bildirim deposuna erişilemiyor.'}));return;}
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()) {
const result=job.operation==='analiz' ? await executeAnalysisJob(job,{pin:qaJobs.pinAiJobInput,analyze:async()=>({result:{summary:'QA kayıtlı genel analiz: Laboratuvar kaynak metni korundu.',items:[],requiredDocuments:[],roadmap:[]},mode:job.input.mode,model:{label:'QA',provider:'sentetik',model:'qa'},attempts:[]})})
: {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)'])};
qaJobs.finishAiJob(job.id,job.runId,result);
}
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?.startsWith('/qa-output-documents?')&&req.method==='POST') {
const mode=new URL(req.url,'http://localhost').searchParams.get('mode');
if(mode==='hold'){holdDocuments=true;skipDocumentReads=0;outputDocumentReads=0;}
if(mode==='hold-final'){holdDocuments=true;skipDocumentReads=1;outputDocumentReads=0;}
if(mode==='revise')qaFiles=qaFiles?.map(file=>({...file,boyut:file.boyut+1}));
if(mode==='remove')qaFiles=[];
if(mode==='release'){holdDocuments=false;for(const pending of pendingDocuments){pending.writeHead(200,{'Content-Type':'application/json'});pending.end(JSON.stringify({success:true,belgeler:qaFiles || []}));}pendingDocuments.clear();}
res.end('Belge yanıtı: '+mode+'; okunan: '+outputDocumentReads+'; bekleyen: '+pendingDocuments.size);return;
}
if(req.url==='/api/me/documents'&&req.method==='GET'){
if(holdDocuments){outputDocumentReads++;if(skipDocumentReads>0)skipDocumentReads--;else{pendingDocuments.add(res);res.on('close',()=>pendingDocuments.delete(res));return;}}
if(qaFiles){res.writeHead(200,{'Content-Type':'application/json'});res.end(JSON.stringify({success:true,belgeler:qaFiles}));return;}
}
if(req.url==='/qa-overview'){
res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Hazırlık özeti görünüm testi</h1>${[390,768,1440].map(width=>`<h2>${width} piksel</h2><iframe title="Hazırlık ${width}" src="/qa-overview-frame" style="display:block;width:${width}px;height:700px;border:0"></iframe>`).join('')}</html>`);return;
}
if(req.url?.startsWith('/qa-overview-frame')){
const frameWidth=new URL(req.url,'http://localhost').searchParams.get('width');
const widthStyle=/^(390|768|1440)$/.test(frameWidth||'')?` style="width:${frameWidth}px"`:'';
const cases=tenders.map((t,i)=>{
const base={...t,baslik:i===0?'QA Uzun laboratuvar cihazı '+ 'KesintisizÜrünKodu'.repeat(12):t.baslik,atananPersonel:i===0?'QA Sorumlu Uzman':'',takipte:true};
if(i!==0)return base;
const ownerFile={id:'qa-owner-file',ad:'QA sorumlu belgesi.pdf',tur:'firma',boyut:100,olusturma:'2026-09-06T00:00:00Z',erisilebilir:true};
const prepared=readyBid(base,ownerFile);
const [documentKeyValue]=Object.keys(prepared.bidDraft.checks.documents);
prepared.bidDraft.checks.documents[documentKeyValue].owner='QA Belge Sorumlusu';
return prepared;
});
const html=`<html lang="tr"><head><meta name="viewport" content="width=device-width,initial-scale=1"></head><body${widthStyle}><div id="root"></div><script type="module">
import React from '/node_modules/.vite/deps/react.js';
import ReactDOM from '/node_modules/.vite/deps/react-dom_client.js';
import {AppProvider,useApp} from '/src/context/AppContext.jsx';
import {PreparationOverview} from '/src/components/PreparationOverview.jsx';
import '/src/index.css';
function Probe(){const {activeTenderDetail}=useApp();return React.createElement('main',{},React.createElement(PreparationOverview,{tenders:${JSON.stringify(cases)},now:new Date('2026-09-06T09:00:00Z')}),React.createElement('p',{role:'status'},activeTenderDetail?'Seçilen hedef: '+activeTenderDetail.ikn+' / '+activeTenderDetail.preparationTarget:''));}
ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(AppProvider,{},React.createElement(Probe)));
</script></body></html>`;
res.setHeader('Content-Type','text/html; charset=utf-8');res.end(await server.transformIndexHtml('/qa-overview-frame',html));return;
}
if(req.url==='/qa-calendar-links'||req.url?.startsWith('/qa-calendar-links?')){
const missingTime=new URL(req.url,'http://localhost').searchParams.has('missing');
const calendarTender={...tenders[0],id:missingTime?'qa-calendar-missing':'qa-calendar-valid',ikn:'2026/1582991',tarih:'10.09.2026',saat:missingTime?'':'10:00',aciklama:missingTime?'':'Kaynak tarihi doğrulama',baslik:'QA Giresun mikroskop',kurum:'Giresun TEST KURUMU',ihaleYeri:'QA toplantı salonu'};
const html=`<html lang="tr"><head><meta name="viewport" content="width=device-width,initial-scale=1"></head><body><div id="root"></div><script type="module">
import React from '/node_modules/.vite/deps/react.js';
import ReactDOM from '/node_modules/.vite/deps/react-dom_client.js';
import {AppProvider,useApp} from '/src/context/AppContext.jsx';
import {TenderDetailModal} from '/src/components/TenderDetailModal.jsx';
import '/src/index.css';
const selected=${JSON.stringify(calendarTender)};
function Probe(){const {setActiveTenderDetail,authUser}=useApp();React.useEffect(()=>{if(!authUser?.email)return;const timer=setTimeout(()=>setActiveTenderDetail(selected),0);return()=>clearTimeout(timer);},[authUser?.email]);return React.createElement(TenderDetailModal);}
ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(AppProvider,{},React.createElement(Probe)));
</script></body></html>`;
res.setHeader('Content-Type','text/html; charset=utf-8');res.end(await server.transformIndexHtml(missingTime?'/qa-calendar-links-missing':'/qa-calendar-links',html));return;
}
if(req.url==='/qa-source-update'||req.url==='/qa-source-update?ready=1'||req.url==='/qa-source-update?ready=1&file=1') {
const query=new URL(req.url,'http://localhost').searchParams;
qaFiles=query.has('file')?[{...qaFile}]:null;
const selected=query.has('ready')?readyBid(tenders[0],qaFiles?.[0]):tenders[0];
if(selected.bidDraft){const summary=preparationSummary(selected,{loading:false,error:null,documents:qaFiles || []},new Date('2026-09-06T09:00:00Z'));if(summary.issues.length)throw new Error('QA ready fixture: '+JSON.stringify(summary.issues));}
const html=`<html lang="tr"><head><meta name="viewport" content="width=device-width,initial-scale=1"></head><body><div id="root"></div><script type="module">
import React from '/node_modules/.vite/deps/react.js';
import ReactDOM from '/node_modules/.vite/deps/react-dom_client.js';
import {AppProvider,useApp} from '/src/context/AppContext.jsx';
import {BidPackageBuilder} from '/src/components/BidPackageBuilder.jsx';
import '/src/index.css';
const selected=${JSON.stringify(selected)};
function Probe(){const {setTenders}=useApp();const [status,setStatus]=React.useState('');const control=async mode=>setStatus(await (await fetch('/qa-output-documents?mode='+mode,{method:'POST'})).text());return React.createElement('main',{},
React.createElement('h1',{},'Yerel kaynak güncelleme testi'),
React.createElement('p',{},'Yalnız sentetik veri. Açık dosya korunurken güncel kaynak değişir.'),
React.createElement('button',{onClick:()=>control('hold')},'Belge yanıtını beklet'),
React.createElement('button',{onClick:()=>control('hold-final')},'Son belge kontrolünü beklet'),
React.createElement('button',{onClick:()=>control('revise')},'Bağlı belgenin sürümünü değiştir'),
React.createElement('button',{onClick:()=>control('remove')},'Bağlı belgeyi kaldır'),
React.createElement('button',{onClick:()=>control('status')},'Bekleyen belge isteklerini göster'),
React.createElement('button',{onClick:()=>control('release')},'Belge yanıtını serbest bırak'),
React.createElement('p',{role:'status'},status),
React.createElement('button',{onClick:()=>setTenders([{...selected,id:'qa-rotated-source',baslik:'QA Güncellenmiş alım',kazananFirma:'QA Kazanan'}])},'Aynı İKN için yeni kayıt ID ve kazanan yayımla'),
React.createElement(BidPackageBuilder,{tender:selected,now:new Date('2026-09-06T09:00:00Z')}));}
ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(AppProvider,{},React.createElement(Probe)));
</script></body></html>`;
res.setHeader('Content-Type','text/html; charset=utf-8');res.end(await server.transformIndexHtml('/qa-source-update',html));return;
}
if(req.url==='/qa-brief-controls') {res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel haftalık özet testi</h1><button id="read">Özet okuma hatası</button><button id="write">Özet kayıt hatası</button><button id="none">Özet hatalarını kapat</button><p role="status"></p><script>for(const mode of ['read','write','none'])document.getElementById(mode).onclick=async()=>{const r=await fetch('/qa-brief-failure?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
if(req.url?.startsWith('/qa-brief-failure?')&&req.method==='POST'){briefFailure=new URL(req.url,'http://localhost').searchParams.get('mode');res.end('Özet test durumu: '+briefFailure);return;}
if(req.url?.startsWith('/api/me/brief')&&((briefFailure==='read'&&req.method==='GET')||(briefFailure==='write'&&req.method==='POST'))){res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Haftalık özet deposuna erişilemiyor.'}));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.url?.startsWith('/qa-switch-account?')) {
const account=new URL(req.url,'http://localhost').searchParams.get('account');
const selected=account==='other'?otherUser:user;
const selectedToken=account==='other'?otherToken:token;
res.setHeader('Content-Type','text/html; charset=utf-8');
res.end(`<html lang="tr"><body><p>QA hesap değişimi</p><script type="module">import {setSession} from '/src/services/auth.js';setSession(${JSON.stringify(selectedToken)},${JSON.stringify(selected)});location.replace('/');</script></body></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;}
if(req.url==='/qa-signin') {res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel laboratuvar testi</h1><p>Sentetik ihale verileri ve geçici kullanıcı deposu kullanılır.</p><button id="login">Test hesabıyla gir</button><script type="module">import {setSession} from '/src/services/auth.js';document.querySelector('#login').onclick=()=>{setSession(${JSON.stringify(token)},${JSON.stringify(user)});location.href='/';};</script></html>`);return;}
if(req.url==='/api/tenders' || req.url?.startsWith('/api/tenders?')){res.setHeader('Content-Type','application/json');res.end(JSON.stringify({success:true,tenders,totalCount:5,todayCount:0,historyCount:0,source:'Yerel sentetik QA',fetchedAt:new Date().toISOString()}));return;}
next();
});}}]});
await ui.listen();console.log('QA_URL='+ui.resolvedUrls.local[0]+'qa-signin');
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);