import {test} from 'node:test'; import assert from 'node:assert/strict'; import {uniqueTenders,announcementFields,officialDateFields,mergeAnnouncementFields} from '../server/tender-quality.mjs'; test('duplicate DT records merge without merging another institution or losing source identities',()=>{ const t={id:'dt-1',ikn:'26DT1693370',kurum:'Hastane'}; const out=uniqueTenders([t,{...t,id:'dt-2',aciklama:'Tam metin'},{...t,id:'dt-3',kurum:'Başka hastane'}]); assert.equal(out.length,2);assert.deepEqual(out[0].sourceIds,['dt-1','dt-2']);assert.equal(out[0].aciklama,'Tam metin'); }); test('distinct contract lots and corrections remain separate',()=>{ const t={ikn:'2026/123',durum:'Sonuçlandı',kisim:'1',id:'a',kazananFirma:'Firma'}; assert.equal(uniqueTenders([t,t,{...t,id:'b',kisim:'2'}]).length,2); assert.equal(uniqueTenders([{ikn:'2026/123',id:'1'},{ikn:'2026/123',id:'2',baslik:'Düzeltme ilanı'}]).length,2); }); test('result status whitespace is normalized without collapsing distinct lots',()=>{ const first={ikn:'2026/456',durum:' Sonuçlandı ',kisim:'1',id:'a',kazananFirma:'Firma'}; const same={...first,id:'b'}; const other={...first,id:'c',kisim:'2'}; const out=uniqueTenders([first,same,other]); assert.equal(out.length,2);assert.deepEqual(out[0].sourceIds,['a','b']); }); test('malformed source rows do not crash pool deduplication',()=>{ const out=uniqueTenders([null,42,{},[],{ikn:'2026/123',baslik:'Eksik kimlik'}]); assert.deepEqual(out,[]); }); test('announcement preserves full text and extracts source-backed fields',()=>{ const text='x'.repeat(4500)+' Tarih ve Saati : 10.09.2026 - 10:00 3.3. Teslim edileceği yer : Patoloji laboratuvarı 3.4. Süresi/teslim tarihi : 30 gün 3.5. İşe başlama tarihi : sözleşme'; const d=announcementFields(text);assert.equal(d.aciklama,text);assert.equal(d.saat,'10:00');assert.equal(d.teslimYeri,'Patoloji laboratuvarı');assert.equal(d.teslimatSuresi,'30 gün'); assert.deepEqual(announcementFields('Eksik ilan'),{aciklama:'Eksik ilan'}); }); // Shape observed in the real EKAP announcement for 2026/1431768. test('EKAP table separators do not hide the deadline or leak into delivery fields',()=>{ const text='2.1. Tarih ve Saati | : | 08.09.2026 - 11:00 | 2.2. Adres | : | İhale salonu | 3.3. Yapılacağı/teslim edileceği yer | : | Turgut Özal Tıp Merkezi Deposu-Malatya | 3.4. Süresi/teslim tarihi | : | Her siparişin tebliğinden itibaren 30 (otuz) takvim günü | 3.5. İşe başlama tarihi | : | Sözleşme tarihi'; const d=announcementFields(text); assert.equal(d.aciklama,text); assert.equal(d.tarih,'08.09.2026');assert.equal(d.saat,'11:00'); assert.equal(d.teslimYeri,'Turgut Özal Tıp Merkezi Deposu-Malatya'); assert.equal(d.teslimatSuresi,'Her siparişin tebliğinden itibaren 30 (otuz) takvim günü'); }); test('announcement deadline parser accepts common label variants',()=>{ const variants=[ 'İhale tarihi ve saati: 11.09.2026 - 09:30', 'Son Teklif Tarihi : 12.09.2026 14:15', 'İhale Tarihi/Saati : 13.09.2026 - 08:05', ]; for (const [i,text] of variants.entries()) { const d=announcementFields(text); assert.equal(d.tarih,`${String(11+i).padStart(2,'0')}.09.2026`); assert.equal(d.saat,['09:30','14:15','08:05'][i]); } }); test('official EKAP detail date takes precedence over stale announcement date',()=>{ const official=officialDateFields('16.09.2026 10:00'); assert.deepEqual(official,{tarih:'16.09.2026',saat:'10:00',sourceDateTime:'16.09.2026 10:00'}); const merged=mergeAnnouncementFields('2.1. Tarih ve Saati: 09.09.2026 - 10:00',official); assert.equal(merged.tarih,'16.09.2026'); assert.equal(merged.saat,'10:00'); assert.match(merged.sourceDateWarning,/Resmî ihale detay tarihi/); }); test('official date merge preserves announcement fields when dates agree',()=>{ const merged=mergeAnnouncementFields('2.1. Tarih ve Saati: 10.09.2026 - 10:00',officialDateFields('2026-09-10T10:00:00')); assert.equal(merged.tarih,'10.09.2026'); assert.equal(merged.saat,'10:00'); assert.equal(merged.sourceDateWarning,undefined); });