// 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'; 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 keyUser={email:'keys-browser-qa@example.test',name:'API QA',isAdmin:true}; const keyToken=signToken(keyUser); let apiKeyListFailure=false; let briefFailure='none'; 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 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']]; const tenders=entries.map(([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-responsive'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`
Gerçek uygulama ayrı bir görünüm alanında yüklenir.
`);return;} if(req.url==='/qa-telegram-controls'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`Gerçek Telegram mesajı gönderilmez.
`);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(`Sağlayıcı taklidi kullanılır; ücretli AI çağrısı yapılmaz.
`);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==='/qa-source-update') { const 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(`Yalnız geçici test deposunu etkiler.
`);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(`Sentetik ihale verileri ve geçici kullanıcı deposu kullanılır.
`);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);