import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createTenderICS, tenderDeadline, deadlineInfo, calendarMonthDays, istanbulDateKey } from '../src/services/tenderCalendar.js'; const t = {ikn:'2026/1582991',baslik:'Mikroskop',kurum:'Giresun',tarih:'10.09.2026',saat:'',aciklama:'Tarih ve Saati : 10.09.2026 - 10:00'}; test('Giresun date is September 10 at 10:00 Turkey, not hard-coded August', () => { assert.equal(tenderDeadline(t).toISOString(),'2026-09-10T07:00:00.000Z'); assert.match(createTenderICS(t), /DTSTART:20260910T070000Z/); }); test('all five tender deadlines and year rollover', () => { for (const [date,time,expected] of [ ['09.09.2026','10:00','2026-09-09T07:00'],['11.09.2026','10:00','2026-09-11T07:00'], ['22.09.2026','10:30','2026-09-22T07:30'],['22.09.2026','11:00','2026-09-22T08:00'], ['01.01.2027','01:00','2026-12-31T22:00'],['29.02.2028','10:00','2028-02-29T07:00']]) assert.ok(tenderDeadline({...t,tarih:date,saat:time}).toISOString().startsWith(expected)); }); test('missing time, impossible date, invalid time are rejected', () => { for (const patch of [{saat:'',aciklama:''},{tarih:'31.02.2026',saat:'10:00'},{saat:'25:00'}]) assert.throws(() => tenderDeadline({...t,...patch})); }); test('ICS escapes text, folds UTF-8 lines, keeps stable UID', () => { const ics = createTenderICS({...t,baslik:'Ş'.repeat(100)+'\nEND:VEVENT;,'}); assert.ok(ics.split('\r\n').every(l => Buffer.byteLength(l) <= 75)); assert.equal(ics.split('UID:')[1].split('\r\n')[0],'2026%2F1582991@ihaleist.com'); assert.ok(ics.includes('\\nEND:VEVENT\\;\\,')); }); test('all countdowns use Istanbul calendar days and exact expiry', () => { const now = new Date('2026-09-06T08:00:00Z'); assert.equal(deadlineInfo(t,now).days,4); assert.equal(deadlineInfo(t,now).label,'4 gün'); assert.equal(deadlineInfo(t,new Date('2026-09-10T06:59:00Z')).expired,false); assert.equal(deadlineInfo(t,new Date('2026-09-10T07:00:00Z')).expired,true); assert.equal(istanbulDateKey(new Date('2026-12-31T22:00:00Z')),'2027-01-01'); }); test('missing time stays unknown today, impossible date is not counted', () => { const now=new Date('2026-09-10T10:00:00Z'); const info=deadlineInfo({tarih:'10.09.2026'},now); assert.equal(info.expired,null);assert.equal(info.deadline,null); assert.match(info.label,/saat doğrulanmalı/); assert.equal(deadlineInfo({tarih:'31.02.2026'},now).days,null); assert.equal(deadlineInfo({tarih:'09.09.2026'},now).expired,true); }); test('month view includes late-month tenders and leap days', () => { assert.equal(calendarMonthDays('2026-09').length,30); assert.ok(calendarMonthDays('2026-09').some(d=>d.date==='22.09.2026')); assert.equal(calendarMonthDays('2028-02').length,29); assert.equal(calendarMonthDays('2027-02').length,28); assert.deepEqual(calendarMonthDays('2026-13'),[]); }); test('deadline ICS is a transparent instant, preserves UID across date changes and states update limits',()=>{ const first=createTenderICS(t,new Date('2026-09-01T00:00:00Z')); const changed=createTenderICS({...t,tarih:'12.09.2026',saat:'14:30'},new Date('2026-09-02T00:00:00Z')); assert.equal(first.match(/^UID:.+$/m)[0],changed.match(/^UID:.+$/m)[0]); assert.match(changed,/DTSTART:20260912T113000Z/); assert.doesNotMatch(changed,/DTEND:|DURATION:/); assert.match(changed,/TRANSP:TRANSPARENT/); assert.match(changed.replace(/\r\n /g,''),/otomatik güncellenmez/); const hostile=createTenderICS({...t,baslik:'A\rEND:VEVENT\rBEGIN:VEVENT\nB'}); assert.equal(hostile.split('\r\n').filter(line=>line==='BEGIN:VEVENT').length,1); assert.equal(hostile.split('\r\n').filter(line=>line==='END:VEVENT').length,1); }); test('calendar agenda sorts known times before unknown and preserves undated records',async()=>{ const {calendarAgenda}=await import('../src/services/tenderCalendar.js'); const now=new Date('2026-09-06T08:00:00Z'); const records=[ {id:'late',tarih:'10.09.2026',saat:'16:00'}, {id:'unknown',tarih:'10.09.2026'}, {id:'early',tarih:'10.09.2026',saat:'09:00'}, {id:'bad',tarih:'31.02.2026'}, {id:'missing'}, {id:'old',tarih:'01.09.2026',saat:'09:00',durum:'İptal'}, ]; const agenda=calendarAgenda(records,now); assert.deepEqual(agenda.days.get('2026-09-10').map(e=>e.tender.id),['early','late','unknown']); assert.deepEqual(agenda.undated.map(e=>e.tender.id),['bad','missing']); assert.equal(agenda.days.get('2026-09-01')[0].info.expired,true); assert.equal([...agenda.days.values()].flat().length+agenda.undated.length,records.length); assert.equal(records[0].id,'late'); }); test('calendar month navigation crosses year boundaries without invalid months',async()=>{ const {shiftCalendarMonth}=await import('../src/services/tenderCalendar.js'); assert.equal(shiftCalendarMonth('2026-12',1),'2027-01'); assert.equal(shiftCalendarMonth('2026-01',-1),'2025-12'); assert.equal(shiftCalendarMonth('2024-02',1),'2024-03'); assert.equal(shiftCalendarMonth('9999-12',1),null); assert.equal(shiftCalendarMonth('0001-01',-1),null); assert.equal(shiftCalendarMonth('2026-13',1),null); });