Verify paginated source history in the browser and fix QA route matching

This commit is contained in:
2026-09-06 10:51:13 +03:00
parent 68fe84e9c7
commit 8d6f3bc3ea
4 changed files with 28 additions and 2 deletions
File diff suppressed because one or more lines are too long
+8
View File
@@ -681,3 +681,11 @@ Resmi İKN ortak ihale anahtarıdır; doğrudan temin numarası kurumla birlikte
Oturum gerektiren sayfalı geçmiş API'si ve ayrıntı ekranında isteğe bağlı geçmiş görünümü eklendi. Önce/sonra metinleri açılabilir, daha eski kayıtlar getirilebilir. Boş geçmiş değişiklik yokluğunun kanıtı olarak sunulmaz. Kayıt hatası havuz uyarısına, okuma hatası yeniden denemeli ekran durumuna yansır. Kaynak kaydı yalnız uygulamanın gözlemlediği andan itibaren birikir; geçmişe dönük resmi zeyilnameler yüklenmiş değildir. Oturum gerektiren sayfalı geçmiş API'si ve ayrıntı ekranında isteğe bağlı geçmiş görünümü eklendi. Önce/sonra metinleri açılabilir, daha eski kayıtlar getirilebilir. Boş geçmiş değişiklik yokluğunun kanıtı olarak sunulmaz. Kayıt hatası havuz uyarısına, okuma hatası yeniden denemeli ekran durumuna yansır. Kaynak kaydı yalnız uygulamanın gözlemlediği andan itibaren birikir; geçmişe dönük resmi zeyilnameler yüklenmiş değildir.
Doğrulama: gerçek SQLite yeniden açılışları, aynı veriyi tekrar alma, değer geri dönüşü, kaynak ayrımı, kurum kapsamlı DT kimliği, sayfalama; HTTP oturum/boş sonuç/geçersiz sayfa/metot. Tüm 147 test ve üretim derlemesi geçti. Son depo-açılış temizliği değişikliğinden sonra iki depo testi tekrar geçti. Canlı kaynak birikimi, tarayıcıdan dolu geçmiş ve zeyilname bildirim kabulü açık. Bu volume kullanıcı yedeğinin kapsamı dışında; tam D11 yedeğine ayrıca dahil edilmeli. Canlı yayın yapılmadı. Doğrulama: gerçek SQLite yeniden açılışları, aynı veriyi tekrar alma, değer geri dönüşü, kaynak ayrımı, kurum kapsamlı DT kimliği, sayfalama; HTTP oturum/boş sonuç/geçersiz sayfa/metot. Tüm 147 test ve üretim derlemesi geçti. Son depo-açılış temizliği değişikliğinden sonra iki depo testi tekrar geçti. Canlı kaynak birikimi, tarayıcıdan dolu geçmiş ve zeyilname bildirim kabulü açık. Bu volume kullanıcı yedeğinin kapsamı dışında; tam D11 yedeğine ayrıca dahil edilmeli. Canlı yayın yapılmadı.
### D04 / D09 — Kaynak geçmişi tarayıcı kabulü
Yerel QA yardımcı sunucusu geçici gerçek SQLite deposuna ilk gözlem ve 22 saat değişikliği ekleyecek şekilde genişletildi. İlk tarayıcı denemesi test middleware'inin `/api/tenders` önekli bütün yolları örnek listeyle karşıladığını ortaya çıkardı. Eşleşme yalnız liste yoluna ve sorgusuna daraltıldı; çalışan test sunucusu bu kod değişikliği için kontrollü yeniden başlatıldı.
Gerçek geçmiş API'si üzerinden Giresun ayrıntısı → Kaynak değişiklik geçmişi → Geçmişi yükle akışında 20 kayıt; ilk Saat değişti başlığında Önce 11:00 / Sonra 10:00; Daha eski kayıtlar ile toplam 23 kayıt ve ilk gözlem; son sayfada devam düğmesinin kaybolması doğrulandı. Altı kontrol doğru. Kanıt qa-source-history.json. Veriler sentetik ve geçici; canlı resmi zeyilname kabulü değildir.
Yeni açılır başlıkların klavye erişimi için modal odak döngüsüne summary öğeleri eklendi. Üretim derlemesi geçti. Önceki 147 otomatik test bu yalnız QA/odak seçicisi turunda tekrarlanmadı. Ağ hata/yeniden deneme ve gerçek kaynak birikimi kabulü açık. Canlı yayın yapılmadı.
+1 -1
View File
@@ -31,7 +31,7 @@ export const TenderDetailModal = () => {
e.preventDefault(); e.stopPropagation(); setActiveTenderDetail(null); return; e.preventDefault(); e.stopPropagation(); setActiveTenderDetail(null); return;
} }
if (e.key !== 'Tab') return; if (e.key !== 'Tab') return;
const focusable = [...dialogRef.current.querySelectorAll('button, a[href], input, select, textarea, [tabindex]')] const focusable = [...dialogRef.current.querySelectorAll('button, a[href], input, select, textarea, summary, [tabindex]')]
.filter(el => el.tabIndex >= 0 && !el.disabled && el.getClientRects().length && !el.closest('[inert]')); .filter(el => el.tabIndex >= 0 && !el.disabled && el.getClientRects().length && !el.closest('[inert]'));
const first = focusable[0], last = focusable.at(-1); const first = focusable[0], last = focusable.at(-1);
if (!first) { e.preventDefault(); dialogRef.current.focus(); } if (!first) { e.preventDefault(); dialogRef.current.focus(); }
+6 -1
View File
@@ -18,12 +18,17 @@ const probe=netServer();await new Promise(r=>probe.listen(0,'127.0.0.1',r));cons
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 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 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ı',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)})); 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ı',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 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)=>{ 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-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==='/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(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(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==='/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?.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;} 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(); next();
});}}]}); });}}]});
await ui.listen();console.log('QA_URL='+ui.resolvedUrls.local[0]+'qa-signin'); await ui.listen();console.log('QA_URL='+ui.resolvedUrls.local[0]+'qa-signin');