281 lines
11 KiB
Python
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
|