== Query parameter extraction == PASS scalar token extracted PASS array-valued token[] rejected as null PASS missing key returns null PASS array-valued token never reaches token validator == Token and base validation == PASS valid token accepted PASS short token rejected PASS token with space rejected PASS token with slash rejected PASS 129-char token rejected PASS 128-char token accepted PASS valid base business PASS unknown base rejected PASS empty base rejected == Exact ICS URL construction == PASS exact export URL PASS invalid token throws in URL builder == Legacy ICS URL validation == PASS valid legacy URL accepted PASS http scheme rejected PASS credentials rejected PASS custom port rejected PASS unknown host rejected (redirect target) PASS wrong path rejected PASS extra query rejected PASS missing export query rejected PASS fragment rejected == Strict date parsing == PASS date-only parsed as all-day PASS date-only starts at midnight PASS invalid Feb 31 is a controlled failure (null) PASS unspecified time does not inherit server clock == Escaped text, unfolding, duplicate properties == PASS calendar name de-identified PASS escaped comma+semicolon decoded PASS escaped backslash decoded PASS no literal backslash-n remains PASS folded line unfolded PASS duplicate SUMMARY collapses to a string == Visible range end (final Sunday 23:59:59) == PASS Feb 2026 range ends 23:59:59 on final Sunday PASS timed event late on final Sunday is included == Stable within-day ordering == PASS order = all-day, 09:00 by title, 14:00 == Timezone handling == PASS UTC 23:30Z crosses to Jan 2 in Perth PASS all-day 2026-02-15 not shifted (Perth) PASS all-day 2026-02-15 not shifted (Sydney) PASS Sydney DST-fallback event lands on 2026-04-05 PASS Perth event shows 09:00 local in Perth PASS multi-TZID fixture keeps the env display timezone == Event-TZID inference (no X-WR-TIMEZONE) == PASS feed really has no X-WR-TIMEZONE PASS single event TZID is inferred PASS VTIMEZONE DTSTART lines are not mistaken for event TZIDs PASS a DTSTART outside any component is ignored PASS quoted TZID params are unquoted PASS an empty TZID param is ignored, not a failure PASS DTEND timezone does not make inference ambiguous PASS a DTEND-only timezone infers nothing PASS EXDATE/RDATE/RECURRENCE-ID timezones infer nothing PASS VALUE=DATE DTSTART infers nothing PASS VALUE=DATE DTSTART with a TZID still infers nothing PASS a malformed date-only DTSTART with a TZID infers nothing PASS an all-day DTSTART does not veto a timed one PASS repeated matching timed DTSTART TZIDs infer PASS two different event TZIDs are ambiguous PASS order does not matter for ambiguity PASS an invalid TZID alone fails closed PASS a valid TZID followed by an invalid one fails closed PASS an invalid TZID before a valid one fails closed PASS a quoted invalid TZID fails closed PASS no TZID anywhere infers nothing PASS display timezone is Australia/Perth PASS "Have a go day" lands on 2026-08-09 PASS "Have a go day" shows 14:00, not 06:00 PASS "Have a go day" ends 16:00, not 08:00 PASS "Have a go day" instant is still 06:00Z PASS event meta reads 14:00 - 16:00 PASS evening event stays on its own day (19:00) PASS all-day event is not shifted by the inferred timezone PASS all-day event still reads "All day" PASS X-WR-TIMEZONE outranks an inferred event TZID PASS inferred event TZID outranks the env default PASS env default still applies with no TZID to infer PASS env default still applies to an ambiguous feed PASS env default still applies to a DTEND-only timezone PASS env default still applies when a timed TZID is invalid PASS env default still applies when the only TZID is invalid PASS env default still applies to an all-day-only calendar PASS UTC remains the last resort PASS UTC is the last resort when a timed TZID is invalid PASS a Perth event keeps its 06:00Z instant under a UTC display clock PASS a Sydney event keeps its 04:00Z instant under a UTC display clock PASS display clock follows DTSTART for a split-zone event PASS a differently zoned DTEND still parses in its own zone == Recurrence == PASS weekly honours EXDATE (02-11 removed) and RDATE (02-20 added) PASS daily COUNT=3 PASS monthly on the 31st is DEGRADED: Jan 31 only, nothing stepped PASS monthly never clamps to Feb 28 PASS monthly never produces a March-2 overflow PASS monthly never substitutes the skip-schedule (Mar 31 / May 31) PASS the parsed month-end component reports recurrenceResolution=degraded_unsupported PASS supported fixture series report recurrenceResolution=supported PASS a one-off fixture component reports recurrenceResolution=not_recurring PASS Sydney daily keeps 09:00 wall-clock across DST == Event limit (no silent truncation) == PASS default max-events helper honours the env override PASS calendar below the limit parses completely PASS calendar exactly at the limit parses completely PASS calendar above the limit throws too_large PASS parser returns no partial result when over the limit PASS 5001-event calendar parses completely (no silent 5000 cut) == Cache separation and atomic generation == PASS cache key varies by base PASS cache key varies by token PASS cache key is a sha-256 hex PASS cache available under temp dir PASS cache put ok PASS cache round-trips content PASS different base is a separate entry PASS atomic write leaves no temp files PASS corrupt entry rejected as a miss PASS negative entry stored + read All calendar-data self-tests passed.