# #region Test.LLMAnalysisScheduler [C:3] [TYPE Module] [SEMANTICS test, llm, analysis, scheduler, cron] # @BRIEF Tests for llm_analysis/scheduler.py — _parse_cron, schedule_dashboard_validation. # @RELATION BINDS_TO -> [LLMAnalysisScheduler] # @TEST_CONTRACT: _parse_cron -> dict | cron expression parsing # @TEST_CONTRACT: schedule_dashboard_validation -> None | schedules a validation job import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src")) import os os.environ.setdefault("SECRET_KEY", "test-secret-key-for-testing") os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:") os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:") from unittest.mock import AsyncMock, MagicMock, patch import pytest from src.plugins.llm_analysis.scheduler import _parse_cron, schedule_dashboard_validation class TestParseCron: """_parse_cron — basic cron expression parser.""" def test_standard_5_part_cron(self): """Standard 5-part cron expression parsed correctly.""" result = _parse_cron("0 8 * * 1-5") assert result == { "minute": "0", "hour": "8", "day": "*", "month": "*", "day_of_week": "1-5", } def test_daily_at_midnight(self): """Daily at midnight.""" result = _parse_cron("0 0 * * *") assert result["hour"] == "0" assert result["minute"] == "0" def test_every_hour(self): """Every hour at minute 0.""" result = _parse_cron("0 * * * *") assert result["minute"] == "0" assert result["hour"] == "*" def test_invalid_parts_count(self): """Less or more than 5 parts returns empty dict.""" assert _parse_cron("0 8 * *") == {} assert _parse_cron("0 8 * * 1-5 extra") == {} def test_empty_string(self): """Empty string returns empty dict.""" assert _parse_cron("") == {} def test_step_values(self): """Step values like */15 parsed correctly.""" result = _parse_cron("*/15 * * * *") assert result["minute"] == "*/15" class TestScheduleDashboardValidation: """schedule_dashboard_validation — schedules recurring validation.""" def test_schedule_with_params(self): """Scheduler and task manager called with correct args.""" mock_scheduler = MagicMock() mock_task_manager = MagicMock() with patch("src.plugins.llm_analysis.scheduler.get_scheduler_service", return_value=mock_scheduler), \ patch("src.plugins.llm_analysis.scheduler.get_task_manager", return_value=mock_task_manager): schedule_dashboard_validation( dashboard_id="dash-1", cron_expression="0 6 * * *", params={"threshold": 0.8}, ) # Scheduler should have added a cron job mock_scheduler.add_job.assert_called_once() call_args = mock_scheduler.add_job.call_args assert call_args[0][1] == "cron" # trigger type assert call_args[1]["id"] == "llm_val_dash-1" assert call_args[1]["replace_existing"] is True assert call_args[1]["minute"] == "0" assert call_args[1]["hour"] == "6" def test_schedule_creates_task_on_execution(self): """The scheduled job function creates a task when executed.""" mock_scheduler = MagicMock() mock_task_manager = AsyncMock() with patch("src.plugins.llm_analysis.scheduler.get_scheduler_service", return_value=mock_scheduler), \ patch("src.plugins.llm_analysis.scheduler.get_task_manager", return_value=mock_task_manager): schedule_dashboard_validation( dashboard_id="dash-1", cron_expression="0 6 * * *", params={"threshold": 0.8}, ) # Get the job function and call it job_func = mock_scheduler.add_job.call_args[0][0] import asyncio asyncio.run(job_func()) mock_task_manager.create_task.assert_called_once_with( plugin_id="llm_dashboard_validation", params={"dashboard_id": "dash-1", "threshold": 0.8}, ) def test_schedule_without_params(self): """Schedule without extra params still works.""" mock_scheduler = MagicMock() mock_task_manager = AsyncMock() with patch("src.plugins.llm_analysis.scheduler.get_scheduler_service", return_value=mock_scheduler), \ patch("src.plugins.llm_analysis.scheduler.get_task_manager", return_value=mock_task_manager): schedule_dashboard_validation( dashboard_id="dash-2", cron_expression="0 0 * * *", params={}, ) job_func = mock_scheduler.add_job.call_args[0][0] import asyncio asyncio.run(job_func()) mock_task_manager.create_task.assert_called_once_with( plugin_id="llm_dashboard_validation", params={"dashboard_id": "dash-2"}, ) # #endregion Test.LLMAnalysisScheduler