diff --git a/apps/api/tests/broker/vevent.test.ts b/apps/api/tests/broker/vevent.test.ts index e94dec7..529bb44 100644 --- a/apps/api/tests/broker/vevent.test.ts +++ b/apps/api/tests/broker/vevent.test.ts @@ -464,3 +464,43 @@ describe('computeAlertInstantUtc', () => { expect(result.toISOString()).toBe('2026-11-01T14:00:00.000Z'); }); }); + +// ─── Phase 11 Plan 05 WR-01: positive-duration TRIGGER classifies as custom ── + +describe('classifyValarms — WR-01: positive-duration trigger (fires after event)', () => { + // RFC 5545 allows TRIGGER:+PT15M — fires 15 min AFTER event start. + // Before the fix: Math.abs() discards the sign and classifies it as preset/offlist 15. + // After the fix: positive duration → {kind:'custom'} (preserve as-is). + + it('WR-01: TRIGGER:+PT15M classifies as custom, NOT preset', () => { + // TRIGGER:+PT15M is a post-event alarm; must not be mis-read as a 15-min-before lead. + // This test MUST FAIL before the fix. + const ics = makeIcs( + 'BEGIN:VALARM\r\nTRIGGER;RELATED=END:PT15M\r\nACTION:DISPLAY\r\nDESCRIPTION:Reminder\r\nEND:VALARM', + ); + expect(classifyValarms(ics)).toEqual({ kind: 'custom' }); + }); + + it('WR-01: TRIGGER:+PT30M classifies as custom, NOT offlist', () => { + // 30 min is in PRESET_MINUTES — without the sign check, Math.abs would give preset/30. + const ics = makeIcs( + 'BEGIN:VALARM\r\nTRIGGER:PT30M\r\nACTION:DISPLAY\r\nDESCRIPTION:Reminder\r\nEND:VALARM', + ); + expect(classifyValarms(ics)).toEqual({ kind: 'custom' }); + }); + + it('WR-01: negative TRIGGER:-PT15M still classifies as preset (unchanged behavior)', () => { + // Regression guard: negative triggers must not be affected by the fix. + const ics = makeIcs( + 'BEGIN:VALARM\r\nTRIGGER:-PT15M\r\nACTION:DISPLAY\r\nDESCRIPTION:Reminder\r\nEND:VALARM', + ); + expect(classifyValarms(ics)).toEqual({ kind: 'preset', leadMinutes: 15 }); + }); + + it('WR-01: negative off-list TRIGGER:-PT45M still classifies as offlist', () => { + const ics = makeIcs( + 'BEGIN:VALARM\r\nTRIGGER:-PT45M\r\nACTION:DISPLAY\r\nDESCRIPTION:Reminder\r\nEND:VALARM', + ); + expect(classifyValarms(ics)).toEqual({ kind: 'offlist', leadMinutes: 45 }); + }); +});