Tests cover providers, dedup, Telegram, scoring, main runner, and Airbnb stubs. Ticketmaster and SeatGeek use configurable lat/lon/radius (Thornhill default). Pipeline filters noise listings, merges same-day sports duplicates, optional MIN_ALERT_SCORE, and Telegram severity summary. Made-with: Cursor
100 lines
3.0 KiB
Python
100 lines
3.0 KiB
Python
"""Tests for the Airbnb calendar price automation module."""
|
|
|
|
from datetime import date
|
|
from unittest.mock import MagicMock, call, patch
|
|
|
|
import pytest
|
|
|
|
from src.airbnb.calendar import (
|
|
update_price,
|
|
_navigate_to_month,
|
|
CALENDAR_URL,
|
|
SELECTORS,
|
|
_MAX_UPDATE_ATTEMPTS,
|
|
)
|
|
|
|
|
|
class TestUpdatePrice:
|
|
def _make_page(self, *, fail_on_click: bool = False) -> MagicMock:
|
|
page = MagicMock()
|
|
if fail_on_click:
|
|
from playwright.sync_api import TimeoutError as PlaywrightTimeout
|
|
page.click.side_effect = PlaywrightTimeout("timed out")
|
|
return page
|
|
|
|
def test_successful_price_update(self):
|
|
page = self._make_page()
|
|
result = update_price(page, date(2026, 5, 10), 180)
|
|
|
|
assert result is True
|
|
page.goto.assert_called_once()
|
|
assert "2026-05-10" in str(page.click.call_args_list[0])
|
|
page.fill.assert_called_once()
|
|
|
|
def test_navigates_to_calendar_url(self):
|
|
page = self._make_page()
|
|
update_price(page, date(2026, 5, 10), 180)
|
|
|
|
page.goto.assert_called_once_with(
|
|
CALENDAR_URL, wait_until="networkidle", timeout=30_000
|
|
)
|
|
|
|
def test_fills_correct_price(self):
|
|
page = self._make_page()
|
|
update_price(page, date(2026, 5, 10), 200)
|
|
|
|
page.fill.assert_called_once_with(
|
|
SELECTORS["price_input"], "200", timeout=5_000
|
|
)
|
|
|
|
def test_retries_on_timeout(self):
|
|
from playwright.sync_api import TimeoutError as PlaywrightTimeout
|
|
|
|
page = MagicMock()
|
|
page.goto.side_effect = PlaywrightTimeout("timed out")
|
|
|
|
with patch("src.airbnb.calendar.time.sleep"):
|
|
result = update_price(page, date(2026, 5, 10), 180)
|
|
|
|
assert result is False
|
|
assert page.goto.call_count == _MAX_UPDATE_ATTEMPTS
|
|
|
|
def test_retries_on_generic_exception(self):
|
|
page = MagicMock()
|
|
page.goto.side_effect = RuntimeError("unexpected")
|
|
|
|
with patch("src.airbnb.calendar.time.sleep"):
|
|
result = update_price(page, date(2026, 5, 10), 180)
|
|
|
|
assert result is False
|
|
assert page.goto.call_count == _MAX_UPDATE_ATTEMPTS
|
|
|
|
def test_succeeds_on_second_attempt(self):
|
|
from playwright.sync_api import TimeoutError as PlaywrightTimeout
|
|
|
|
page = MagicMock()
|
|
page.goto.side_effect = [PlaywrightTimeout("first fail"), None]
|
|
|
|
with patch("src.airbnb.calendar.time.sleep"):
|
|
result = update_price(page, date(2026, 5, 10), 180)
|
|
|
|
assert result is True
|
|
assert page.goto.call_count == 2
|
|
|
|
|
|
class TestNavigateToMonth:
|
|
def test_stub_does_not_crash(self):
|
|
page = MagicMock()
|
|
_navigate_to_month(page, date(2026, 5, 10))
|
|
|
|
|
|
class TestSelectors:
|
|
def test_date_cell_selector_uses_date_format(self):
|
|
sel = SELECTORS["date_cell"].format(date_str="2026-05-10")
|
|
assert "2026-05-10" in sel
|
|
|
|
def test_all_selectors_defined(self):
|
|
assert "date_cell" in SELECTORS
|
|
assert "price_input" in SELECTORS
|
|
assert "save_button" in SELECTORS
|