from datetime import datetime from zoneinfo import ZoneInfo import pytest from app.crawler import _parse_date, _parse_date_range, _weekly_dates, external_key, ExtractedEvent NOW = datetime(2026, 9, 7, 12, 0, tzinfo=ZoneInfo("Australia/Sydney")) SOURCE = {"timezone": "Australia/Sydney", "date_format": ""} def test_parses_naive_australian_date_with_timezone(): value = _parse_date("7 September 2026 7:30pm", SOURCE, NOW) assert value.isoformat() == "2026-09-07T19:30:00+10:00" def test_parses_explicit_format_without_year_to_next_occurrence(): value = _parse_date("4 Sep 19:30", {**SOURCE, "date_format": "%d %b %H:%M"}, NOW) assert value.year == 2027 @pytest.mark.parametrize("separator", ["-", "–", "—"]) def test_same_month_range(separator): start, end = _parse_date_range(f"2 {separator} 13 Sep 2026", SOURCE, NOW) assert (start.year, start.month, start.day) == (2026, 9, 2) assert (end.year, end.month, end.day) == (2026, 9, 13) def test_cross_month_range(): start, end = _parse_date_range("30 Sep - 4 Oct 2026", SOURCE, NOW) assert (start.month, start.day) == (9, 30) assert (end.month, end.day) == (10, 4) def test_non_range_has_no_end(): start, end = _parse_date_range("9 September 2026 8pm", SOURCE, NOW) assert start.hour == 20 assert end is None def test_rejects_relative_only_and_implausible_dates(): with pytest.raises(ValueError): _parse_date("next Friday", SOURCE, NOW) with pytest.raises(ValueError): _parse_date("13 Sep 2002", SOURCE, NOW) def test_weekly_dates_generate_twelve_occurrences(): values = _weekly_dates("Every Friday", NOW) assert len(values) == 12 assert values[0].weekday() == 4 assert all((right - left).days == 7 for left, right in zip(values, values[1:])) def test_external_key_is_case_insensitive_but_time_sensitive(): first = ExtractedEvent("Trivia", NOW, address="Town Hall") same = ExtractedEvent("TRIVIA", NOW, address="TOWN HALL") later = ExtractedEvent("Trivia", NOW.replace(hour=13), address="Town Hall") assert external_key(first) == external_key(same) assert external_key(first) != external_key(later)