# [DEF:TranslateSchedulerTests:Module] # @SEMANTICS: tests, translate, scheduler # @PURPOSE: Tests for TranslationScheduler CRUD and APScheduler integration. # @LAYER: Test # @RELATION: BINDS_TO -> [TranslationScheduler:Module] # # @TEST_CONTRACT: ScheduleFlow -> # { # invariants: [ # "Create schedule returns TranslationSchedule with valid fields", # "Update schedule modifies cron/timezone/active", # "Delete schedule removes the row", # "Enable/disable toggles is_active", # "get_next_executions returns ISO datetime strings", # "List active schedules returns only active rows" # ] # } # @TEST_FIXTURE: valid_job -> created via TranslateJobService # @TEST_FIXTURE: schedule_data -> valid cron expression # @TEST_EDGE: schedule_not_found -> ValueError on get/delete non-existent # @TEST_EDGE: next_executions_invalid_cron -> empty list # @TEST_INVARIANT: schedule_crud_flow -> VERIFIED_BY: [valid_job, schedule_data] import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent / "src")) from unittest.mock import MagicMock import pytest from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from src.core.database import Base from src.plugins.translate.scheduler import TranslationScheduler from src.plugins.translate.service import TranslateJobService from src.schemas.translate import TranslateJobCreate # In-memory SQLite SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:" engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}) TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base.metadata.create_all(bind=engine) @pytest.fixture def db_session(): connection = engine.connect() transaction = connection.begin() session = TestingSessionLocal(bind=connection) yield session session.close() transaction.rollback() connection.close() # [DEF:test_create_schedule:Function] # @PURPOSE: Verify schedule creation with valid params. def test_create_schedule(db_session): """Test creating a schedule for a job.""" config_mgr = MagicMock() config_mgr.get_environments.return_value = [] svc = TranslateJobService(db_session, config_mgr, "test_user") payload = TranslateJobCreate(name="Sched Job", source_dialect="pg", target_dialect="ch") job = svc.create_job(payload) scheduler = TranslationScheduler(db_session, config_mgr, "test_user") schedule = scheduler.create_schedule( job_id=job.id, cron_expression="0 2 * * *", timezone="UTC", ) assert schedule.job_id == job.id assert schedule.cron_expression == "0 2 * * *" assert schedule.timezone == "UTC" assert schedule.is_active is True assert schedule.id is not None # [/DEF:test_create_schedule:Function] # [DEF:test_update_schedule:Function] # @PURPOSE: Verify schedule update. def test_update_schedule(db_session): """Test updating a schedule.""" config_mgr = MagicMock() config_mgr.get_environments.return_value = [] svc = TranslateJobService(db_session, config_mgr, "test_user") job = svc.create_job(TranslateJobCreate(name="Job", source_dialect="pg", target_dialect="ch")) scheduler = TranslationScheduler(db_session, config_mgr, "test_user") scheduler.create_schedule(job.id, "0 2 * * *") updated = scheduler.update_schedule(job.id, cron_expression="30 3 * * *", timezone_str="US/Eastern", is_active=False) assert updated.cron_expression == "30 3 * * *" assert updated.timezone == "US/Eastern" assert updated.is_active is False # [/DEF:test_update_schedule:Function] # [DEF:test_delete_schedule:Function] # @PURPOSE: Verify schedule deletion. def test_delete_schedule(db_session): """Test deleting a schedule.""" config_mgr = MagicMock() config_mgr.get_environments.return_value = [] svc = TranslateJobService(db_session, config_mgr, "test_user") job = svc.create_job(TranslateJobCreate(name="Job", source_dialect="pg", target_dialect="ch")) scheduler = TranslationScheduler(db_session, config_mgr, "test_user") scheduler.create_schedule(job.id, "0 2 * * *") scheduler.delete_schedule(job.id) with pytest.raises(ValueError, match="No schedule found"): scheduler.get_schedule(job.id) # [/DEF:test_delete_schedule:Function] # [DEF:test_enable_disable_schedule:Function] # @PURPOSE: Verify enable/disable toggle. def test_enable_disable_schedule(db_session): """Test enabling and disabling a schedule.""" config_mgr = MagicMock() config_mgr.get_environments.return_value = [] svc = TranslateJobService(db_session, config_mgr, "test_user") job = svc.create_job(TranslateJobCreate(name="Job", source_dialect="pg", target_dialect="ch")) scheduler = TranslationScheduler(db_session, config_mgr, "test_user") scheduler.create_schedule(job.id, "0 2 * * *") sched = scheduler.set_schedule_active(job.id, False) assert sched.is_active is False sched = scheduler.set_schedule_active(job.id, True) assert sched.is_active is True # [/DEF:test_enable_disable_schedule:Function] # [DEF:test_get_schedule_not_found:Function] # @PURPOSE: Verify ValueError on getting non-existent schedule. def test_get_schedule_not_found(db_session): """Test that getting a non-existent schedule raises ValueError.""" config_mgr = MagicMock() config_mgr.get_environments.return_value = [] scheduler = TranslationScheduler(db_session, config_mgr, "test_user") with pytest.raises(ValueError, match="No schedule found"): scheduler.get_schedule("non-existent") # [/DEF:test_get_schedule_not_found:Function] # [DEF:test_list_active_schedules:Function] # @PURPOSE: Verify listing only active schedules. def test_list_active_schedules(db_session): """Test listing active schedules.""" config_mgr = MagicMock() config_mgr.get_environments.return_value = [] svc = TranslateJobService(db_session, config_mgr, "test_user") job1 = svc.create_job(TranslateJobCreate(name="Job1", source_dialect="pg", target_dialect="ch")) scheduler = TranslationScheduler(db_session, config_mgr, "test_user") scheduler.create_schedule(job1.id, "0 2 * * *") scheduler.set_schedule_active(job1.id, True) active = TranslationScheduler.list_active_schedules(db_session) assert len(active) >= 1 assert active[0].job_id == job1.id # [/DEF:test_list_active_schedules:Function] # [DEF:test_get_next_executions:Function] # @PURPOSE: Verify next execution time computation. def test_get_next_executions(): """Test computing next execution times.""" times = TranslationScheduler.get_next_executions("0 2 * * *", "UTC", n=3) assert len(times) == 3 for t in times: assert "T" in t # ISO format check # [/DEF:test_get_next_executions:Function] # [DEF:test_get_next_executions_invalid:Function] # @PURPOSE: Verify invalid cron returns empty list. def test_get_next_executions_invalid(): """Test invalid cron returns empty list.""" times = TranslationScheduler.get_next_executions("invalid cron", "UTC", n=3) assert times == [] # [/DEF:test_get_next_executions_invalid:Function] # [/DEF:TranslateSchedulerTests:Module]