Files
ss-tools/backend/tests/test_translate_scheduler.py
busya a3c7c402b7 fix(llm): add fetch-models endpoint, fix SQL Lab INSERT (client_id truncation, sync mode, target_column, timestamp normalization)
- Add POST /api/llm/providers/fetch-models route with LLMClient.fetch_models()
- Add target_column to TranslationJob model/schema/service/orchestrator
- Fix SQL Lab execute: truncate client_id to 11 chars (varchar(11))
- Switch SQL Lab to sync mode (runAsync: false) — no Celery workers
- Fix polling: unwrap nested result from Superset query API
- Fix ClickHouse timestamp: normalize float timestamps to YYYY-MM-DD
2026-05-13 20:06:15 +03:00

206 lines
7.6 KiB
Python

# [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"))
import pytest
from unittest.mock import MagicMock
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from datetime import datetime, timezone
import uuid
from src.core.database import Base
from src.app import app
from src.dependencies import get_current_user, get_config_manager, get_db
from src.plugins.translate.scheduler import TranslationScheduler, execute_scheduled_translation
from src.plugins.translate.service import TranslateJobService
from src.schemas.translate import TranslateJobCreate
from src.models.translate import TranslationJob, TranslationSchedule, TranslationRun
# 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."""
from src.core.config_models import Environment
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."""
from src.core.config_models import Environment
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."""
from src.core.config_models import Environment
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."""
from src.core.config_models import Environment
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."""
from src.core.config_models import Environment
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."""
from src.core.config_models import Environment
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]