You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
5.5 KiB
159 lines
5.5 KiB
"""Tests for scheduler.py — automated pipeline scheduling.
|
|
|
|
TDD: write failing test, implement, refactor.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import signal
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
class TestPipelineSchedulerInit:
|
|
def test_default_db_path(self):
|
|
from scheduler import PipelineScheduler
|
|
|
|
sched = PipelineScheduler()
|
|
assert sched.db_path == "data/motions.db"
|
|
assert not sched._running
|
|
|
|
def test_custom_db_path(self):
|
|
from scheduler import PipelineScheduler
|
|
|
|
sched = PipelineScheduler(db_path="/tmp/test.db")
|
|
assert sched.db_path == "/tmp/test.db"
|
|
|
|
|
|
class TestPipelineSchedulerRunPipeline:
|
|
def test_calls_pipeline_run_with_db_path(self):
|
|
from scheduler import PipelineScheduler
|
|
|
|
sched = PipelineScheduler(db_path="/tmp/test.db")
|
|
with patch("scheduler.run_pipeline") as mock_run:
|
|
mock_run.return_value = 0
|
|
sched.run_pipeline()
|
|
mock_run.assert_called_once()
|
|
# Verify args contain db_path via Namespace
|
|
args = mock_run.call_args[0][0]
|
|
assert args.db_path == "/tmp/test.db"
|
|
|
|
def test_logs_error_on_pipeline_failure(self):
|
|
from scheduler import PipelineScheduler
|
|
|
|
sched = PipelineScheduler()
|
|
with patch("scheduler.run_pipeline") as mock_run:
|
|
mock_run.side_effect = RuntimeError("pipeline failed")
|
|
with patch("scheduler._logger") as mock_logger:
|
|
result = sched.run_pipeline()
|
|
assert result == 1
|
|
mock_logger.exception.assert_called_once()
|
|
|
|
|
|
class TestPipelineSchedulerRunSummarizer:
|
|
def test_calls_summarizer_update(self):
|
|
from scheduler import PipelineScheduler
|
|
|
|
sched = PipelineScheduler()
|
|
with patch("scheduler.summarizer") as mock_summarizer:
|
|
sched.run_summarizer()
|
|
mock_summarizer.update_motion_summaries.assert_called_once()
|
|
|
|
def test_logs_error_on_summarizer_failure(self):
|
|
from scheduler import PipelineScheduler
|
|
|
|
sched = PipelineScheduler()
|
|
with patch("scheduler.summarizer") as mock_summarizer:
|
|
mock_summarizer.update_motion_summaries.side_effect = RuntimeError(
|
|
"summarizer failed"
|
|
)
|
|
with patch("scheduler._logger") as mock_logger:
|
|
sched.run_summarizer()
|
|
mock_logger.exception.assert_called_once()
|
|
|
|
|
|
class TestPipelineSchedulerSchedule:
|
|
def test_schedule_daily_adds_job(self):
|
|
from scheduler import PipelineScheduler
|
|
|
|
sched = PipelineScheduler()
|
|
with patch("scheduler.schedule") as mock_schedule:
|
|
mock_job = MagicMock()
|
|
mock_schedule.every.return_value.day.at.return_value.do = mock_job
|
|
sched.schedule_daily("02:00")
|
|
mock_schedule.every.assert_called_once()
|
|
|
|
def test_schedule_summarizer_adds_job(self):
|
|
from scheduler import PipelineScheduler
|
|
|
|
sched = PipelineScheduler()
|
|
with patch("scheduler.schedule") as mock_schedule:
|
|
mock_job = MagicMock()
|
|
mock_schedule.every.return_value.hour.do = mock_job
|
|
sched.schedule_summarizer(every_n_hours=6)
|
|
mock_schedule.every.assert_called_once()
|
|
|
|
|
|
class TestPipelineSchedulerLoop:
|
|
def test_start_runs_pending_jobs(self):
|
|
from scheduler import PipelineScheduler
|
|
|
|
sched = PipelineScheduler()
|
|
call_count = 0
|
|
|
|
def _stop_after_first(*args, **kwargs):
|
|
nonlocal call_count
|
|
call_count += 1
|
|
if call_count >= 3:
|
|
sched.stop()
|
|
|
|
with patch("scheduler.schedule.run_pending") as mock_run_pending:
|
|
with patch("scheduler.time.sleep", side_effect=_stop_after_first):
|
|
with patch("scheduler.signal.signal"):
|
|
sched.start()
|
|
assert mock_run_pending.called
|
|
assert not sched._running
|
|
|
|
def test_stop_sets_running_false(self):
|
|
from scheduler import PipelineScheduler
|
|
|
|
sched = PipelineScheduler()
|
|
sched._running = True
|
|
sched.stop()
|
|
assert not sched._running
|
|
|
|
def test_signal_handler_stops_scheduler(self):
|
|
from scheduler import PipelineScheduler
|
|
|
|
sched = PipelineScheduler()
|
|
sched._running = True
|
|
with patch.object(sched, "stop") as mock_stop:
|
|
sched._signal_handler(signal.SIGINT, None)
|
|
mock_stop.assert_called_once()
|
|
|
|
|
|
class TestSchedulerCLI:
|
|
def test_main_parses_args(self):
|
|
from scheduler import main
|
|
|
|
with patch("scheduler.PipelineScheduler") as mock_sched_class:
|
|
mock_sched = MagicMock()
|
|
mock_sched_class.return_value = mock_sched
|
|
rc = main(["--pipeline-time", "03:00"])
|
|
assert rc == 0
|
|
mock_sched_class.assert_called_once_with(db_path="data/motions.db")
|
|
mock_sched.schedule_daily.assert_called_once_with("03:00")
|
|
mock_sched.start.assert_called_once()
|
|
|
|
def test_main_custom_db_path(self):
|
|
from scheduler import main
|
|
|
|
with patch("scheduler.PipelineScheduler") as mock_sched_class:
|
|
mock_sched = MagicMock()
|
|
mock_sched.run_pipeline.return_value = 0
|
|
mock_sched_class.return_value = mock_sched
|
|
rc = main(["--db-path", "/tmp/test.db", "--once"])
|
|
assert rc == 0
|
|
mock_sched_class.assert_called_once_with(db_path="/tmp/test.db")
|
|
mock_sched.run_pipeline.assert_called_once()
|
|
|