Files
AtAnyRate/tests/airbnb/test_calendar.py
T
ilia a08839961b Make CI ruff/pytest gates hard, fix stale tests
Removes || true from the ruff and pytest steps (job names unchanged so branch protection contexts still match), adds [tool.ruff] config (E4/E7/E9/F/I, line-length 120), applies ruff autofixes, and rewrites tests that had drifted from the current calendar/auth/main APIs — failures the soft gates had been hiding.
2026-07-26 15:42:40 -04:00

92 lines
3.1 KiB
Python

"""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