"""Tests for the recurring worker-tier jobs (scheduler.py): job registration, cadence from env, idempotent start/stop, and that each job calls the right underlying function. No real hours-long wait — inspects APScheduler's own job registry rather than letting anything actually fire.""" import datetime import pytest from notifications import scheduler @pytest.fixture(autouse=True) def _clean_scheduler(): """Every test starts from "not running" and leaves nothing behind, even on failure — scheduler._scheduler is module-global state shared across tests.""" scheduler.stop() yield scheduler.stop() def test_start_registers_both_jobs_with_configured_intervals(monkeypatch): monkeypatch.setattr(scheduler, "WARM_CITIES_INTERVAL_HOURS", 24) monkeypatch.setattr(scheduler, "INDEXNOW_INTERVAL_HOURS", 6) scheduler.start() jobs = {j.id: j for j in scheduler._scheduler.get_jobs()} assert set(jobs) == {"warm_cities", "indexnow"} assert jobs["warm_cities"].trigger.interval == datetime.timedelta(hours=24) assert jobs["indexnow"].trigger.interval == datetime.timedelta(hours=6) assert jobs["warm_cities"].func is scheduler._warm_cities_job assert jobs["indexnow"].func is scheduler._indexnow_job def test_start_does_not_run_a_job_immediately(): """warm_cities already runs at deploy time; an immediate duplicate run on every worker boot/restart would just be wasted work — the first scheduled run must be one interval away, not "now".""" scheduler.start() now = datetime.datetime.now(datetime.timezone.utc) for job in scheduler._scheduler.get_jobs(): assert job.next_run_time > now def test_start_is_idempotent(monkeypatch): scheduler.start() first = scheduler._scheduler calls = [] monkeypatch.setattr(scheduler.BackgroundScheduler, "__init__", lambda self, **kw: calls.append(1) or None) scheduler.start() assert scheduler._scheduler is first assert calls == [] # a second BackgroundScheduler was never constructed def test_stop_shuts_down_and_clears_state(): scheduler.start() assert scheduler._scheduler is not None scheduler.stop() assert scheduler._scheduler is None def test_stop_without_start_is_a_safe_no_op(): scheduler.stop() # must not raise assert scheduler._scheduler is None def test_warm_cities_job_calls_warm_cities_main(monkeypatch): import warm_cities calls = [] monkeypatch.setattr(warm_cities, "main", lambda: calls.append(1)) scheduler._warm_cities_job() assert calls == [1] def test_indexnow_job_calls_submit_if_changed(monkeypatch): import indexnow calls = [] monkeypatch.setattr(indexnow, "submit_if_changed", lambda: calls.append(1)) scheduler._indexnow_job() assert calls == [1]