Files
ihaleist/tests/calendar.test.mjs
T

114 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createTenderICS, parseTenderICS, 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,''),/artan sürümle takvimi güncelle/);
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('ICS exposes a stable event version for calendar updates',()=>{
const first=createTenderICS({...t,documentVersion:3},new Date('2026-09-01T00:00:00Z'));
const changed=createTenderICS({...t,tarih:'12.09.2026',saat:'14:30',documentVersion:4},new Date('2026-09-02T00:00:00Z'));
assert.match(first,/SEQUENCE:3/); assert.match(changed,/SEQUENCE:4/);
assert.match(changed,/LAST-MODIFIED:20260902T000000Z/);
assert.equal(first.match(/UID:[^\r\n]+/)[0],changed.match(/UID:[^\r\n]+/)[0]);
assert.match(changed,/art%C4%B1?an|artan sürüm|artan/);
});
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);
});
test('ICS import preserves Istanbul deadline, source identity and escaped text', () => {
const ics=createTenderICS({ikn:'2026/42',baslik:'Mikroskop',kurum:'Giresun Hastanesi',tarih:'10.09.2026',saat:'10:00'});
const imported=parseTenderICS(ics);
assert.equal(imported.length,1);
assert.deepEqual(imported[0],{id:imported[0].id,ikn:'2026/42',baslik:'Mikroskop',kurum:'Giresun Hastanesi',sehir:'',tarih:'10.09.2026',saat:'10:00',durum:'Harici takvim',alimTuru:'',externalCalendar:true,icsSequence:0});
});
test('ICS import rejects malformed, oversized and unbounded event collections', () => {
assert.throws(() => parseTenderICS('BEGIN:VCALENDAR\nEND:VCALENDAR'), /etkinliği bulunamadı/);
assert.throws(() => parseTenderICS('x'.repeat(200001)), /çok büyük/);
const many=Array.from({length:101},(_,i)=>`BEGIN:VEVENT\nUID:${i}\nDTSTART:20260910T100000Z\nSUMMARY:E${i}\nEND:VEVENT`).join('\n');
assert.throws(() => parseTenderICS(many), /en fazla 100/);
assert.throws(() => parseTenderICS('BEGIN:VEVENT\nUID:x\nDTSTART:20260231T100000Z\nSUMMARY:Bozuk\nEND:VEVENT'), /tarih veya başlık/);
});