# #region TestCoreScheduler [C:3] [TYPE Module] [SEMANTICS test, scheduler, apscheduler, load, reload] # @BRIEF Verify SchedulerService load_schedules and translation job add/remove contracts. # @RELATION BINDS_TO -> [SchedulerService] # @TEST_EDGE: load_schedules_reloads — jobs survive remove_all_jobs when re-registered from DB # @TEST_EDGE: add_remove_translation_job — job correctly registered and removed from APScheduler # @TEST_INVARIANT: translation_jobs_persist_across_reload -> VERIFIED_BY: [test_load_schedules_reloads_translation_schedules] import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent / "src")) from unittest.mock import MagicMock, patch from apscheduler.schedulers.background import BackgroundScheduler # --------------------------------------------------------------------------- # Helpers — fresh scheduler & mock config # --------------------------------------------------------------------------- def _make_mock_config_manager(backup_env_id="env1", backup_cron="0 3 * * *"): """Build a mock ConfigManager returning a single backup-enabled environment.""" mgr = MagicMock() mock_env = MagicMock() mock_env.id = backup_env_id mock_env.backup_schedule = MagicMock() mock_env.backup_schedule.enabled = True mock_env.backup_schedule.cron_expression = backup_cron mock_config = MagicMock() mock_config.environments = [mock_env] mgr.get_config.return_value = mock_config return mgr def _make_mock_task_manager(): """Build a mock TaskManager returning no active tasks.""" tm = MagicMock() tm.get_tasks.return_value = [] return tm # --------------------------------------------------------------------------- # Fixture helpers — start/stop a real BackgroundScheduler # --------------------------------------------------------------------------- def _start_scheduler(): """Create and start a BackgroundScheduler. Returns the scheduler instance.""" sched = BackgroundScheduler() sched.start() return sched def _stop_scheduler(sched): """Shutdown a BackgroundScheduler if running.""" if sched.running: sched.shutdown(wait=False) # #region test_load_schedules_reloads_translation_schedules [C:1] [TYPE Function] def test_load_schedules_reloads_translation_schedules(): """Verify load_schedules re-registers translation jobs after remove_all_jobs.""" from src.core.scheduler import SchedulerService sched = _start_scheduler() try: cfg = _make_mock_config_manager() tm = _make_mock_task_manager() svc = SchedulerService.__new__(SchedulerService) svc.task_manager = tm svc.config_manager = cfg svc.scheduler = sched # Pre-register a translation job (simulates what happens before reload) svc.add_translation_job( schedule_id="sched-1", job_id="job-1", cron_expression="0 2 * * *", ) # Verify it exists BEFORE reload job_before = sched.get_job("translate_sched-1") assert job_before is not None, "Translation job should exist before reload" # Mock the DB query that load_schedules uses internally mock_schedule = MagicMock() mock_schedule.id = "sched-1" mock_schedule.job_id = "job-1" mock_schedule.cron_expression = "0 2 * * *" mock_schedule.timezone = "UTC" mock_schedule.execution_mode = "full" mock_db = MagicMock() mock_db.query.return_value.filter.return_value.all.return_value = [mock_schedule] with patch("src.core.scheduler.SessionLocal", return_value=mock_db): svc.load_schedules() # After reload, the translation job should STILL exist job_after = sched.get_job("translate_sched-1") assert job_after is not None, ( "Translation job should survive load_schedules (re-registered from DB)" ) # Backup job should also exist (added from config) backup_job = sched.get_job("backup_env1") assert backup_job is not None, "Backup job should be registered by load_schedules" finally: _stop_scheduler(sched) # #endregion test_load_schedules_reloads_translation_schedules # #region test_add_and_remove_translation_job [C:1] [TYPE Function] def test_add_and_remove_translation_job(): """Verify add_translation_job registers and remove_translation_job removes.""" from src.core.scheduler import SchedulerService sched = _start_scheduler() try: cfg = _make_mock_config_manager() tm = _make_mock_task_manager() svc = SchedulerService.__new__(SchedulerService) svc.task_manager = tm svc.config_manager = cfg svc.scheduler = sched # Should NOT exist before add assert sched.get_job("translate_sched-x") is None svc.add_translation_job( schedule_id="sched-x", job_id="job-x", cron_expression="*/5 * * * *", ) # Should exist after add job = sched.get_job("translate_sched-x") assert job is not None, "Translation job should be registered after add" assert job.id == "translate_sched-x" svc.remove_translation_job("sched-x") # Should NOT exist after remove assert sched.get_job("translate_sched-x") is None, ( "Translation job should be removed after remove_translation_job" ) finally: _stop_scheduler(sched) # #endregion test_add_and_remove_translation_job # #region test_remove_nonexistent_translation_job_no_error [C:1] [TYPE Function] def test_remove_nonexistent_translation_job_no_error(): """Verify remove_translation_job handles non-existent job silently.""" from src.core.scheduler import SchedulerService sched = _start_scheduler() try: cfg = _make_mock_config_manager() tm = _make_mock_task_manager() svc = SchedulerService.__new__(SchedulerService) svc.task_manager = tm svc.config_manager = cfg svc.scheduler = sched # Should not raise — silent ignore svc.remove_translation_job("nonexistent-sched") finally: _stop_scheduler(sched) # #endregion test_remove_nonexistent_translation_job_no_error # #endregion TestCoreScheduler