feat(deploy): schedule fundamentals shadow imports
This commit is contained in:
@@ -80,6 +80,28 @@ class TestTradingDayCrons:
|
||||
)
|
||||
assert fire.strftime("%a") == "Mon"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("key", "hour", "minute"),
|
||||
(
|
||||
("schedule_dolt_earnings_cron", 2, 30),
|
||||
("schedule_sec_fundamentals_cron", 4, 0),
|
||||
),
|
||||
)
|
||||
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):
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Unit tests for app.scheduler module."""
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from app.scheduler import (
|
||||
@@ -8,7 +10,9 @@ from app.scheduler import (
|
||||
_parse_frequency,
|
||||
_resume_tickers,
|
||||
_last_successful,
|
||||
_run_shadow_import,
|
||||
configure_scheduler,
|
||||
get_job_runtime_snapshot,
|
||||
queue_backtest_options,
|
||||
queue_backtest_target_model,
|
||||
scheduler,
|
||||
@@ -106,6 +110,8 @@ class TestConfigureScheduler:
|
||||
"benchmark_collector",
|
||||
"sentiment_collector",
|
||||
"fundamental_collector",
|
||||
"dolt_earnings_import",
|
||||
"sec_fundamentals_import",
|
||||
"rr_scanner",
|
||||
"shadow_book",
|
||||
"ticker_universe_sync",
|
||||
@@ -137,6 +143,8 @@ class TestConfigureScheduler:
|
||||
"data_collector",
|
||||
"data_backfill",
|
||||
"fundamental_collector",
|
||||
"dolt_earnings_import",
|
||||
"sec_fundamentals_import",
|
||||
"market_regime",
|
||||
"near_close_pipeline",
|
||||
"regime_monitor",
|
||||
@@ -147,3 +155,91 @@ class TestConfigureScheduler:
|
||||
"shadow_book",
|
||||
"ticker_universe_sync",
|
||||
])
|
||||
|
||||
|
||||
class _SessionContext:
|
||||
async def __aenter__(self):
|
||||
return object()
|
||||
|
||||
async def __aexit__(self, *exc):
|
||||
return None
|
||||
|
||||
|
||||
class TestShadowImportJobs:
|
||||
@staticmethod
|
||||
def _session_factory():
|
||||
return _SessionContext()
|
||||
|
||||
async def test_promoted_run_surfaces_completion(self, monkeypatch):
|
||||
async def enabled(db, job_name):
|
||||
return True
|
||||
|
||||
async def imported(importer):
|
||||
return SimpleNamespace(
|
||||
status="promoted", revision="abcdef1234567890", error_details=None
|
||||
)
|
||||
|
||||
monkeypatch.setattr("app.scheduler.async_session_factory", self._session_factory)
|
||||
monkeypatch.setattr("app.scheduler._is_job_enabled", enabled)
|
||||
monkeypatch.setattr("app.scheduler.run_import", imported)
|
||||
|
||||
await _run_shadow_import("dolt_earnings_import", object())
|
||||
|
||||
runtime = get_job_runtime_snapshot("dolt_earnings_import")
|
||||
assert runtime["status"] == "completed"
|
||||
assert runtime["processed"] == 1
|
||||
assert runtime["message"] == "promoted · abcdef123456"
|
||||
|
||||
async def test_failed_run_surfaces_error(self, monkeypatch):
|
||||
async def enabled(db, job_name):
|
||||
return True
|
||||
|
||||
async def imported(importer):
|
||||
return SimpleNamespace(
|
||||
status="failed", revision=None, error_details="validation failed"
|
||||
)
|
||||
|
||||
monkeypatch.setattr("app.scheduler.async_session_factory", self._session_factory)
|
||||
monkeypatch.setattr("app.scheduler._is_job_enabled", enabled)
|
||||
monkeypatch.setattr("app.scheduler.run_import", imported)
|
||||
|
||||
await _run_shadow_import("sec_fundamentals_import", object())
|
||||
|
||||
runtime = get_job_runtime_snapshot("sec_fundamentals_import")
|
||||
assert runtime["status"] == "error"
|
||||
assert runtime["processed"] == 0
|
||||
assert runtime["message"] == "validation failed"
|
||||
|
||||
async def test_source_lock_surfaces_skipped(self, monkeypatch):
|
||||
async def enabled(db, job_name):
|
||||
return True
|
||||
|
||||
async def imported(importer):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr("app.scheduler.async_session_factory", self._session_factory)
|
||||
monkeypatch.setattr("app.scheduler._is_job_enabled", enabled)
|
||||
monkeypatch.setattr("app.scheduler.run_import", imported)
|
||||
|
||||
await _run_shadow_import("dolt_earnings_import", object())
|
||||
|
||||
runtime = get_job_runtime_snapshot("dolt_earnings_import")
|
||||
assert runtime["status"] == "skipped"
|
||||
assert "already running" in runtime["message"]
|
||||
|
||||
async def test_disabled_job_never_runs_importer(self, monkeypatch):
|
||||
async def disabled(db, job_name):
|
||||
return False
|
||||
|
||||
async def should_not_run(importer):
|
||||
raise AssertionError("disabled job ran importer")
|
||||
|
||||
monkeypatch.setattr("app.scheduler.async_session_factory", self._session_factory)
|
||||
monkeypatch.setattr("app.scheduler._is_job_enabled", disabled)
|
||||
monkeypatch.setattr("app.scheduler.run_import", should_not_run)
|
||||
|
||||
await _run_shadow_import("sec_fundamentals_import", object())
|
||||
|
||||
runtime = get_job_runtime_snapshot("sec_fundamentals_import")
|
||||
assert runtime["status"] == "skipped"
|
||||
assert runtime["message"] == "Disabled"
|
||||
|
||||
Reference in New Issue
Block a user