import { test } from 'node:test'; import assert from 'node:assert/strict'; import { buildEventLines, buildCalendar, escapeText, foldLine, eventUid } from '../server/ics.js'; test('escapeText maskiert Sonderzeichen', () => { assert.equal(escapeText('a,b;c\\d\ne'), 'a\\,b\\;c\\\\d\\ne'); }); test('foldLine faltet lange Zeilen nach 75 Oktetten', () => { const long = 'X'.repeat(200); const folded = foldLine(long); const lines = folded.split('\r\n'); assert.ok(lines.length > 1); assert.ok(lines.every((l) => Buffer.byteLength(l) <= 75)); assert.ok(lines.slice(1).every((l) => l.startsWith(' '))); assert.equal(lines.map((l, i) => (i ? l.slice(1) : l)).join(''), long); // Umlaute werden nicht zerschnitten const umlaut = foldLine('Ä'.repeat(100)); assert.equal(umlaut.split('\r\n').map((l, i) => (i ? l.slice(1) : l)).join(''), 'Ä'.repeat(100)); }); test('Termin mit Uhrzeit und Zeitzone', () => { const lines = buildEventLines({ uid: 'u1', date: '2026-10-05', summary: 'Morning Meeting: A / B', time: '09:00', durationMinutes: 15, sequence: 2, now: new Date('2026-09-15T10:00:00Z') }); assert.ok(lines.includes('DTSTART;TZID=Europe/Berlin:20261005T090000')); assert.ok(lines.includes('DTEND;TZID=Europe/Berlin:20261005T091500')); assert.ok(lines.includes('SEQUENCE:2')); assert.ok(lines.includes('DTSTAMP:20260915T100000Z')); }); test('ganztägiger Termin ohne Uhrzeit', () => { const lines = buildEventLines({ uid: 'u1', date: '2026-10-30', summary: 'x', time: '' }); assert.ok(lines.includes('DTSTART;VALUE=DATE:20261030')); assert.ok(lines.includes('DTEND;VALUE=DATE:20261031')); }); test('Kalender ist wohlgeformt, ohne METHOD für CalDAV, mit ORGANIZER vor Teilnehmern', () => { const ev = buildEventLines({ uid: eventUid('2026-10-05'), date: '2026-10-05', summary: 'S', time: '09:00', attendees: [{ name: 'Max Mustermann', email: 'max@example.de' }], organizer: { name: 'Rotatory', email: 'team@example.de' } }); const ics = buildCalendar([ev]); assert.ok(!ics.includes('METHOD:'), 'CalDAV-Objekte dürfen keine METHOD tragen'); assert.ok(buildCalendar([ev], { method: 'PUBLISH' }).includes('METHOD:PUBLISH')); assert.ok(ics.includes('ORGANIZER;CN="Rotatory":mailto:team@example.de')); assert.ok(ics.indexOf('ORGANIZER') < ics.indexOf('ATTENDEE')); const noOrganizer = buildCalendar([buildEventLines({ uid: 'x', date: '2026-10-05', summary: 'S', attendees: [{ name: 'A', email: 'a@example.de' }] })]); assert.ok(!noOrganizer.includes('ATTENDEE'), 'ohne ORGANIZER keine Teilnehmer'); assert.ok(ics.startsWith('BEGIN:VCALENDAR\r\n')); assert.ok(ics.endsWith('END:VCALENDAR\r\n')); assert.ok(ics.includes('BEGIN:VTIMEZONE')); assert.ok(ics.includes('UID:rotatory-2026-10-05@rotatory.local')); assert.ok(ics.includes('ATTENDEE;CN="Max Mustermann"')); assert.ok(ics.split('\r\n').every((l) => Buffer.byteLength(l) <= 75)); });