213 lines
9.0 KiB
Python
213 lines
9.0 KiB
Python
# #region Test.Orchestrator [C:3] [TYPE Module] [SEMANTICS test, translate, orchestrator, lifecycle]
|
|
# @BRIEF Tests for orchestrator.py — TranslationOrchestrator delegates to sub-components.
|
|
# @RELATION BINDS_TO -> [TranslationOrchestrator]
|
|
# @TEST_CONTRACT: start_run -> TranslationRun | delegates to TranslationPlanner
|
|
# @TEST_CONTRACT: execute_run -> TranslationRun | delegates to TranslationStageRunner
|
|
# @TEST_CONTRACT: retry_failed_batches -> TranslationRun | delegates to TranslationStageRunner
|
|
# @TEST_CONTRACT: retry_insert -> TranslationRun | delegates to TranslationStageRunner
|
|
# @TEST_CONTRACT: cancel_run -> TranslationRun | delegates to TranslationStageRunner
|
|
# @TEST_CONTRACT: get_run_status -> dict | delegates to TranslationResultAggregator
|
|
# @TEST_CONTRACT: get_run_records -> dict | delegates to TranslationResultAggregator
|
|
# @TEST_CONTRACT: get_run_history -> tuple | delegates to TranslationResultAggregator
|
|
# @TEST_EDGE: sql_insert_service_backward_compat
|
|
# @TEST_EDGE: language_stats_updates
|
|
|
|
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 MagicMock, AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
from src.plugins.translate.orchestrator import TranslationOrchestrator
|
|
|
|
|
|
class TestTranslationOrchestrator:
|
|
"""TranslationOrchestrator — delegates all operations to sub-components."""
|
|
|
|
def _make_orchestrator(self):
|
|
"""Create orchestrator with mocked sub-components."""
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
|
|
with patch(
|
|
"src.plugins.translate.orchestrator.TranslationPlanner"
|
|
) as MockPlanner, patch(
|
|
"src.plugins.translate.orchestrator.TranslationStageRunner"
|
|
) as MockRunner, patch(
|
|
"src.plugins.translate.orchestrator.TranslationResultAggregator"
|
|
) as MockAggregator:
|
|
orch = TranslationOrchestrator(db, config, "user")
|
|
return orch, db, config, MockPlanner, MockRunner, MockAggregator
|
|
|
|
def test_start_run_delegates(self):
|
|
"""start_run delegates to TranslationPlanner."""
|
|
orch, _, _, MockPlanner, _, _ = self._make_orchestrator()
|
|
mock_run = MagicMock()
|
|
orch._planner = MockPlanner.return_value
|
|
orch._planner.plan_run.return_value = mock_run
|
|
|
|
result = orch.start_run("job-1", is_scheduled=True, trigger_type="cron", full_translation=True)
|
|
|
|
orch._planner.plan_run.assert_called_once_with(
|
|
job_id="job-1", is_scheduled=True, trigger_type="cron", full_translation=True,
|
|
)
|
|
assert result == mock_run
|
|
|
|
def test_start_run_minimal(self):
|
|
"""start_run with only job_id uses defaults."""
|
|
orch, _, _, MockPlanner, _, _ = self._make_orchestrator()
|
|
mock_run = MagicMock()
|
|
orch._planner = MockPlanner.return_value
|
|
orch._planner.plan_run.return_value = mock_run
|
|
|
|
result = orch.start_run("job-1")
|
|
|
|
orch._planner.plan_run.assert_called_once_with(
|
|
job_id="job-1", is_scheduled=False, trigger_type=None, full_translation=False,
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_run_delegates(self):
|
|
"""execute_run delegates to TranslationStageRunner."""
|
|
orch, _, _, _, MockRunner, _ = self._make_orchestrator()
|
|
mock_run = MagicMock()
|
|
orch._runner = MockRunner.return_value
|
|
orch._runner.execute_run = AsyncMock(return_value=mock_run)
|
|
|
|
progress = MagicMock()
|
|
result = await orch.execute_run(mock_run, on_batch_progress=progress, skip_insert=True)
|
|
|
|
orch._runner.execute_run.assert_called_once_with(
|
|
run=mock_run, on_batch_progress=progress, skip_insert=True,
|
|
)
|
|
assert result == mock_run
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_retry_failed_batches_delegates(self):
|
|
"""retry_failed_batches delegates to TranslationStageRunner."""
|
|
orch, _, _, _, MockRunner, _ = self._make_orchestrator()
|
|
mock_run = MagicMock()
|
|
orch._runner = MockRunner.return_value
|
|
orch._runner.retry_failed_batches = AsyncMock(return_value=mock_run)
|
|
|
|
result = await orch.retry_failed_batches("run-1")
|
|
|
|
orch._runner.retry_failed_batches.assert_called_once_with("run-1")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_retry_insert_delegates(self):
|
|
"""retry_insert delegates to TranslationStageRunner."""
|
|
orch, _, _, _, MockRunner, _ = self._make_orchestrator()
|
|
mock_run = MagicMock()
|
|
orch._runner = MockRunner.return_value
|
|
orch._runner.retry_insert = AsyncMock(return_value=mock_run)
|
|
|
|
result = await orch.retry_insert("run-1")
|
|
|
|
orch._runner.retry_insert.assert_called_once_with("run-1")
|
|
|
|
def test_cancel_run_delegates(self):
|
|
"""cancel_run delegates to TranslationStageRunner."""
|
|
orch, _, _, _, MockRunner, _ = self._make_orchestrator()
|
|
mock_run = MagicMock()
|
|
orch._runner = MockRunner.return_value
|
|
orch._runner.cancel_run.return_value = mock_run
|
|
|
|
result = orch.cancel_run("run-1")
|
|
|
|
orch._runner.cancel_run.assert_called_once_with("run-1")
|
|
|
|
def test_get_run_status_delegates(self):
|
|
"""get_run_status delegates to TranslationResultAggregator."""
|
|
orch, _, _, _, _, MockAggregator = self._make_orchestrator()
|
|
orch._aggregator = MockAggregator.return_value
|
|
orch._aggregator.get_run_status.return_value = {"status": "COMPLETED"}
|
|
|
|
result = orch.get_run_status("run-1")
|
|
|
|
orch._aggregator.get_run_status.assert_called_once_with("run-1")
|
|
|
|
def test_get_run_records_delegates(self):
|
|
"""get_run_records delegates to TranslationResultAggregator."""
|
|
orch, _, _, _, _, MockAggregator = self._make_orchestrator()
|
|
orch._aggregator = MockAggregator.return_value
|
|
orch._aggregator.get_run_records.return_value = {"items": [], "total": 0}
|
|
|
|
result = orch.get_run_records("run-1", page=2, page_size=10, status_filter="failed",
|
|
deduplicate=True)
|
|
|
|
orch._aggregator.get_run_records.assert_called_once_with(
|
|
"run-1", 2, 10, "failed", deduplicate=True,
|
|
)
|
|
|
|
def test_get_run_history_delegates(self):
|
|
"""get_run_history delegates to TranslationResultAggregator."""
|
|
orch, _, _, _, _, MockAggregator = self._make_orchestrator()
|
|
orch._aggregator = MockAggregator.return_value
|
|
orch._aggregator.get_run_history.return_value = (5, [{"id": "r1"}])
|
|
|
|
total, items = orch.get_run_history("job-1", page=1, page_size=20)
|
|
|
|
orch._aggregator.get_run_history.assert_called_once_with("job-1", 1, 20)
|
|
assert total == 5
|
|
assert items == [{"id": "r1"}]
|
|
|
|
def test_generate_and_insert_sql_backward_compat(self):
|
|
"""_generate_and_insert_sql creates SQLInsertService and delegates."""
|
|
orch, db, config, _, _, _ = self._make_orchestrator()
|
|
mock_job = MagicMock()
|
|
mock_run = MagicMock()
|
|
|
|
# SQLInsertService is imported INSIDE the method from .orchestrator_sql
|
|
with patch(
|
|
"src.plugins.translate.orchestrator_sql.SQLInsertService"
|
|
) as MockSQL:
|
|
mock_svc = MockSQL.return_value
|
|
mock_svc.generate_and_insert_sql.return_value = {"status": "success"}
|
|
|
|
import asyncio
|
|
result = asyncio.run(orch._generate_and_insert_sql(mock_job, mock_run))
|
|
|
|
MockSQL.assert_called_once_with(db, config, orch.event_log)
|
|
mock_svc.generate_and_insert_sql.assert_called_once_with(mock_job, mock_run)
|
|
|
|
def test_update_language_stats_delegates(self):
|
|
"""_update_language_stats delegates to TranslationResultAggregator."""
|
|
orch, _, _, _, _, MockAggregator = self._make_orchestrator()
|
|
orch._aggregator = MockAggregator.return_value
|
|
stats_map = {"ru": MagicMock()}
|
|
|
|
orch._update_language_stats("run-1", stats_map)
|
|
|
|
orch._aggregator.update_language_stats.assert_called_once_with("run-1", stats_map)
|
|
|
|
def test_initializes_event_log_and_components(self):
|
|
"""Constructor initializes all sub-components."""
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
|
|
with patch(
|
|
"src.plugins.translate.orchestrator.TranslationPlanner"
|
|
) as MockPlanner, patch(
|
|
"src.plugins.translate.orchestrator.TranslationStageRunner"
|
|
) as MockRunner, patch(
|
|
"src.plugins.translate.orchestrator.TranslationResultAggregator"
|
|
) as MockAggregator:
|
|
orch = TranslationOrchestrator(db, config, "user")
|
|
|
|
assert orch.db == db
|
|
assert orch.config_manager == config
|
|
assert orch.current_user == "user"
|
|
assert orch.event_log is not None
|
|
MockPlanner.assert_called_once_with(db, orch.event_log, "user")
|
|
MockRunner.assert_called_once_with(db, config, orch.event_log, "user")
|
|
MockAggregator.assert_called_once_with(db, orch.event_log)
|
|
# #endregion Test.Orchestrator
|