"""Tests for the Airbnb calendar price automation module.""" from datetime import date from unittest.mock import MagicMock, patch from playwright.sync_api import TimeoutError as PlaywrightTimeout from src.airbnb.calendar import ( _MAX_UPDATE_ATTEMPTS, DEFAULT_HOST_ORIGIN, parse_month_heading_text, resolve_calendar_url, update_price, ) CALENDAR_URL = f"{DEFAULT_HOST_ORIGIN}/multicalendar/12345" class TestResolveCalendarUrl: def test_override_wins(self): url = resolve_calendar_url("12345", "https://www.airbnb.ca/multicalendar/999/") assert url == "https://www.airbnb.ca/multicalendar/999" def test_listing_id_builds_multicalendar_url(self): assert resolve_calendar_url("12345", "") == f"{DEFAULT_HOST_ORIGIN}/multicalendar/12345" def test_no_listing_id_falls_back_to_hosting_calendar(self): assert resolve_calendar_url("", "") == f"{DEFAULT_HOST_ORIGIN}/hosting/calendar" class TestParseMonthHeadingText: def test_parses_month_and_year(self): assert parse_month_heading_text("April 2026") == (2026, 4) def test_parses_with_surrounding_text(self): assert parse_month_heading_text(" December 2025 ") == (2025, 12) def test_rejects_garbage(self): assert parse_month_heading_text("not a month heading") is None class TestUpdatePrice: def test_successful_price_update(self): page = MagicMock() with patch("src.airbnb.calendar._run_price_update_attempt") as attempt: result = update_price(page, date(2026, 5, 10), 180, CALENDAR_URL) assert result is True attempt.assert_called_once_with(page, date(2026, 5, 10), 180, CALENDAR_URL) def test_retries_on_timeout(self): page = MagicMock() with ( patch( "src.airbnb.calendar._run_price_update_attempt", side_effect=PlaywrightTimeout("timed out"), ) as attempt, patch("src.airbnb.calendar.time.sleep"), ): result = update_price(page, date(2026, 5, 10), 180, CALENDAR_URL) assert result is False assert attempt.call_count == _MAX_UPDATE_ATTEMPTS def test_retries_on_generic_exception(self): page = MagicMock() with ( patch( "src.airbnb.calendar._run_price_update_attempt", side_effect=RuntimeError("unexpected"), ) as attempt, patch("src.airbnb.calendar.time.sleep"), ): result = update_price(page, date(2026, 5, 10), 180, CALENDAR_URL) assert result is False assert attempt.call_count == _MAX_UPDATE_ATTEMPTS def test_succeeds_on_second_attempt(self): page = MagicMock() with ( patch( "src.airbnb.calendar._run_price_update_attempt", side_effect=[PlaywrightTimeout("first fail"), None], ) as attempt, patch("src.airbnb.calendar.time.sleep"), ): result = update_price(page, date(2026, 5, 10), 180, CALENDAR_URL) assert result is True assert attempt.call_count == 2