Surface shared inventory failures and isolate test database storage

This commit is contained in:
2026-09-06 10:30:51 +03:00
parent dbe43c241d
commit 0c16bbd96f
6 changed files with 48 additions and 10 deletions
+8
View File
@@ -579,3 +579,11 @@ Hazırlık formunun hazır hesabına doğrulanmış gelecek tarih/saat kontrolü
Tarih/saat ayrıştırması hatalı bitişik ekleri (ör. `10:0099`) geçerli saat kabul etmez. Mevcut Türkiye saat dilimi ve kaynak tarih doğrulaması kullanılır. Tarih/saat ayrıştırması hatalı bitişik ekleri (ör. `10:0099`) geçerli saat kabul etmez. Mevcut Türkiye saat dilimi ve kaynak tarih doğrulaması kullanılır.
Testler: son teklif anından bir milisaniye önce/sonra; bilinmeyen/geçersiz tarih ve saat; gelecekte tarih taşısa da iptal/sonuçlanmış kayıt. Tüm 127 test ve üretim derlemesi geçti. Gerçek tarayıcıda zamanı geçen uzun çıktı kabulü henüz yapılmadı. Büyük paket uyarıları sürüyor. Planın diğer işleri ve canlı yayın açık. Testler: son teklif anından bir milisaniye önce/sonra; bilinmeyen/geçersiz tarih ve saat; gelecekte tarih taşısa da iptal/sonuçlanmış kayıt. Tüm 127 test ve üretim derlemesi geçti. Gerçek tarayıcıda zamanı geçen uzun çıktı kabulü henüz yapılmadı. Büyük paket uyarıları sürüyor. Planın diğer işleri ve canlı yayın açık.
### D01 / D05 — Ortak kalem deposu hata ve test ayrımı
SQLite desteği olmayan ortamda ortak kalem deposu artık boş liste gibi görünmez; hata verir. Depo bağlantısı ancak WAL ve şema kurulumu başarılı olduktan sonra önbelleğe alınır. Başarısız açılışın bağlantısı kapatılır; sorun giderilince aynı süreçte yeniden deneme mümkündür. Kalem listeleme/arama endpointleri erişim sorununu anlaşılır 503 yanıtıyla döndürür.
`IHALEIST_SHARED_DATA_DIR` ile ortak kalem deposu dizini yapılandırılabilir. Tarayıcı QA ve HTTP test sunucuları ortak kalem verisini kendi geçici dizinlerinde tutacak şekilde güncellendi. Önceden başlatılmış test süreçleri yeniden başlatılmadı; mevcut veri dosyaları silinmedi. Diğer ortak veri depolarının tamamı bu değişiklikle izole edilmiş sayılmaz.
Test: bozuk gerçek SQLite dosyasıyla açılış başarısızlığı, dosyanın onarılmasından sonra aynı süreçte boş liste ve yeni kalem yazma/okuma. Tüm 128 test ve üretim derlemesi geçti. HTTP testinin yeni izole depo ayarı sonrasında dört HTTP kabul testi ayrıca geçti. Canlı sistemde yapay arıza oluşturulmadı. Büyük paket uyarıları ve planın kalan kapsamı açık; canlı yayın yapılmadı.
+4 -1
View File
@@ -775,13 +775,16 @@ const server = http.createServer(async (req, res) => {
if (url.pathname === '/api/kalem') { if (url.pathname === '/api/kalem') {
const ikn = url.searchParams.get('ikn'); const ikn = url.searchParams.get('ikn');
if (!ikn) return json(res, 400, { success: false, error: 'ikn gerekli' }); if (!ikn) return json(res, 400, { success: false, error: 'ikn gerekli' });
return json(res, 200, { success: true, ...kalemStore.kalemOku(ikn) }); try { return json(res, 200, { success: true, ...kalemStore.kalemOku(ikn) }); }
catch { return json(res,503,{success:false,error:'Kalem deposuna erişilemiyor. Daha sonra yeniden deneyin.'}); }
} }
if (url.pathname === '/api/kalem/ara') { if (url.pathname === '/api/kalem/ara') {
const q = url.searchParams.get('q') || ''; const q = url.searchParams.get('q') || '';
if (q.trim().length < 2) return json(res, 400, { success: false, error: 'en az 2 karakter' }); if (q.trim().length < 2) return json(res, 400, { success: false, error: 'en az 2 karakter' });
try {
const sonuc = kalemStore.kalemAra(q); const sonuc = kalemStore.kalemAra(q);
return json(res, 200, { success: true, adet: sonuc.length, sonuclar: sonuc }); return json(res, 200, { success: true, adet: sonuc.length, sonuclar: sonuc });
} catch { return json(res,503,{success:false,error:'Kalem araması şu anda kullanılamıyor. Yeniden deneyin.'}); }
} }
// --- Ürün araması: metni ihale DOKÜMANININ içinde arar --- // --- Ürün araması: metni ihale DOKÜMANININ içinde arar ---
// EKAP teklif cetvelini kendi tarafında indeksliyor; doküman indirmeye // EKAP teklif cetvelini kendi tarafında indeksliyor; doküman indirmeye
+8 -5
View File
@@ -23,7 +23,7 @@ try {
} }
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DATA_DIR = path.join(__dirname, 'data'); const DATA_DIR = process.env.IHALEIST_SHARED_DATA_DIR || path.join(__dirname, 'data');
const DB_PATH = path.join(DATA_DIR, 'kalem.sqlite'); const DB_PATH = path.join(DATA_DIR, 'kalem.sqlite');
const SCHEMA = ` const SCHEMA = `
@@ -56,11 +56,14 @@ export function available() {
function open() { function open() {
if (db) return db; if (db) return db;
if (!DatabaseSync) return null; if (!DatabaseSync) throw new Error('Kalem deposu bu sunucuda kullanılamıyor.');
fs.mkdirSync(DATA_DIR, { recursive: true }); fs.mkdirSync(DATA_DIR, { recursive: true });
db = new DatabaseSync(DB_PATH); const candidate = new DatabaseSync(DB_PATH);
db.exec('PRAGMA journal_mode = WAL;'); try {
db.exec(SCHEMA); candidate.exec('PRAGMA journal_mode = WAL;');
candidate.exec(SCHEMA);
db = candidate;
} catch (error) { candidate.close(); throw error; }
return db; return db;
} }
+1 -1
View File
@@ -12,7 +12,7 @@ const {signToken}=await import('../server/auth.mjs');
const user={email:'browser-qa@example.test',name:'Laboratuvar QA',isAdmin:false}; const user={email:'browser-qa@example.test',name:'Laboratuvar QA',isAdmin:false};
const token=signToken(user); const token=signToken(user);
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 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,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)}));
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)=>{
+1 -1
View File
@@ -19,7 +19,7 @@ const request=async(path,method='GET',body,auth=token)=>{
before(async()=>{ before(async()=>{
const socket=createServer();await new Promise(r=>socket.listen(0,'127.0.0.1',r));const port=socket.address().port;await new Promise(r=>socket.close(r)); const socket=createServer();await new Promise(r=>socket.listen(0,'127.0.0.1',r));const port=socket.address().port;await new Promise(r=>socket.close(r));
base='http://127.0.0.1:'+port; base='http://127.0.0.1:'+port;
child=spawn(process.execPath,['server/index.js'],{cwd:new URL('..',import.meta.url),env:{...process.env,NODE_ENV:'test',PORT:String(port),IHALEIST_USER_DATA_DIR:dir,UYUM_GOMME:'0'},stdio:'ignore'}); child=spawn(process.execPath,['server/index.js'],{cwd:new URL('..',import.meta.url),env:{...process.env,NODE_ENV:'test',PORT:String(port),IHALEIST_USER_DATA_DIR:dir,IHALEIST_SHARED_DATA_DIR:join(dir,'shared'),UYUM_GOMME:'0'},stdio:'ignore'});
for(let i=0;i<100;i++){try{await fetch(base+'/api/auth/google/config');return;}catch{await new Promise(r=>setTimeout(r,50));}} for(let i=0;i<100;i++){try{await fetch(base+'/api/auth/google/config');return;}catch{await new Promise(r=>setTimeout(r,50));}}
throw new Error('QA API did not start'); throw new Error('QA API did not start');
}); });
+24
View File
@@ -0,0 +1,24 @@
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {mkdtempSync,writeFileSync,rmSync,existsSync} from 'node:fs';
import {tmpdir} from 'node:os';
import {join} from 'node:path';
import {execFileSync} from 'node:child_process';
test('failed shared-store initialization is retryable after storage is repaired',()=>{
const dir=mkdtempSync(join(tmpdir(),'ihaleist-shared-recovery-'));
try {
writeFileSync(join(dir,'kalem.sqlite'),'not a sqlite database');
execFileSync(process.execPath,['--input-type=module','-e',`
import assert from 'node:assert/strict';
import {rmSync} from 'node:fs';
import {join} from 'node:path';
const store=await import('./server/kalem-store.mjs');
assert.throws(()=>store.kalemOku('2026/123'));
rmSync(join(process.env.IHALEIST_SHARED_DATA_DIR,'kalem.sqlite'));
assert.deepEqual(store.kalemOku('2026/123'),{kalemler:[],kaynak:null});
assert.equal(store.kalemYaz('2026/123',[{ad:'QA tüp',miktar:2,birim:'adet'}]),1);
assert.equal(store.kalemOku('2026/123').kalemler[0].ad,'QA tüp');
`],{cwd:new URL('..',import.meta.url),env:{...process.env,IHALEIST_SHARED_DATA_DIR:dir},stdio:'pipe'});
assert.equal(existsSync(join(dir,'kalem.sqlite')),true);
} finally {rmSync(dir,{recursive:true,force:true});}
});