chore: remainder — backend test infra, agent config, docker, i18n, frontend ui

- Backend: alembic env, config manager/models, dependencies, translate plugin
- Backend tests: async_sync_regression, integration tests, git services, test_agent
- Docker: docker-compose.yml updates
- Agent: qa-tester.md update, semantics-testing SKILL.md update
- Frontend: TopNavbar, sidebarNavigation, FeaturesSettings, FeatureGate
- i18n: assistant.json en/ru locale updates
- New: frontend/src/lib/components/agent/ directory
This commit is contained in:
2026-06-10 15:06:36 +03:00
parent 2b303f92b3
commit 143f14d516
34 changed files with 5693 additions and 178 deletions

View File

@@ -0,0 +1,499 @@
# #region DictionaryIntegrationTests [C:4] [TYPE Module] [SEMANTICS test, integration, dictionary, postgres, testcontainers]
# @BRIEF Integration tests for DictionaryCRUD and DictionaryEntryCRUD with real PostgreSQL.
# @RELATION BINDS_TO -> [DictionaryCRUD]
# @RELATION BINDS_TO -> [DictionaryEntryCRUD]
# @RELATION BINDS_TO -> [TerminologyDictionary]
# @RELATION BINDS_TO -> [DictionaryEntry]
# @TEST_CONTRACT DictionaryCRUD ->
# {
# invariants: [
# "create_dictionary persists with all fields",
# "update_dictionary modifies only specified fields",
# "delete_dictionary cascades to entries and job associations",
# "delete_dictionary blocked when attached to active jobs",
# "add_entry enforces uniqueness on (dict_id, source_term_norm, source_lang, target_lang)",
# "same term with different language pairs allowed"
# ]
# }
# @TEST_EDGE duplicate_entry -> ValueError on repeated normalized term
# @TEST_EDGE delete_active_job -> ValueError with active/scheduled message
# @TEST_EDGE same_term_different_lang_pair -> allowed (not duplicate)
# @TEST_EDGE invalid_regex -> ValueError on invalid regex pattern
# @TEST_INVARIANT unique_normalized -> VERIFIED_BY: [test_add_entry_duplicate_normalized, test_same_term_different_lang_pair_allowed]
# @TEST_INVARIANT cascade_integrity -> VERIFIED_BY: [test_delete_dictionary_cascades_to_entries]
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
import pytest
from sqlalchemy.orm import Session
from src.models.translate import (
DictionaryEntry,
TerminologyDictionary,
TranslationJob,
TranslationJobDictionary,
)
from src.plugins.translate.dictionary import DictionaryManager
# #region TestDictionaryCRUDIntegration [C:3] [TYPE Class]
# @BRIEF Integration tests for dictionary-level CRUD with real PostgreSQL.
class TestDictionaryCRUDIntegration:
"""Verify dictionary CRUD operations with real PostgreSQL."""
# region test_create_dictionary_persists_all_fields [C:2] [TYPE Function]
# @BRIEF Verify dictionary creation persists all fields.
def test_create_dictionary_persists_all_fields(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(
db_session,
name="Finance Terms",
description="Financial terminology mappings",
created_by="test_user",
is_active=True,
)
assert dictionary.id is not None
assert dictionary.name == "Finance Terms"
assert dictionary.description == "Financial terminology mappings"
assert dictionary.created_by == "test_user"
assert dictionary.is_active is True
assert dictionary.created_at is not None
# Verify persisted
fetched = DictionaryManager.get_dictionary(db_session, dictionary.id)
assert fetched.id == dictionary.id
assert fetched.name == "Finance Terms"
# endregion test_create_dictionary_persists_all_fields
# region test_update_dictionary_partial_update [C:2] [TYPE Function]
# @BRIEF Verify partial update only modifies specified fields.
def test_update_dictionary_partial_update(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(
db_session, name="Original", description="Original desc", created_by="user1",
)
original_id = dictionary.id
original_created_by = dictionary.created_by
# Update only name
updated = DictionaryManager.update_dictionary(
db_session, original_id, name="Updated Name",
)
assert updated.id == original_id
assert updated.name == "Updated Name"
assert updated.description == "Original desc" # Unchanged
assert updated.created_by == original_created_by # Unchanged
# endregion test_update_dictionary_partial_update
# region test_delete_dictionary_cascades_to_entries [C:2] [TYPE Function]
# @BRIEF Verify deleting dictionary removes all entries.
def test_delete_dictionary_cascades_to_entries(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="To Delete")
# Add entries
DictionaryManager.add_entry(
db_session, dictionary.id, "hello", "hola",
source_language="en", target_language="es",
)
DictionaryManager.add_entry(
db_session, dictionary.id, "world", "mundo",
source_language="en", target_language="es",
)
# Verify entries exist
entries, total = DictionaryManager.list_entries(db_session, dictionary.id)
assert total == 2
# Delete dictionary
DictionaryManager.delete_dictionary(db_session, dictionary.id)
# Verify dictionary gone
with pytest.raises(ValueError, match="Dictionary not found"):
DictionaryManager.get_dictionary(db_session, dictionary.id)
# Verify entries gone (FK cascade)
remaining_entries = (
db_session.query(DictionaryEntry)
.filter(DictionaryEntry.dictionary_id == dictionary.id)
.all()
)
assert len(remaining_entries) == 0
# endregion test_delete_dictionary_cascades_to_entries
# region test_delete_dictionary_blocked_by_active_job [C:2] [TYPE Function]
# @BRIEF Verify deletion blocked when attached to active/scheduled jobs.
def test_delete_dictionary_blocked_by_active_job(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Protected")
# Create active job with association
job = TranslationJob(
name="Active Job",
source_dialect="postgresql",
target_dialect="clickhouse",
status="ACTIVE",
created_by="test_user",
)
db_session.add(job)
db_session.flush()
link = TranslationJobDictionary(job_id=job.id, dictionary_id=dictionary.id)
db_session.add(link)
db_session.commit()
# Attempt delete should fail
with pytest.raises(ValueError, match="active/scheduled"):
DictionaryManager.delete_dictionary(db_session, dictionary.id)
# Verify dictionary still exists
fetched = DictionaryManager.get_dictionary(db_session, dictionary.id)
assert fetched is not None
# endregion test_delete_dictionary_blocked_by_active_job
# region test_delete_dictionary_blocked_by_scheduled_job [C:2] [TYPE Function]
# @BRIEF Verify deletion blocked when attached to SCHEDULED jobs.
def test_delete_dictionary_blocked_by_scheduled_job(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Scheduled Protected")
# Create scheduled job with association
job = TranslationJob(
name="Scheduled Job",
source_dialect="postgresql",
target_dialect="clickhouse",
status="SCHEDULED",
created_by="test_user",
)
db_session.add(job)
db_session.flush()
link = TranslationJobDictionary(job_id=job.id, dictionary_id=dictionary.id)
db_session.add(link)
db_session.commit()
with pytest.raises(ValueError, match="active/scheduled"):
DictionaryManager.delete_dictionary(db_session, dictionary.id)
# endregion test_delete_dictionary_blocked_by_scheduled_job
# region test_delete_dictionary_allowed_with_completed_job [C:2] [TYPE Function]
# @BRIEF Verify deletion allowed when only completed/failed jobs reference it.
def test_delete_dictionary_allowed_with_completed_job(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Completed OK")
# Create completed job with association
job = TranslationJob(
name="Completed Job",
source_dialect="postgresql",
target_dialect="clickhouse",
status="COMPLETED",
created_by="test_user",
)
db_session.add(job)
db_session.flush()
link = TranslationJobDictionary(job_id=job.id, dictionary_id=dictionary.id)
db_session.add(link)
db_session.commit()
# Should succeed
DictionaryManager.delete_dictionary(db_session, dictionary.id)
with pytest.raises(ValueError, match="Dictionary not found"):
DictionaryManager.get_dictionary(db_session, dictionary.id)
# endregion test_delete_dictionary_allowed_with_completed_job
# region test_list_dictionaries_pagination [C:2] [TYPE Function]
# @BRIEF Verify paginated listing works correctly.
def test_list_dictionaries_pagination(self, db_session: Session):
# Create 7 dictionaries
for i in range(7):
DictionaryManager.create_dictionary(db_session, name=f"Dict {i}")
# Page 1
dicts, total = DictionaryManager.list_dictionaries(db_session, page=1, page_size=3)
assert total == 7
assert len(dicts) == 3
# Page 2
dicts, total = DictionaryManager.list_dictionaries(db_session, page=2, page_size=3)
assert total == 7
assert len(dicts) == 3
# Page 3
dicts, total = DictionaryManager.list_dictionaries(db_session, page=3, page_size=3)
assert total == 7
assert len(dicts) == 1
# endregion test_list_dictionaries_pagination
# #endregion TestDictionaryCRUDIntegration
# #region TestDictionaryEntryCRUDIntegration [C:3] [TYPE Class]
# @BRIEF Integration tests for entry-level CRUD with real PostgreSQL.
class TestDictionaryEntryCRUDIntegration:
"""Verify entry CRUD operations with real PostgreSQL."""
# region test_add_entry_persists_all_fields [C:2] [TYPE Function]
# @BRIEF Verify entry creation persists all fields.
def test_add_entry_persists_all_fields(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Test")
entry = DictionaryManager.add_entry(
db_session,
dictionary.id,
"hello world",
"привет мир",
source_language="en",
target_language="ru",
context_notes="greeting",
is_regex=False,
)
assert entry.id is not None
assert entry.dictionary_id == dictionary.id
assert entry.source_term == "hello world"
assert entry.target_term == "привет мир"
assert entry.source_language == "en"
assert entry.target_language == "ru"
assert entry.context_notes == "greeting"
assert entry.is_regex is False
# Verify persisted
entries, total = DictionaryManager.list_entries(db_session, dictionary.id)
assert total == 1
assert entries[0].source_term == "hello world"
# endregion test_add_entry_persists_all_fields
# region test_add_entry_duplicate_normalized [C:2] [TYPE Function]
# @BRIEF Verify duplicate detection uses normalized (lowercased) term.
def test_add_entry_duplicate_normalized(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Norm Test")
DictionaryManager.add_entry(
db_session, dictionary.id, "Hello", "Hola",
source_language="en", target_language="es",
)
# Same term, different case — should be duplicate
with pytest.raises(ValueError, match="already exists"):
DictionaryManager.add_entry(
db_session, dictionary.id, "HELLO", "Bonjour",
source_language="en", target_language="es",
)
with pytest.raises(ValueError, match="already exists"):
DictionaryManager.add_entry(
db_session, dictionary.id, "hello", "Ciao",
source_language="en", target_language="es",
)
# endregion test_add_entry_duplicate_normalized
# region test_same_term_different_lang_pair_allowed [C:2] [TYPE Function]
# @BRIEF Verify same term with different language pair is allowed.
def test_same_term_different_lang_pair_allowed(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Multi Lang")
entry1 = DictionaryManager.add_entry(
db_session, dictionary.id, "hello", "привет",
source_language="en", target_language="ru",
)
entry2 = DictionaryManager.add_entry(
db_session, dictionary.id, "hello", "hallo",
source_language="en", target_language="de",
)
entry3 = DictionaryManager.add_entry(
db_session, dictionary.id, "hello", "bonjour",
source_language="en", target_language="fr",
)
assert entry1.id != entry2.id != entry3.id
assert entry1.target_language == "ru"
assert entry2.target_language == "de"
assert entry3.target_language == "fr"
entries, total = DictionaryManager.list_entries(db_session, dictionary.id)
assert total == 3
# endregion test_same_term_different_lang_pair_allowed
# region test_same_term_different_dictionary_allowed [C:2] [TYPE Function]
# @BRIEF Verify same term in different dictionaries is allowed.
def test_same_term_different_dictionary_allowed(self, db_session: Session):
dict1 = DictionaryManager.create_dictionary(db_session, name="Dict 1")
dict2 = DictionaryManager.create_dictionary(db_session, name="Dict 2")
entry1 = DictionaryManager.add_entry(
db_session, dict1.id, "hello", "hola",
source_language="en", target_language="es",
)
entry2 = DictionaryManager.add_entry(
db_session, dict2.id, "hello", "bonjour",
source_language="en", target_language="fr",
)
assert entry1.id != entry2.id
assert entry1.dictionary_id == dict1.id
assert entry2.dictionary_id == dict2.id
# endregion test_same_term_different_dictionary_allowed
# region test_edit_entry_updates_fields [C:2] [TYPE Function]
# @BRIEF Verify editing entry updates specified fields.
def test_edit_entry_updates_fields(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Edit Test")
entry = DictionaryManager.add_entry(
db_session, dictionary.id, "hello", "hola",
source_language="en", target_language="es",
context_notes="greeting",
)
updated = DictionaryManager.edit_entry(
db_session, entry.id,
target_term="HOLA!",
context_notes="formal greeting",
)
assert updated.target_term == "HOLA!"
assert updated.context_notes == "formal greeting"
assert updated.source_term == "hello" # Unchanged
# endregion test_edit_entry_updates_fields
# region test_edit_entry_source_term_checks_uniqueness [C:2] [TYPE Function]
# @BRIEF Verify editing source_term checks uniqueness constraint.
def test_edit_entry_source_term_checks_uniqueness(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Uniq Test")
DictionaryManager.add_entry(
db_session, dictionary.id, "hello", "hola",
source_language="en", target_language="es",
)
entry2 = DictionaryManager.add_entry(
db_session, dictionary.id, "world", "mundo",
source_language="en", target_language="es",
)
# Try to change entry2's source to "hello" — should fail
with pytest.raises(ValueError, match="already exists"):
DictionaryManager.edit_entry(db_session, entry2.id, source_term="HELLO")
# endregion test_edit_entry_source_term_checks_uniqueness
# region test_delete_entry [C:2] [TYPE Function]
# @BRIEF Verify entry deletion.
def test_delete_entry(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Delete Test")
entry = DictionaryManager.add_entry(
db_session, dictionary.id, "hello", "hola",
source_language="en", target_language="es",
)
DictionaryManager.delete_entry(db_session, entry.id)
entries, total = DictionaryManager.list_entries(db_session, dictionary.id)
assert total == 0
# endregion test_delete_entry
# region test_clear_entries [C:2] [TYPE Function]
# @BRIEF Verify clearing all entries for a dictionary.
def test_clear_entries(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Clear Test")
DictionaryManager.add_entry(
db_session, dictionary.id, "hello", "hola",
source_language="en", target_language="es",
)
DictionaryManager.add_entry(
db_session, dictionary.id, "world", "mundo",
source_language="en", target_language="es",
)
DictionaryManager.add_entry(
db_session, dictionary.id, "goodbye", "adiós",
source_language="en", target_language="es",
)
deleted = DictionaryManager.clear_entries(db_session, dictionary.id)
assert deleted == 3
entries, total = DictionaryManager.list_entries(db_session, dictionary.id)
assert total == 0
# endregion test_clear_entries
# region test_add_entry_regex_valid_pattern [C:2] [TYPE Function]
# @BRIEF Verify regex entry with valid pattern.
def test_add_entry_regex_valid_pattern(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Regex Valid")
entry = DictionaryManager.add_entry(
db_session, dictionary.id,
r"\d{3}-\d{2}-\d{4}",
"ID-MASKED",
source_language="en",
target_language="ru",
is_regex=True,
)
assert entry.is_regex is True
assert entry.source_term == r"\d{3}-\d{2}-\d{4}"
entries, total = DictionaryManager.list_entries(db_session, dictionary.id)
assert total == 1
assert entries[0].is_regex is True
# endregion test_add_entry_regex_valid_pattern
# region test_add_entry_regex_invalid_pattern_raises [C:2] [TYPE Function]
# @BRIEF Verify invalid regex pattern raises ValueError.
def test_add_entry_regex_invalid_pattern_raises(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Regex Invalid")
with pytest.raises(ValueError, match="Invalid regex pattern"):
DictionaryManager.add_entry(
db_session, dictionary.id,
r"[unclosed",
"broken",
source_language="en",
target_language="ru",
is_regex=True,
)
# endregion test_add_entry_regex_invalid_pattern_raises
# region test_edit_entry_toggle_regex [C:2] [TYPE Function]
# @BRIEF Verify toggling is_regex via edit_entry.
def test_edit_entry_toggle_regex(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Regex Toggle")
entry = DictionaryManager.add_entry(
db_session, dictionary.id, "hello", "hola",
source_language="en", target_language="es",
is_regex=False,
)
assert entry.is_regex is False
updated = DictionaryManager.edit_entry(db_session, entry.id, is_regex=True)
assert updated.is_regex is True
updated2 = DictionaryManager.edit_entry(db_session, entry.id, is_regex=False)
assert updated2.is_regex is False
# endregion test_edit_entry_toggle_regex
# region test_edit_entry_regex_source_validates_pattern [C:2] [TYPE Function]
# @BRIEF Verify editing source_term on regex entry validates pattern.
def test_edit_entry_regex_source_validates_pattern(self, db_session: Session):
dictionary = DictionaryManager.create_dictionary(db_session, name="Regex Edit Val")
entry = DictionaryManager.add_entry(
db_session, dictionary.id,
r"\d+",
"NUMBER",
source_language="en",
target_language="ru",
is_regex=True,
)
# Try to set invalid regex
with pytest.raises(ValueError, match="Invalid regex pattern"):
DictionaryManager.edit_entry(db_session, entry.id, source_term=r"[unclosed")
# endregion test_edit_entry_regex_source_validates_pattern
# #endregion TestDictionaryEntryCRUDIntegration
# #endregion DictionaryIntegrationTests