91 lines
5.0 KiB
JavaScript
91 lines
5.0 KiB
JavaScript
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);
|
||
});
|