DispelEvilBot/tests/test_notifications_schedule.py

125 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Тесты расписаний бота: совпадение времени, таймзоны и сид V16."""
from __future__ import annotations
import re
from datetime import UTC, datetime
from pathlib import Path
from bot.services import notifications
from bot.services.notifications import DEFAULT_SCHEDULES, ScheduledMessage, _from_row
SEED_FILE = Path(__file__).resolve().parents[1] / "sql" / "V16__seed_schedules.sql"
_ROW_RE = re.compile(r"\('([^']+)',\s*(\d+),\s*(\d+),\s*'\{([^}]*)\}',\s*'([^']+)'")
def test_matches_daily_in_schedule_timezone() -> None:
schedule = ScheduledMessage(16, 30, "notify.blago_30")
# 13:30 UTC = 16:30 МСК
assert schedule.matches(datetime(2026, 10, 19, 13, 30, tzinfo=UTC))
assert not schedule.matches(datetime(2026, 10, 19, 13, 31, tzinfo=UTC))
def test_matches_respects_weekdays_iso() -> None:
friday = ScheduledMessage(15, 0, "notify.vklad", weekdays=(5,))
# 2026-10-16 — пятница, 12:00 UTC = 15:00 МСК
assert friday.matches(datetime(2026, 10, 16, 12, 0, tzinfo=UTC))
# 2026-10-15 — четверг
assert not friday.matches(datetime(2026, 10, 15, 12, 0, tzinfo=UTC))
def test_matches_uses_schedule_timezone() -> None:
berlin = ScheduledMessage(16, 30, "notify.blago_30", timezone="Europe/Berlin")
# Зимой Берлин = UTC+1: 15:30 UTC = 16:30
assert berlin.matches(datetime(2026, 1, 15, 15, 30, tzinfo=UTC))
def test_from_row_converts_fields() -> None:
schedule = _from_row(
{
"hour": 14,
"minute": 5,
"template_key": "notify.vklad",
"weekdays": [7],
"timezone": "Europe/Moscow",
}
)
assert schedule == ScheduledMessage(14, 5, "notify.vklad", weekdays=(7,), timezone="Europe/Moscow")
def test_default_schedules_match_seed() -> None:
"""Расписание-фолбэк в коде не должно расходиться с сидом V16."""
seeded = []
for key, hour, minute, days, timezone in _ROW_RE.findall(SEED_FILE.read_text(encoding="utf-8")):
weekdays = tuple(int(day) for day in days.split(",") if day.strip())
seeded.append(
ScheduledMessage(
hour=int(hour),
minute=int(minute),
template_key=key,
weekdays=weekdays,
timezone=timezone,
)
)
assert seeded == list(DEFAULT_SCHEDULES)
async def test_tick_skips_already_sent_minute(monkeypatch) -> None:
schedule = ScheduledMessage(12, 0, "notify.blago", schedule_id=1)
sent: list[str] = []
async def load() -> tuple[ScheduledMessage, ...]:
return (schedule,)
async def claim(schedule_id: int, run_at: datetime) -> bool:
return False
async def subscribers() -> list[dict]:
return []
async def broadcast(bot, key: str, rows: list[dict]) -> None:
sent.append(key)
monkeypatch.setattr(notifications, "_load_schedules", load)
monkeypatch.setattr(notifications.schedules_repo, "claim_run", claim)
monkeypatch.setattr(notifications.users_service, "list_subscribers", subscribers)
monkeypatch.setattr(notifications, "_send_broadcast", broadcast)
await notifications._tick(None, datetime(2026, 1, 1, 9, 0, tzinfo=UTC))
assert sent == []
async def test_tick_sends_once_when_claimed(monkeypatch) -> None:
schedule = ScheduledMessage(12, 0, "notify.blago", schedule_id=1)
sent: list[str] = []
claims: list[tuple[int, datetime]] = []
async def load() -> tuple[ScheduledMessage, ...]:
return (schedule,)
async def claim(schedule_id: int, run_at: datetime) -> bool:
claims.append((schedule_id, run_at))
return True
async def subscribers() -> list[dict]:
return []
async def broadcast(bot, key: str, rows: list[dict]) -> None:
sent.append(key)
monkeypatch.setattr(notifications, "_load_schedules", load)
monkeypatch.setattr(notifications.schedules_repo, "claim_run", claim)
monkeypatch.setattr(notifications.users_service, "list_subscribers", subscribers)
monkeypatch.setattr(notifications, "_send_broadcast", broadcast)
await notifications._tick(None, datetime(2026, 1, 1, 9, 0, tzinfo=UTC))
assert sent == ["notify.blago"]
assert claims == [(1, datetime(2026, 1, 1, 9, 0, tzinfo=UTC))]