diff --git a/tests/test_gwr.py b/tests/test_gwr.py
new file mode 100644
index 0000000..03cd342
--- /dev/null
+++ b/tests/test_gwr.py
@@ -0,0 +1,208 @@
+"""Tests for agenda.gwr module."""
+
+import os
+import tempfile
+from datetime import date
+from typing import Any
+from unittest.mock import AsyncMock, patch
+
+import pytest
+
+from agenda.gwr import (
+ advance_ticket_date,
+ advance_tickets_page_html,
+ extract_dates,
+ extract_weekday_date,
+ parse_date_string,
+)
+
+
+class TestParseDateString:
+ """Tests for parse_date_string function."""
+
+ def test_parse_date_with_year(self) -> None:
+ """Test parsing date string with year included."""
+ result = parse_date_string("Monday 25 December 2023")
+ assert result == date(2023, 12, 25)
+
+ def test_parse_date_without_year(self) -> None:
+ """Test parsing date string without year (should use current year)."""
+ current_year = date.today().year
+ result = parse_date_string("Monday 25 December")
+ assert result == date(current_year, 12, 25)
+
+ def test_parse_different_formats(self) -> None:
+ """Test parsing various date formats."""
+ result = parse_date_string("Friday 1 January 2024")
+ assert result == date(2024, 1, 1)
+
+ result = parse_date_string("Saturday 29 February 2024")
+ assert result == date(2024, 2, 29)
+
+
+class TestExtractDates:
+ """Tests for extract_dates function."""
+
+ def test_extract_valid_dates(self) -> None:
+ """Test extracting dates from valid HTML."""
+ html = """
+
Weekdays | Monday 25 December 2023 |
+ Saturdays | Saturday 23 December 2023 |
+ Sundays | Sunday 24 December 2023 |
+ """
+ result = extract_dates(html)
+ expected = {
+ "Weekdays": date(2023, 12, 25),
+ "Saturdays": date(2023, 12, 23),
+ "Sundays": date(2023, 12, 24),
+ }
+ assert result == expected
+
+ def test_extract_dates_with_asterisks(self) -> None:
+ """Test extracting dates when HTML contains asterisks."""
+ html = """
+ Weekdays | Monday 25 December 2023** |
+ """
+ result = extract_dates(html)
+ expected = {"Weekdays": date(2023, 12, 25)}
+ assert result == expected
+
+ def test_extract_dates_no_match(self) -> None:
+ """Test extracting dates when no pattern matches."""
+ html = "No relevant table data here
"
+ result = extract_dates(html)
+ assert result is None
+
+ def test_extract_dates_whitespace_handling(self) -> None:
+ """Test extracting dates with various whitespace."""
+ html = """
+ Weekdays | Monday 25 December 2023 |
+ """
+ result = extract_dates(html)
+ expected = {"Weekdays": date(2023, 12, 25)}
+ assert result == expected
+
+
+class TestExtractWeekdayDate:
+ """Tests for extract_weekday_date function."""
+
+ def test_extract_valid_weekday_date(self) -> None:
+ """Test extracting weekday date from valid HTML."""
+ html = """
+ Weekdays | Monday 25 December 2023 |
+ """
+ result = extract_weekday_date(html)
+ assert result == date(2023, 12, 25)
+
+ def test_extract_weekday_date_with_asterisks(self) -> None:
+ """Test extracting weekday date when HTML contains asterisks."""
+ html = """
+ Weekdays | Monday 25 December 2023** |
+ """
+ result = extract_weekday_date(html)
+ assert result == date(2023, 12, 25)
+
+ def test_extract_weekday_date_no_match(self) -> None:
+ """Test extracting weekday date when no pattern matches."""
+ html = "No weekday data here
"
+ result = extract_weekday_date(html)
+ assert result is None
+
+ def test_extract_weekday_date_multiline(self) -> None:
+ """Test extracting weekday date with multiline content."""
+ html = """
+
+ Weekdays |
+ Monday 25 December 2023 |
+
+ """
+ result = extract_weekday_date(html)
+ assert result == date(2023, 12, 25)
+
+
+class TestAdvanceTicketsPageHtml:
+ """Tests for advance_tickets_page_html function."""
+
+ @pytest.mark.asyncio
+ async def test_cache_hit(self) -> None:
+ """Test using cached HTML when file is fresh."""
+ with tempfile.TemporaryDirectory() as temp_dir:
+ cache_file = os.path.join(temp_dir, "advance-tickets.html")
+ test_content = "test content"
+
+ # Create a fresh cache file
+ with open(cache_file, "w") as f:
+ f.write(test_content)
+
+ result = await advance_tickets_page_html(temp_dir, ttl=3600)
+ assert result == test_content
+
+ @pytest.mark.asyncio
+ async def test_force_cache(self) -> None:
+ """Test forcing cache usage even when file doesn't exist."""
+ with tempfile.TemporaryDirectory() as temp_dir:
+ cache_file = os.path.join(temp_dir, "advance-tickets.html")
+ test_content = "cached content"
+
+ with open(cache_file, "w") as f:
+ f.write(test_content)
+
+ result = await advance_tickets_page_html(temp_dir, force_cache=True)
+ assert result == test_content
+
+ @pytest.mark.asyncio
+ @patch("httpx.AsyncClient")
+ async def test_fetch_from_web(self, mock_client: Any) -> None:
+ """Test fetching from web when cache is stale."""
+ mock_response = AsyncMock()
+ mock_response.text = "fresh content"
+ mock_client.return_value.__aenter__.return_value.get.return_value = (
+ mock_response
+ )
+
+ with tempfile.TemporaryDirectory() as temp_dir:
+ result = await advance_tickets_page_html(temp_dir, ttl=0)
+ assert result == "fresh content"
+
+ # Check that content was cached
+ cache_file = os.path.join(temp_dir, "advance-tickets.html")
+ with open(cache_file) as f:
+ cached_content = f.read()
+ assert cached_content == "fresh content"
+
+
+class TestAdvanceTicketDate:
+ """Tests for advance_ticket_date function."""
+
+ @pytest.mark.asyncio
+ @patch("agenda.gwr.advance_tickets_page_html")
+ async def test_advance_ticket_date_success(self, mock_html: Any) -> None:
+ """Test successfully extracting advance ticket date."""
+ mock_html.return_value = """
+ Weekdays | Monday 25 December 2023 |
+ """
+
+ result = await advance_ticket_date("/fake/dir")
+ assert result == date(2023, 12, 25)
+ mock_html.assert_called_once_with("/fake/dir", force_cache=False)
+
+ @pytest.mark.asyncio
+ @patch("agenda.gwr.advance_tickets_page_html")
+ async def test_advance_ticket_date_no_match(self, mock_html: Any) -> None:
+ """Test when no weekday date can be extracted."""
+ mock_html.return_value = "No relevant data
"
+
+ result = await advance_ticket_date("/fake/dir")
+ assert result is None
+
+ @pytest.mark.asyncio
+ @patch("agenda.gwr.advance_tickets_page_html")
+ async def test_advance_ticket_date_force_cache(self, mock_html: Any) -> None:
+ """Test advance_ticket_date with force_cache parameter."""
+ mock_html.return_value = """
+ Weekdays | Tuesday 26 December 2023 |
+ """
+
+ result = await advance_ticket_date("/fake/dir", force_cache=True)
+ assert result == date(2023, 12, 26)
+ mock_html.assert_called_once_with("/fake/dir", force_cache=True)