Files
signal-platform/tests/unit/test_schedule_config.py
T

134 lines
4.8 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.
"""Unit tests for the cron pipeline schedule config."""
from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.exceptions import ValidationError
from app.scheduler import SCHEDULE_DEFAULTS, validate_cron
from app.services.admin_service import get_schedule_config, update_schedule_config
@pytest.fixture
async def session() -> AsyncSession:
from tests.conftest import _test_session_factory
async with _test_session_factory() as session:
yield session
class TestValidateCron:
def test_accepts_valid(self):
validate_cron("0 7 * * *", "Europe/Berlin")
validate_cron("0 14-22 * * 1-5", "UTC")
def test_rejects_bad_cron(self):
with pytest.raises(Exception):
validate_cron("not a cron", "UTC")
def test_rejects_bad_timezone(self):
with pytest.raises(Exception):
validate_cron("0 7 * * *", "Mars/Phobos")
class TestTradingDayCrons:
"""APScheduler's from_crontab() uses 0=Monday, so numeric "1-5" means
TueSat: it skips every Monday and fires on Saturdays. Weekday schedules
must therefore be spelled with day *names*.
"""
_WEEKDAY_KEYS = (
"schedule_near_close_pipeline_cron",
"schedule_after_close_pipeline_cron",
"schedule_intraday_pipeline_cron",
)
@pytest.mark.parametrize("key", _WEEKDAY_KEYS)
def test_fires_monday_and_never_saturday(self, key: str):
from datetime import datetime, timedelta
from apscheduler.triggers.cron import CronTrigger
trigger = CronTrigger.from_crontab(
SCHEDULE_DEFAULTS[key], timezone=SCHEDULE_DEFAULTS["schedule_timezone"]
)
# Walk a full week of fire times from a known Sunday.
cursor = datetime(2026, 7, 19, tzinfo=trigger.timezone)
weekdays = set()
previous = None
for _ in range(12):
fire = trigger.get_next_fire_time(previous, cursor)
weekdays.add(fire.strftime("%a"))
previous = fire
cursor = fire + timedelta(seconds=1)
assert "Mon" in weekdays, f"{key} skips Mondays — numeric day-of-week?"
assert {"Sat", "Sun"}.isdisjoint(weekdays), f"{key} fires on a weekend"
def test_fundamentals_runs_on_monday(self):
from datetime import datetime
from apscheduler.triggers.cron import CronTrigger
trigger = CronTrigger.from_crontab(
SCHEDULE_DEFAULTS["schedule_fundamentals_cron"],
timezone=SCHEDULE_DEFAULTS["schedule_timezone"],
)
fire = trigger.get_next_fire_time(
None, datetime(2026, 7, 19, tzinfo=trigger.timezone)
)
assert fire.strftime("%a") == "Mon"
@pytest.mark.parametrize(
("key", "hour", "minute"),
(
("schedule_dolt_earnings_cron", 2, 30),
("schedule_sec_fundamentals_cron", 4, 0),
("schedule_fundamentals_parity_cron", 5, 30),
),
)
def test_shadow_imports_run_daily_at_expected_et_time(
self, key: str, hour: int, minute: int
):
from datetime import datetime
from apscheduler.triggers.cron import CronTrigger
trigger = CronTrigger.from_crontab(
SCHEDULE_DEFAULTS[key], timezone=SCHEDULE_DEFAULTS["schedule_timezone"]
)
fire = trigger.get_next_fire_time(
None, datetime(2026, 7, 19, tzinfo=trigger.timezone)
)
assert (fire.hour, fire.minute) == (hour, minute)
class TestScheduleConfig:
async def test_defaults_when_unset(self, session: AsyncSession):
config = await get_schedule_config(session)
assert config == SCHEDULE_DEFAULTS
async def test_update_and_read_back(self, session: AsyncSession):
updated = await update_schedule_config(
session, {"schedule_daily_pipeline_cron": "30 6 * * *"}
)
assert updated["schedule_daily_pipeline_cron"] == "30 6 * * *"
# untouched keys keep their defaults
assert updated["schedule_intraday_pipeline_cron"] == SCHEDULE_DEFAULTS["schedule_intraday_pipeline_cron"]
config = await get_schedule_config(session)
assert config["schedule_daily_pipeline_cron"] == "30 6 * * *"
async def test_rejects_bad_cron(self, session: AsyncSession):
with pytest.raises(ValidationError):
await update_schedule_config(session, {"schedule_fundamentals_cron": "every monday"})
async def test_rejects_bad_timezone(self, session: AsyncSession):
with pytest.raises(ValidationError):
await update_schedule_config(session, {"schedule_timezone": "Nowhere/Void"})
async def test_rejects_unknown_key(self, session: AsyncSession):
with pytest.raises(ValidationError):
await update_schedule_config(session, {"schedule_bogus": "0 0 * * *"})