- 10 translate plugin test files (100% coverage on 12 modules) - assistant/handler tools: 85+ tests covering dispatch, registry, resolvers, routes, llm_planner, 13 tool handlers - clean release: artifact_catalog_loader, mappers, approval, publication tests - API routes: translate_helpers, validation_service extensions, datasets to 100% - notifications: providers/service tests - services: profile_preference_service - docs/orthogonal-test-report.md — full speckit.tests audit - Fixes: 3 git_base async mock failures, 4 assistant handler permission-check patches - .gitignore: coverage artifacts
163 lines
6.8 KiB
Python
163 lines
6.8 KiB
Python
# #region Test.PreviewSessionOps [C:3] [TYPE Module] [SEMANTICS test, translate, preview, session, query]
|
|
# @BRIEF Tests for preview_session_ops.py — get_preview_session.
|
|
# @RELATION BINDS_TO -> [preview_session_ops]
|
|
# @TEST_CONTRACT: get_preview_session -> dict | returns latest session with records
|
|
# @TEST_EDGE: no_session_found -> raises ValueError
|
|
|
|
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
|
|
|
|
import pytest
|
|
|
|
from src.plugins.translate.preview_session_ops import get_preview_session
|
|
|
|
|
|
class TestGetPreviewSession:
|
|
"""get_preview_session — fetch latest preview session for a job."""
|
|
|
|
def _make_session(self, overrides: dict | None = None) -> MagicMock:
|
|
"""Create a mock preview session."""
|
|
session = MagicMock()
|
|
session.id = "session-1"
|
|
session.job_id = "job-1"
|
|
session.status = "APPLIED"
|
|
session.created_by = "user-1"
|
|
session.created_at = datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc)
|
|
session.expires_at = datetime(2026, 2, 15, 12, 0, 0, tzinfo=timezone.utc)
|
|
if overrides:
|
|
for k, v in overrides.items():
|
|
setattr(session, k, v)
|
|
return session
|
|
|
|
def _make_record(self) -> MagicMock:
|
|
"""Create a mock preview record."""
|
|
r = MagicMock()
|
|
r.id = "rec-1"
|
|
r.source_sql = "SELECT * FROM foo"
|
|
r.target_sql = "SELECT * FROM bar"
|
|
r.source_object_type = "table"
|
|
r.source_object_id = "obj-1"
|
|
r.source_object_name = "foo"
|
|
r.status = "done"
|
|
r.feedback = None
|
|
r.languages = []
|
|
return r
|
|
|
|
def _make_job(self, target_languages=None, target_dialect=None) -> MagicMock:
|
|
"""Create a mock translation job."""
|
|
job = MagicMock()
|
|
job.id = "job-1"
|
|
job.target_languages = target_languages if target_languages is not None else ["ru", "de"]
|
|
job.target_dialect = target_dialect if target_dialect else "clickhouse"
|
|
return job
|
|
|
|
def _setup_db_mock(self, session, records, job):
|
|
"""Configure db mock chain for the three queries in get_preview_session."""
|
|
db = MagicMock()
|
|
# Query 1: TranslationPreviewSession (filter + order_by + first)
|
|
db.query.return_value.filter.return_value.order_by.return_value.first.return_value = session
|
|
# Query 2: TranslationPreviewRecord (filter + all)
|
|
db.query.return_value.filter.return_value.all.return_value = records
|
|
# Query 3: TranslationJob (filter + first — no order_by)
|
|
db.query.return_value.filter.return_value.first.return_value = job
|
|
return db
|
|
|
|
def test_returns_session_with_records(self):
|
|
"""Returns session dict with serialized records."""
|
|
session = self._make_session()
|
|
record = self._make_record()
|
|
job = self._make_job()
|
|
db = self._setup_db_mock(session, [record], job)
|
|
|
|
with patch("src.plugins.translate.preview_session_ops._serialize_preview_record") as mock_ser:
|
|
mock_ser.return_value = {"id": "rec-1", "source_sql": "SELECT * FROM foo"}
|
|
result = get_preview_session(db, "job-1")
|
|
|
|
assert result["id"] == "session-1"
|
|
assert result["job_id"] == "job-1"
|
|
assert result["status"] == "APPLIED"
|
|
assert result["created_by"] == "user-1"
|
|
assert "created_at" in result
|
|
assert "expires_at" in result
|
|
assert result["target_languages"] == ["ru", "de"]
|
|
assert len(result["records"]) == 1
|
|
assert result["records"][0]["id"] == "rec-1"
|
|
|
|
def test_no_session_found_raises(self):
|
|
"""No session found raises ValueError."""
|
|
db = MagicMock()
|
|
db.query.return_value.filter.return_value.order_by.return_value.first.return_value = None
|
|
|
|
with pytest.raises(ValueError, match="No preview session found for job 'job-1'"):
|
|
get_preview_session(db, "job-1")
|
|
|
|
def test_job_not_found_defaults_to_en(self):
|
|
"""Job not found falls back to ['en'] (last fallback)."""
|
|
session = self._make_session()
|
|
db = MagicMock()
|
|
db.query.return_value.filter.return_value.order_by.return_value.first.return_value = session
|
|
db.query.return_value.filter.return_value.all.return_value = []
|
|
# No job found -> first (no order_by) returns None
|
|
db.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
with patch("src.plugins.translate.preview_session_ops._serialize_preview_record") as mock_ser:
|
|
result = get_preview_session(db, "job-1")
|
|
|
|
# When job is None -> the fallback is ["en"]
|
|
assert result["target_languages"] == ["en"]
|
|
|
|
def test_job_no_target_languages_uses_dialect(self):
|
|
"""Job with None target_languages uses target_dialect."""
|
|
session = self._make_session()
|
|
job = self._make_job(target_dialect="postgresql")
|
|
job.target_languages = None # explicitly override to None
|
|
db = self._setup_db_mock(session, [], job)
|
|
|
|
with patch("src.plugins.translate.preview_session_ops._serialize_preview_record") as mock_ser:
|
|
result = get_preview_session(db, "job-1")
|
|
|
|
assert result["target_languages"] == ["postgresql"]
|
|
|
|
def test_target_languages_not_list_normalized(self):
|
|
"""target_languages that is not a list gets wrapped in list."""
|
|
session = self._make_session()
|
|
job = self._make_job(target_languages="ru") # string instead of list
|
|
db = self._setup_db_mock(session, [], job)
|
|
|
|
with patch("src.plugins.translate.preview_session_ops._serialize_preview_record") as mock_ser:
|
|
result = get_preview_session(db, "job-1")
|
|
|
|
assert result["target_languages"] == ["ru"]
|
|
|
|
def test_session_expires_at_none(self):
|
|
"""Session without expires_at returns None for that field."""
|
|
session = self._make_session({"expires_at": None})
|
|
job = self._make_job()
|
|
db = self._setup_db_mock(session, [], job)
|
|
|
|
with patch("src.plugins.translate.preview_session_ops._serialize_preview_record") as mock_ser:
|
|
result = get_preview_session(db, "job-1")
|
|
|
|
assert result["expires_at"] is None
|
|
|
|
def test_empty_records(self):
|
|
"""No records returns empty records list."""
|
|
session = self._make_session()
|
|
job = self._make_job()
|
|
db = self._setup_db_mock(session, [], job)
|
|
|
|
with patch("src.plugins.translate.preview_session_ops._serialize_preview_record"):
|
|
result = get_preview_session(db, "job-1")
|
|
|
|
assert result["records"] == []
|
|
# #endregion Test.PreviewSessionOps
|