"""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()