Files
ss-tools/backend/tests/plugins/translate/test_orchestrator_query.py

281 lines
11 KiB
Python

# #region Test.OrchestratorQuery [C:3] [TYPE Module] [SEMANTICS test, translate, query, records, history]
# @BRIEF Tests for orchestrator_query.py — get_run_records, get_run_history.
# @RELATION BINDS_TO -> [orchestrator_query]
# @TEST_CONTRACT: get_run_records -> dict | paginated records with optional deduplicate
# @TEST_CONTRACT: get_run_history -> tuple[int, list[dict]] | paginated run history
# @TEST_EDGE: deduplicate_empty_source_hash
# @TEST_EDGE: status_filter
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 datetime import datetime, timezone
from unittest.mock import MagicMock, patch, ANY
import pytest
from src.plugins.translate.orchestrator_query import get_run_records, get_run_history
class TestGetRunRecords:
"""get_run_records — paginated run records."""
def _make_record(self, rec_id: str, **kwargs):
rec = MagicMock()
rec.id = rec_id
rec.batch_id = "batch-1"
rec.source_sql = "SELECT * FROM t"
rec.target_sql = "SELECT * FROM t_translated"
rec.source_object_type = "table"
rec.source_object_id = "123"
rec.source_object_name = "my_table"
rec.source_data = {"col": "val"}
rec.source_hash = f"hash_{rec_id}"
rec.status = "translated"
rec.error_message = None
rec.created_at = datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc)
rec.languages = []
for k, v in kwargs.items():
setattr(rec, k, v)
return rec
def test_basic_query(self):
"""Returns paginated records with metadata."""
db = MagicMock()
# Mock chain: db.query().options().filter()
options_mock = MagicMock()
filter_mock = MagicMock()
options_filter_mock = MagicMock()
db.query.return_value.options.return_value = options_mock
options_mock.filter.return_value = options_filter_mock
options_filter_mock.count.return_value = 2
options_filter_mock.order_by.return_value.offset.return_value.limit.return_value.all.return_value = [
self._make_record("r1"), self._make_record("r2"),
]
result = get_run_records(db, "run-1")
assert result["total"] == 2
assert len(result["items"]) == 2
assert result["page"] == 1
assert result["page_size"] == 50
assert result["status_filter"] is None
assert result["deduplicate"] is False
def test_with_status_filter(self):
"""Status filter applied to query."""
db = MagicMock()
options_mock = MagicMock()
filter_mock1 = MagicMock()
filter_mock2 = MagicMock()
db.query.return_value.options.return_value = options_mock
options_mock.filter.return_value = filter_mock1
filter_mock1.filter.return_value = filter_mock2
filter_mock2.count.return_value = 1
filter_mock2.order_by.return_value.offset.return_value.limit.return_value.all.return_value = [
self._make_record("r1", status="failed"),
]
result = get_run_records(db, "run-1", status_filter="failed")
assert result["total"] == 1
assert result["status_filter"] == "failed"
def test_deduplicate_applied(self):
"""Deduplicate flag applies exclusion logic."""
db = MagicMock()
options_mock = MagicMock()
filter_mock1 = MagicMock()
filter_mock2 = MagicMock()
db.query.return_value.options.return_value = options_mock
options_mock.filter.return_value = filter_mock1
filter_mock1.filter.return_value = filter_mock2
filter_mock2.count.return_value = 1
filter_mock2.order_by.return_value.offset.return_value.limit.return_value.all.return_value = [
self._make_record("r1", source_hash="abc123"),
]
result = get_run_records(db, "run-1", deduplicate=True)
assert result["deduplicate"] is True
assert len(result["items"]) == 1
def test_empty_records(self):
"""No records returns empty list."""
db = MagicMock()
options_mock = MagicMock()
db.query.return_value.options.return_value = options_mock
options_mock.filter.return_value.count.return_value = 0
options_mock.filter.return_value.order_by.return_value.offset.return_value.limit.return_value.all.return_value = []
result = get_run_records(db, "run-1")
assert result["total"] == 0
assert result["items"] == []
def test_record_languages_included(self):
"""Language entries included in record dict."""
db = MagicMock()
lang = MagicMock()
lang.language_code = "ru"
lang.translated_value = "привет"
lang.final_value = None
lang.source_language_detected = "en"
lang.status = "translated"
lang.needs_review = False
rec = self._make_record("r1", languages=[lang])
options_mock = MagicMock()
db.query.return_value.options.return_value = options_mock
options_mock.filter.return_value.count.return_value = 1
options_mock.filter.return_value.order_by.return_value.offset.return_value.limit.return_value.all.return_value = [rec]
result = get_run_records(db, "run-1")
assert len(result["items"][0]["languages"]) == 1
assert result["items"][0]["languages"][0]["language_code"] == "ru"
assert result["items"][0]["languages"][0]["final_value"] == "привет"
def test_record_without_created_at(self):
"""Record without created_at returns None for isoformat."""
db = MagicMock()
rec = self._make_record("r1", created_at=None)
options_mock = MagicMock()
db.query.return_value.options.return_value = options_mock
options_mock.filter.return_value.count.return_value = 1
options_mock.filter.return_value.order_by.return_value.offset.return_value.limit.return_value.all.return_value = [rec]
result = get_run_records(db, "run-1")
assert result["items"][0]["created_at"] is None
def test_pagination_params(self):
"""Pagination params affect offset and limit."""
db = MagicMock()
rec = self._make_record("r1")
options_mock = MagicMock()
db.query.return_value.options.return_value = options_mock
options_mock.filter.return_value.count.return_value = 10
options_mock.filter.return_value.order_by.return_value.offset.return_value.limit.return_value.all.return_value = [rec]
result = get_run_records(db, "run-1", page=2, page_size=10)
assert result["page"] == 2
assert result["page_size"] == 10
class TestGetRunHistory:
"""get_run_history — paginated run history for a job."""
def _make_run(self, run_id: str, **kwargs):
run = MagicMock()
run.id = run_id
run.job_id = "job-1"
run.status = "COMPLETED"
run.trigger_type = "manual"
run.started_at = datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc)
run.completed_at = datetime(2026, 1, 15, 13, 0, 0, tzinfo=timezone.utc)
run.error_message = None
run.total_records = 100
run.successful_records = 95
run.failed_records = 3
run.skipped_records = 2
run.cache_hits = 5
run.insert_status = "success"
run.insert_method = "cte_insert"
run.connection_snapshot = {"db": "test"}
run.superset_execution_id = "q-1"
run.created_by = "user"
run.created_at = datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc)
for k, v in kwargs.items():
setattr(run, k, v)
return run
def test_basic_history(self):
"""Returns total count and run list."""
db = MagicMock()
runs = [self._make_run("r1"), self._make_run("r2")]
filter_mock = MagicMock()
db.query.return_value.filter.return_value = filter_mock
filter_mock.count.return_value = 2
filter_mock.order_by.return_value.offset.return_value.limit.return_value.all.return_value = runs
total, items = get_run_history(db, "job-1")
assert total == 2
assert len(items) == 2
assert items[0]["id"] == "r1"
assert items[0]["status"] == "COMPLETED"
def test_empty_history(self):
"""No runs returns total=0 and empty list."""
db = MagicMock()
filter_mock = MagicMock()
db.query.return_value.filter.return_value = filter_mock
filter_mock.count.return_value = 0
filter_mock.order_by.return_value.offset.return_value.limit.return_value.all.return_value = []
total, items = get_run_history(db, "job-1")
assert total == 0
assert items == []
def test_pagination_params(self):
"""Pagination affects offset and limit."""
db = MagicMock()
runs = [self._make_run("r1")]
filter_mock = MagicMock()
db.query.return_value.filter.return_value = filter_mock
filter_mock.count.return_value = 10
filter_mock.order_by.return_value.offset.return_value.limit.return_value.all.return_value = runs
total, items = get_run_history(db, "job-1", page=3, page_size=5)
assert total == 10
assert len(items) == 1
def test_run_without_dates(self):
"""Run without started_at/completed_at returns None."""
db = MagicMock()
run = self._make_run("r1", started_at=None, completed_at=None)
filter_mock = MagicMock()
db.query.return_value.filter.return_value = filter_mock
filter_mock.count.return_value = 1
filter_mock.order_by.return_value.offset.return_value.limit.return_value.all.return_value = [run]
total, items = get_run_history(db, "job-1")
assert items[0]["started_at"] is None
assert items[0]["completed_at"] is None
def test_all_numeric_fields_default_to_zero(self):
"""Numeric fields default to 0 when None."""
db = MagicMock()
run = self._make_run(
"r1", total_records=None, successful_records=None,
failed_records=None, skipped_records=None, cache_hits=None,
)
filter_mock = MagicMock()
db.query.return_value.filter.return_value = filter_mock
filter_mock.count.return_value = 1
filter_mock.order_by.return_value.offset.return_value.limit.return_value.all.return_value = [run]
total, items = get_run_history(db, "job-1")
assert items[0]["total_records"] == 0
assert items[0]["successful_records"] == 0
assert items[0]["failed_records"] == 0
assert items[0]["skipped_records"] == 0
assert items[0]["cache_hits"] == 0
# #endregion Test.OrchestratorQuery