Files
ss-tools/backend/tests/plugins/translate/test_orchestrator_config.py
busya f75c15dbc6 test: massive coverage expansion — 15 new test modules + assistant tool fixes + orthogonal testing
- 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
2026-06-15 15:38:59 +03:00

167 lines
6.9 KiB
Python

# #region Test.OrchestratorConfig [C:3] [TYPE Module] [SEMANTICS test, translate, config, hash, snapshot]
# @BRIEF Tests for orchestrator_config.py — compute_config_hash, compute_dict_snapshot_hash.
# @RELATION BINDS_TO -> [orchestrator_config]
# @TEST_CONTRACT: compute_config_hash -> str | deterministic 16-char hash from job config
# @TEST_CONTRACT: compute_dict_snapshot_hash -> str | hash from dictionary entries
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.orchestrator_config import (
compute_config_hash,
compute_dict_snapshot_hash,
)
class TestComputeConfigHash:
"""compute_config_hash — deterministic hash from job config."""
def _make_job(self, overrides: dict | None = None) -> MagicMock:
"""Create a mock job with default config."""
job = MagicMock()
job.source_dialect = "postgresql"
job.target_dialect = "clickhouse"
job.source_datasource_id = "ds-1"
job.translation_column = "name"
job.context_columns = ["desc"]
job.target_languages = ["ru", "de"]
job.provider_id = "provider-1"
job.batch_size = 50
job.upsert_strategy = "MERGE"
if overrides:
for k, v in overrides.items():
setattr(job, k, v)
return job
def test_hash_length(self):
"""Hash is 16 characters."""
job = self._make_job()
h = compute_config_hash(job)
assert isinstance(h, str)
assert len(h) == 16
def test_deterministic(self):
"""Same config produces same hash."""
job1 = self._make_job()
job2 = self._make_job()
assert compute_config_hash(job1) == compute_config_hash(job2)
def test_different_config_different_hash(self):
"""Different config produces different hash."""
job1 = self._make_job({"batch_size": 50})
job2 = self._make_job({"batch_size": 100})
assert compute_config_hash(job1) != compute_config_hash(job2)
def test_source_dialect_affects_hash(self):
"""Changing source_dialect changes hash."""
job1 = self._make_job({"source_dialect": "postgresql"})
job2 = self._make_job({"source_dialect": "mysql"})
assert compute_config_hash(job1) != compute_config_hash(job2)
def test_target_languages_sorted(self):
"""target_languages are sorted before hashing."""
job1 = self._make_job({"target_languages": ["de", "ru"]})
job2 = self._make_job({"target_languages": ["ru", "de"]})
assert compute_config_hash(job1) == compute_config_hash(job2)
def test_target_languages_empty(self):
"""Empty target_languages handled."""
job = self._make_job({"target_languages": None})
h = compute_config_hash(job)
assert isinstance(h, str)
assert len(h) == 16
class TestComputeDictSnapshotHash:
"""compute_dict_snapshot_hash — dictionary state hash."""
def test_no_dictionary_links(self):
"""No dictionary links returns hash of empty bytes."""
db = MagicMock()
db.query.return_value.filter.return_value.all.return_value = []
h = compute_dict_snapshot_hash(db, "job-1")
assert isinstance(h, str)
assert len(h) == 16
def test_with_dictionary_links(self):
"""Dictionary links with entries produce a hash."""
db = MagicMock()
mock_link = MagicMock()
mock_link.dictionary_id = "dict-1"
db.query.return_value.filter.return_value.all.return_value = [mock_link]
db.query.return_value.filter.return_value.scalar.return_value = 5 # entry_count
db.query.return_value.filter.return_value.scalar.side_effect = [
5, # entry_count for dict-1
datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc), # max_updated
]
h = compute_dict_snapshot_hash(db, "job-1")
assert isinstance(h, str)
assert len(h) == 16
def test_deterministic_with_same_data(self):
"""Same data produces same hash."""
db1 = MagicMock()
db2 = MagicMock()
mock_link = MagicMock()
mock_link.dictionary_id = "dict-1"
db1.query.return_value.filter.return_value.all.return_value = [mock_link]
db2.query.return_value.filter.return_value.all.return_value = [mock_link]
db1.query.return_value.filter.return_value.scalar.side_effect = [5, datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc)]
db2.query.return_value.filter.return_value.scalar.side_effect = [5, datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc)]
h1 = compute_dict_snapshot_hash(db1, "job-1")
h2 = compute_dict_snapshot_hash(db2, "job-1")
assert h1 == h2
def test_different_entry_count_changes_hash(self):
"""Different entry count produces different hash."""
db1 = MagicMock()
db2 = MagicMock()
mock_link = MagicMock()
mock_link.dictionary_id = "dict-1"
db1.query.return_value.filter.return_value.all.return_value = [mock_link]
db2.query.return_value.filter.return_value.all.return_value = [mock_link]
db1.query.return_value.filter.return_value.scalar.side_effect = [5, datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc)]
db2.query.return_value.filter.return_value.scalar.side_effect = [10, datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc)]
h1 = compute_dict_snapshot_hash(db1, "job-1")
h2 = compute_dict_snapshot_hash(db2, "job-1")
assert h1 != h2
def test_max_updated_none_handled(self):
"""When max_updated is None, '0' is used."""
db = MagicMock()
mock_link = MagicMock()
mock_link.dictionary_id = "dict-1"
db.query.return_value.filter.return_value.all.return_value = [mock_link]
db.query.return_value.filter.return_value.scalar.side_effect = [5, None]
h = compute_dict_snapshot_hash(db, "job-1")
assert isinstance(h, str)
assert len(h) == 16
def test_multiple_dictionaries(self):
"""Multiple dictionary links handled."""
db = MagicMock()
link1 = MagicMock()
link1.dictionary_id = "dict-a"
link2 = MagicMock()
link2.dictionary_id = "dict-b"
db.query.return_value.filter.return_value.all.return_value = [link2, link1] # unsorted
db.query.return_value.filter.return_value.scalar.side_effect = [
3, datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc), # dict-a
7, datetime(2026, 2, 1, 8, 0, 0, tzinfo=timezone.utc), # dict-b
]
h = compute_dict_snapshot_hash(db, "job-1")
assert isinstance(h, str)
assert len(h) == 16
# #endregion Test.OrchestratorConfig