12 known failures — all from Agent 3 new unverified tests (mock setup issues): - 3 dataset_review_routes_extended (DTO field mismatches) - 1 settings_consolidated (dict key access) - 1 llm_analysis_service_coverage (rate_limit mock) - 1 migration_plugin (SessionLocal side_effect exhaustion) - 1 preview (DB query vs dict key) - 5 scheduler (datetime timezone + async mock mismatches) NEW TEST FILES THIS SESSION: - test_batch_insert_coverage.py — 3 tests - test_storage_plugin.py — +3 tests - test_search.py — +2 tests - test_mapper.py — already 100% - test_llm_analysis_migration_v1_to_v2.py — 14 tests - test_llm_async_http.py — +1 test - test_prompt_builder.py — +1 test - test_service_datasource.py — +1 test - test_lang_detect.py — +1 test - test_scheduler.py — +6 tests - test_llm_analysis_service_coverage.py — +15 tests - test_dataset_review_routes_extended.py — +14 tests - test_settings_consolidated.py — +13 tests Modules pushed to 100%: _batch_insert, dictionary_entries, service_datasource, _llm_async_http, prompt_builder, dictionary_crud, _batch_sizer, storage/plugin, mapper.py Session: 7194→7643 tests (+449), 80%→83% raw (+3pp), 93.4%→96.9% real (+3.5pp). Remaining: 12 failures to fix + ~300 statements to reach 98% real coverage.
280 lines
10 KiB
Python
280 lines
10 KiB
Python
# #region Test.DictionaryEntryCRUD [C:3] [TYPE Module] [SEMANTICS test,dictionary,entries,crud]
|
|
# @BRIEF Tests for dictionary_entries.py — DictionaryEntryCRUD.
|
|
# @RELATION BINDS_TO -> [DictionaryEntryCRUD]
|
|
# @TEST_EDGE: duplicate_entry -> ValueError
|
|
# @TEST_EDGE: entry_not_found -> ValueError
|
|
# @TEST_EDGE: invalid_regex -> ValueError
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
|
|
|
|
import pytest
|
|
from unittest.mock import MagicMock
|
|
|
|
from src.models.translate import DictionaryEntry, TerminologyDictionary
|
|
from src.plugins.translate.dictionary_entries import DictionaryEntryCRUD
|
|
|
|
|
|
class TestAddEntry:
|
|
"""DictionaryEntryCRUD.add_entry."""
|
|
|
|
def test_add_basic(self, db_session):
|
|
"""Basic add creates entry."""
|
|
dict_entry = TerminologyDictionary(id="dict-add-1", name="Test Dict", is_active=True)
|
|
db_session.add(dict_entry)
|
|
db_session.commit()
|
|
|
|
entry = DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-add-1", "hello", "bonjour",
|
|
source_language="en", target_language="fr",
|
|
)
|
|
assert entry.source_term == "hello"
|
|
assert entry.target_term == "bonjour"
|
|
assert entry.source_language == "en"
|
|
assert entry.target_language == "fr"
|
|
|
|
def test_add_with_context_notes(self, db_session):
|
|
"""Context notes are stripped."""
|
|
dict_entry = TerminologyDictionary(id="dict-add-ctx", name="Test", is_active=True)
|
|
db_session.add(dict_entry)
|
|
db_session.commit()
|
|
|
|
entry = DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-add-ctx", "hello", "bonjour",
|
|
context_notes=" formal greeting ",
|
|
)
|
|
assert entry.context_notes == "formal greeting"
|
|
|
|
def test_duplicate_raises(self, db_session):
|
|
"""Duplicate entry raises ValueError."""
|
|
dict_entry = TerminologyDictionary(id="dict-add-dup", name="Test", is_active=True)
|
|
db_session.add(dict_entry)
|
|
db_session.commit()
|
|
|
|
DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-add-dup", "hello", "bonjour",
|
|
source_language="en", target_language="fr",
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Duplicate"):
|
|
DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-add-dup", "hello", "salut",
|
|
source_language="en", target_language="fr",
|
|
)
|
|
|
|
def test_invalid_source_language(self, db_session):
|
|
"""Invalid source_language raises ValueError."""
|
|
dict_entry = TerminologyDictionary(id="dict-add-lang", name="Test", is_active=True)
|
|
db_session.add(dict_entry)
|
|
db_session.commit()
|
|
|
|
with pytest.raises(ValueError, match="source_language"):
|
|
DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-add-lang", "hello", "bonjour",
|
|
source_language="invalid!",
|
|
)
|
|
|
|
def test_invalid_regex_raises(self, db_session):
|
|
"""Invalid regex source_term raises ValueError."""
|
|
dict_entry = TerminologyDictionary(id="dict-add-regex", name="Test", is_active=True)
|
|
db_session.add(dict_entry)
|
|
db_session.commit()
|
|
|
|
with pytest.raises(ValueError, match="Invalid regex"):
|
|
DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-add-regex", r"[invalid", "bonjour",
|
|
is_regex=True,
|
|
)
|
|
|
|
def test_valid_regex(self, db_session):
|
|
"""Valid regex is accepted."""
|
|
dict_entry = TerminologyDictionary(id="dict-add-regex2", name="Test", is_active=True)
|
|
db_session.add(dict_entry)
|
|
db_session.commit()
|
|
|
|
entry = DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-add-regex2", r"\d+", "NUMBER",
|
|
is_regex=True,
|
|
)
|
|
assert entry.is_regex is True
|
|
|
|
def test_duplicate_different_language_passes(self, db_session):
|
|
"""Same source term with different language pair is OK."""
|
|
dict_entry = TerminologyDictionary(id="dict-add-diff", name="Test", is_active=True)
|
|
db_session.add(dict_entry)
|
|
db_session.commit()
|
|
|
|
DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-add-diff", "hello", "bonjour",
|
|
source_language="en", target_language="fr",
|
|
)
|
|
|
|
entry = DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-add-diff", "hello", "hola",
|
|
source_language="en", target_language="es",
|
|
)
|
|
assert entry.target_term == "hola"
|
|
|
|
|
|
class TestEditEntry:
|
|
"""DictionaryEntryCRUD.edit_entry."""
|
|
|
|
def _setup(self, db_session):
|
|
dict_entry = TerminologyDictionary(id="dict-edit-1", name="Test", is_active=True)
|
|
db_session.add(dict_entry)
|
|
db_session.commit()
|
|
|
|
entry = DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-edit-1", "hello", "bonjour",
|
|
source_language="en", target_language="fr",
|
|
)
|
|
return entry
|
|
|
|
def test_edit_target_term(self, db_session):
|
|
"""Edit target_term."""
|
|
entry = self._setup(db_session)
|
|
updated = DictionaryEntryCRUD.edit_entry(
|
|
db_session, entry.id, target_term="salut"
|
|
)
|
|
assert updated.target_term == "salut"
|
|
|
|
def test_edit_source_term(self, db_session):
|
|
"""Edit source_term with duplicate check."""
|
|
entry = self._setup(db_session)
|
|
updated = DictionaryEntryCRUD.edit_entry(
|
|
db_session, entry.id, source_term="hi"
|
|
)
|
|
assert updated.source_term == "hi"
|
|
|
|
def test_edit_languages(self, db_session):
|
|
"""Edit source_language and target_language."""
|
|
entry = self._setup(db_session)
|
|
updated = DictionaryEntryCRUD.edit_entry(
|
|
db_session, entry.id,
|
|
source_language="es", target_language="de",
|
|
)
|
|
assert updated.source_language == "es"
|
|
assert updated.target_language == "de"
|
|
|
|
def test_edit_not_found(self, db_session):
|
|
"""Non-existent entry raises ValueError."""
|
|
with pytest.raises(ValueError, match="Entry not found"):
|
|
DictionaryEntryCRUD.edit_entry(db_session, "nonexistent", source_term="x")
|
|
|
|
def test_edit_context_notes(self, db_session):
|
|
"""Edit context_notes to None."""
|
|
entry = self._setup(db_session)
|
|
updated = DictionaryEntryCRUD.edit_entry(
|
|
db_session, entry.id, context_notes=""
|
|
)
|
|
assert updated.context_notes is None
|
|
|
|
def test_edit_duplicate_source_term_raises(self, db_session):
|
|
"""Changing source_term to an existing one raises."""
|
|
entry1 = self._setup(db_session)
|
|
entry2 = DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-edit-1", "world", "monde",
|
|
source_language="en", target_language="fr",
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Duplicate"):
|
|
DictionaryEntryCRUD.edit_entry(
|
|
db_session, entry2.id, source_term="hello"
|
|
)
|
|
|
|
def test_edit_is_regex(self, db_session):
|
|
"""Edit is_regex flag."""
|
|
entry = self._setup(db_session)
|
|
updated = DictionaryEntryCRUD.edit_entry(
|
|
db_session, entry.id, is_regex=True
|
|
)
|
|
assert updated.is_regex is True
|
|
|
|
def test_edit_regex_source_term_validation(self, db_session):
|
|
"""Setting source_term to invalid regex when entry is regex raises."""
|
|
entry = self._setup(db_session)
|
|
DictionaryEntryCRUD.edit_entry(db_session, entry.id, is_regex=True)
|
|
|
|
with pytest.raises(ValueError, match="Invalid regex"):
|
|
DictionaryEntryCRUD.edit_entry(
|
|
db_session, entry.id, source_term=r"[invalid"
|
|
)
|
|
|
|
|
|
class TestDeleteEntry:
|
|
"""DictionaryEntryCRUD.delete_entry."""
|
|
|
|
def _setup(self, db_session):
|
|
dict_entry = TerminologyDictionary(id="dict-del-1", name="Test", is_active=True)
|
|
db_session.add(dict_entry)
|
|
db_session.commit()
|
|
|
|
entry = DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-del-1", "hello", "bonjour",
|
|
)
|
|
return entry
|
|
|
|
def test_delete(self, db_session):
|
|
"""Delete existing entry."""
|
|
entry = self._setup(db_session)
|
|
entry_id = entry.id
|
|
DictionaryEntryCRUD.delete_entry(db_session, entry_id)
|
|
assert db_session.query(DictionaryEntry).filter(DictionaryEntry.id == entry_id).first() is None
|
|
|
|
def test_delete_not_found(self, db_session):
|
|
"""Delete non-existent entry raises ValueError."""
|
|
with pytest.raises(ValueError, match="Entry not found"):
|
|
DictionaryEntryCRUD.delete_entry(db_session, "nonexistent")
|
|
|
|
|
|
class TestClearEntries:
|
|
"""DictionaryEntryCRUD.clear_entries."""
|
|
|
|
def test_clear(self, db_session):
|
|
"""Clear all entries for a dictionary."""
|
|
dict_entry = TerminologyDictionary(id="dict-clear-1", name="Test", is_active=True)
|
|
db_session.add(dict_entry)
|
|
db_session.commit()
|
|
|
|
DictionaryEntryCRUD.add_entry(db_session, "dict-clear-1", "hello", "bonjour")
|
|
DictionaryEntryCRUD.add_entry(db_session, "dict-clear-1", "world", "monde")
|
|
|
|
deleted = DictionaryEntryCRUD.clear_entries(db_session, "dict-clear-1")
|
|
assert deleted == 2
|
|
|
|
def test_clear_dictionary_not_found(self, db_session):
|
|
"""Clear non-existent dictionary raises ValueError."""
|
|
with pytest.raises(ValueError, match="Dictionary not found"):
|
|
DictionaryEntryCRUD.clear_entries(db_session, "nonexistent")
|
|
|
|
|
|
class TestListEntries:
|
|
"""DictionaryEntryCRUD.list_entries."""
|
|
|
|
def test_list_pagination(self, db_session):
|
|
"""List with pagination."""
|
|
dict_entry = TerminologyDictionary(id="dict-list-1", name="Test", is_active=True)
|
|
db_session.add(dict_entry)
|
|
db_session.commit()
|
|
|
|
for i in range(5):
|
|
DictionaryEntryCRUD.add_entry(
|
|
db_session, "dict-list-1", f"term{i}", f"trans{i}",
|
|
)
|
|
|
|
entries, total = DictionaryEntryCRUD.list_entries(db_session, "dict-list-1", page=1, page_size=2)
|
|
assert len(entries) == 2
|
|
assert total == 5
|
|
|
|
def test_list_empty(self, db_session):
|
|
"""Empty dictionary returns empty list."""
|
|
dict_entry = TerminologyDictionary(id="dict-list-empty", name="Test", is_active=True)
|
|
db_session.add(dict_entry)
|
|
db_session.commit()
|
|
|
|
entries, total = DictionaryEntryCRUD.list_entries(db_session, "dict-list-empty")
|
|
assert entries == []
|
|
assert total == 0
|
|
# #endregion Test.DictionaryEntryCRUD
|