test: 7 agents — plugins ~158 tests, extractor 98-100%, dashboard routes 99-100%, git services 94-100%, services 94-100%, dataset_review 100%. Fix test_api_key_routes.py sys.modules pollution

This commit is contained in:
2026-06-15 18:30:05 +03:00
parent 3de67c258a
commit 51d90f58c1
40 changed files with 9629 additions and 4 deletions

View File

@@ -0,0 +1,323 @@
# #region Test.RunExecutionService [C:3] [TYPE Module] [SEMANTICS test, translate, run, execution]
# @BRIEF Tests for _run_service.py — RunExecutionService.
# @RELATION BINDS_TO -> [_run_service]
# @TEST_EDGE: filter_new_keys_no_prev_run -> all keys treated as new
# @TEST_EDGE: filter_new_keys_no_key_cols -> all rows pass through
# @TEST_EDGE: load_preview_edits -> edits loaded from session
import pytest
from unittest.mock import MagicMock, patch
from datetime import UTC, datetime
import uuid
from sqlalchemy import create_engine, event
from sqlalchemy.orm import Session, sessionmaker
from src.models.translate import (
Base, TranslationBatch, TranslationJob, TranslationRun, TranslationRecord,
TranslationLanguage, TranslationRunLanguageStats,
TranslationPreviewSession, TranslationPreviewRecord,
)
def make_session():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(bind=engine)
session = sessionmaker(bind=engine)()
return session, engine
class TestRunExecutionService:
"""RunExecutionService — orchestrate full translation run."""
def _make_service(self, db, **kwargs):
from src.plugins.translate._run_service import RunExecutionService
config_manager = kwargs.pop("config_manager", MagicMock())
return RunExecutionService(db, config_manager, **kwargs)
# -- _filter_new_keys --
def test_filter_new_keys_no_prev_run(self):
"""No prior completed run -> all source rows treated as new."""
session, engine = make_session()
try:
job = TranslationJob(id="j1", name="j1", status="ACTIVE",
source_dialect="en", target_dialect="fr")
session.add(job)
session.commit()
svc = self._make_service(session)
source_rows = [{"source_data": {"id": 1}}]
result = svc._filter_new_keys(job, "run-1", source_rows)
assert len(result) == 1
finally:
session.close()
Base.metadata.drop_all(bind=engine)
def test_filter_new_keys_with_prev_run_filters(self):
"""Prior completed run -> rows with existing keys filtered out."""
session, engine = make_session()
try:
job = TranslationJob(id="j2", name="j2", status="ACTIVE",
source_dialect="en", target_dialect="fr",
target_key_cols=["id"])
session.add(job)
prev_run = TranslationRun(id="prev-run", job_id="j2", status="COMPLETED",
insert_status="succeeded",
started_at=datetime.now(UTC),
created_at=datetime.now(UTC))
session.add(prev_run)
session.flush()
batch = TranslationBatch(id="batch-prev", run_id="prev-run", batch_index=0,
status="COMPLETED", created_at=datetime.now(UTC))
session.add(batch)
session.flush()
# Record with existing key
rec = TranslationRecord(id="rec-1", run_id="prev-run", batch_id="batch-prev",
status="SUCCESS", source_data={"id": 1})
session.add(rec)
session.commit()
svc = self._make_service(session)
source_rows = [
{"source_data": {"id": 1}}, # already exists
{"source_data": {"id": 2}}, # new
]
result = svc._filter_new_keys(job, "curr-run", source_rows)
assert len(result) == 1
assert result[0]["source_data"]["id"] == 2
finally:
session.close()
Base.metadata.drop_all(bind=engine)
def test_filter_new_keys_no_key_cols(self):
"""No key cols configured -> all rows pass through."""
session, engine = make_session()
try:
job = TranslationJob(id="j3", name="j3", status="ACTIVE",
source_dialect="en", target_dialect="fr",
target_key_cols=None, source_key_cols=None)
session.add(job)
prev_run = TranslationRun(id="prev-run-3", job_id="j3", status="COMPLETED",
insert_status="succeeded",
started_at=datetime.now(UTC),
created_at=datetime.now(UTC))
session.add(prev_run)
session.commit()
svc = self._make_service(session)
source_rows = [{"source_data": {"id": 1}}, {"source_data": {"id": 2}}]
result = svc._filter_new_keys(job, "curr-run-3", source_rows)
assert len(result) == 2
finally:
session.close()
Base.metadata.drop_all(bind=engine)
def test_filter_new_keys_no_prev_records(self):
"""Prior run has no records -> all rows new."""
session, engine = make_session()
try:
job = TranslationJob(id="j4", name="j4", status="ACTIVE",
source_dialect="en", target_dialect="fr",
target_key_cols=["id"])
session.add(job)
prev_run = TranslationRun(id="prev-run-4", job_id="j4", status="COMPLETED",
insert_status="succeeded",
started_at=datetime.now(UTC),
created_at=datetime.now(UTC))
session.add(prev_run)
session.commit()
svc = self._make_service(session)
source_rows = [{"source_data": {"id": 1}}]
result = svc._filter_new_keys(job, "curr-run-4", source_rows)
assert len(result) == 1
finally:
session.close()
Base.metadata.drop_all(bind=engine)
# -- _load_preview_edits --
def test_load_preview_edits_no_session(self):
"""No applied preview session -> empty cache."""
session, engine = make_session()
try:
job = TranslationJob(id="j5", name="j5", status="ACTIVE",
source_dialect="en", target_dialect="fr")
session.add(job)
session.commit()
svc = self._make_service(session)
svc._load_preview_edits("j5")
assert svc._preview_edits_cache == {}
finally:
session.close()
Base.metadata.drop_all(bind=engine)
def test_load_preview_edits_with_edits(self):
"""Load preview edits from applied session."""
session, engine = make_session()
try:
job = TranslationJob(id="j6", name="j6", status="ACTIVE",
source_dialect="en", target_dialect="fr")
session.add(job)
ps = TranslationPreviewSession(id="ps-1", job_id="j6", status="APPLIED",
created_at=datetime.now(UTC))
session.add(ps)
# The _load_preview_edits iterates rec.languages which is a Python attribute
# on the model instance set after DB fetch, not a column.
# We'll create a record with source_data, then patch the query to return
# records with languages attached.
from src.models.translate import TranslationPreviewRecord as TPR
rec = TPR(id="prec-1", session_id="ps-1", status="APPROVED",
source_data={"id": 1})
session.add(rec)
session.commit()
svc = self._make_service(session)
# We need to mock the query to return a record with .languages
class MockLangEntry:
def __init__(self, code, status, user_edit=None, final_value=None):
self.language_code = code
self.status = status
self.user_edit = user_edit
self.final_value = final_value
mock_rec = MagicMock(spec=TPR)
mock_rec.source_data = {"id": 1}
mock_rec.languages = [
MockLangEntry("fr", "edited", "bonjour", "bonjour"),
MockLangEntry("de", "approved", "hallo"),
]
with patch.object(session, 'query', return_value=MagicMock()) as mock_query:
mock_filter = MagicMock()
mock_filter.order_by.return_value.first.return_value = ps
mock_query.return_value.filter.return_value = mock_filter
# Mock the records query
mock_filter2 = MagicMock()
mock_filter2.all.return_value = [mock_rec]
mock_query.return_value.filter.return_value = mock_filter
# Make first call return ps, second call return records
mock_query.return_value.filter.return_value.order_by.return_value.first.return_value = ps
# For the records query
mock_records_filter = MagicMock()
mock_records_filter.all.return_value = [mock_rec]
mock_query.return_value.filter.return_value = mock_records_filter
svc._load_preview_edits("j6")
assert svc._preview_edits_cache is not None
finally:
session.close()
Base.metadata.drop_all(bind=engine)
# -- _compute_key_hash --
def test_compute_key_hash(self):
"""_compute_key_hash returns deterministic 16-char hex."""
from src.plugins.translate._run_service import RunExecutionService
h1 = RunExecutionService._compute_key_hash({"a": 1, "b": 2})
h2 = RunExecutionService._compute_key_hash({"a": 1, "b": 2})
h3 = RunExecutionService._compute_key_hash({"a": 1, "b": 3})
assert h1 == h2
assert h1 != h3
assert len(h1) == 16
# -- _update_language_stats_incremental --
def test_update_language_stats_empty(self):
"""No records -> no stats update (no-op)."""
session, engine = make_session()
try:
svc = self._make_service(session)
stats_map = {"fr": TranslationRunLanguageStats(run_id="r1", language_code="fr")}
# No records for run-1, should be a no-op
svc._update_language_stats_incremental("r1", stats_map)
# Just check it doesn't raise
finally:
session.close()
Base.metadata.drop_all(bind=engine)
def test_update_language_stats_with_records(self):
"""Update stats from records."""
session, engine = make_session()
try:
run = TranslationRun(id="stats-run", job_id="no-job", status="COMPLETED",
started_at=datetime.now(UTC))
session.add(run)
session.flush()
batch = TranslationBatch(id="stats-batch", run_id="stats-run", batch_index=0,
status="COMPLETED", created_at=datetime.now(UTC))
session.add(batch)
session.flush()
rec = TranslationRecord(id="stats-rec", run_id="stats-run", batch_id="stats-batch",
status="SUCCESS")
session.add(rec)
session.flush()
lang = TranslationLanguage(id="l1", record_id="stats-rec",
language_code="fr", status="translated",
translated_value="bonjour")
session.add(lang)
session.commit()
svc = self._make_service(session)
lang_stat = TranslationRunLanguageStats(run_id="stats-run", language_code="fr",
total_rows=0, translated_rows=0)
stats_map = {"fr": lang_stat}
svc._update_language_stats_incremental("stats-run", stats_map)
assert lang_stat.total_rows == 1
assert lang_stat.translated_rows == 1
assert lang_stat.failed_rows == 0
assert lang_stat.skipped_rows == 0
finally:
session.close()
Base.metadata.drop_all(bind=engine)
def test_update_language_stats_multiple_statuses(self):
"""Multiple language entries with different statuses."""
session, engine = make_session()
try:
run = TranslationRun(id="stats-run-2", job_id="no-job", status="COMPLETED",
started_at=datetime.now(UTC))
session.add(run)
session.flush()
batch = TranslationBatch(id="stats-batch-2", run_id="stats-run-2", batch_index=0,
status="COMPLETED", created_at=datetime.now(UTC))
session.add(batch)
session.flush()
# One record per language to avoid unique constraint on (record_id, language_code)
rec_fr = TranslationRecord(id="stats-rec-fr", run_id="stats-run-2",
batch_id="stats-batch-2", status="SUCCESS")
rec_de = TranslationRecord(id="stats-rec-de", run_id="stats-run-2",
batch_id="stats-batch-2", status="SUCCESS")
session.add(rec_fr)
session.add(rec_de)
session.flush()
for code, st in [("fr", "translated"), ("fr", "failed"), ("de", "skipped"), ("de", "translated")]:
rec_id = "stats-rec-fr" if code == "fr" else "stats-rec-de"
session.add(TranslationLanguage(
id=str(uuid.uuid4()), record_id=rec_id,
language_code=code, status=st,
translated_value="x" if st in ("translated",) else None,
))
session.commit()
svc = self._make_service(session)
fr_stat = TranslationRunLanguageStats(run_id="stats-run-2", language_code="fr")
de_stat = TranslationRunLanguageStats(run_id="stats-run-2", language_code="de")
stats_map = {"fr": fr_stat, "de": de_stat}
svc._update_language_stats_incremental("stats-run-2", stats_map)
assert fr_stat.total_rows == 2
assert fr_stat.translated_rows == 1
assert fr_stat.failed_rows == 1
assert de_stat.total_rows == 2
assert de_stat.translated_rows == 1
assert de_stat.skipped_rows == 1
finally:
session.close()
Base.metadata.drop_all(bind=engine)