# #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