# #region TranslateCorrectionTests [C:2] [TYPE Module] # @SEMANTICS: tests, translate, corrections, dictionary # @PURPOSE: Tests for term correction API endpoints and DictionaryManager correction methods. # @LAYER Tests # @RELATION BINDS_TO -> [DictionaryManagerModule] # @RELATION BINDS_TO -> [TranslateRoutes] # # @TEST_CONTRACT: CorrectionFlow -> # { # required_fields: {source_term, incorrect_target_term, corrected_target_term, dictionary_id}, # invariants: [ # "POST /api/translate/corrections creates or updates entry", # "Conflict detection works for existing entries", # "POST /api/translate/corrections/bulk atomic all-or-nothing", # "Origin tracking fields are populated" # ] # } # @TEST_FIXTURE: valid_dictionary -> created via DictionaryManager # @TEST_EDGE: missing_dictionary_id -> 422 # @TEST_EDGE: conflict_detected -> conflict response returned # @TEST_EDGE: bulk_correction_all_succeed -> atomic commit # @TEST_EDGE: bulk_correction_with_conflicts -> conflicts listed # @TEST_INVARIANT: correction_flow -> VERIFIED_BY: [valid_dictionary, conflict_detected] from pathlib import Path import sys sys.path.insert(0, str(Path(__file__).parent.parent / "src")) import pytest from unittest.mock import MagicMock from fastapi.testclient import TestClient from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from src.app import app from src.core.database import Base from src.dependencies import get_config_manager, get_current_user, get_db from src.plugins.translate.dictionary import DictionaryManager # In-memory SQLite SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:" engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}) TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base.metadata.create_all(bind=engine) mock_user = MagicMock() mock_user.username = "testuser" mock_user.roles = [] admin_role = MagicMock() admin_role.name = "Admin" mock_user.roles.append(admin_role) @pytest.fixture def db_session(): connection = engine.connect() transaction = connection.begin() session = TestingSessionLocal(bind=connection) yield session session.close() transaction.rollback() connection.close() @pytest.fixture def mock_api_deps(): config_manager = MagicMock() config_manager.get_environments.return_value = [] connection = engine.connect() transaction = connection.begin() session = TestingSessionLocal(bind=connection) def override_get_db(): try: yield session finally: pass overrides = { get_current_user: lambda: mock_user, get_config_manager: lambda: config_manager, get_db: override_get_db, } for dep, override in overrides.items(): app.dependency_overrides[dep] = override yield {"config_manager": config_manager, "session": session} app.dependency_overrides.clear() session.close() transaction.rollback() connection.close() @pytest.fixture def client(mock_api_deps): return TestClient(app) # #region test_submit_correction_creates_entry [C:2] [TYPE Function] # @PURPOSE: Verify that a correction creates a new dictionary entry. def test_submit_correction_creates_entry(db_session): """Test that submitting a correction creates a new entry.""" dict_obj = DictionaryManager.create_dictionary( db_session, name="Test Dict", created_by="testuser", ) result = DictionaryManager.submit_correction( db_session, dict_id=dict_obj.id, source_term="hello", incorrect_target_term="privet", corrected_target_term="zdravstvuyte", origin_run_id="run-123", origin_row_key="row-1", origin_user_id="testuser", ) assert result["action"] == "created" assert result["entry_id"] is not None entries, total = DictionaryManager.list_entries(db_session, dict_obj.id) assert total == 1 assert entries[0].source_term == "hello" assert entries[0].target_term == "zdravstvuyte" assert entries[0].origin_run_id == "run-123" assert entries[0].origin_row_key == "row-1" assert entries[0].origin_user_id == "testuser" # #endregion test_submit_correction_creates_entry # #region test_submit_correction_conflict_detected [C:2] [TYPE Function] # @PURPOSE: Verify conflict detection when entry already exists. def test_submit_correction_conflict_detected(db_session): """Test that conflict is detected when entry already exists.""" dict_obj = DictionaryManager.create_dictionary( db_session, name="Dict", ) # Create initial entry DictionaryManager.add_entry(db_session, dict_obj.id, "hello", "privet") # Submit correction with conflict result = DictionaryManager.submit_correction( db_session, dict_id=dict_obj.id, source_term="hello", incorrect_target_term="privet", corrected_target_term="zdravstvuyte", on_conflict="keep_existing", ) assert result["action"] == "conflict_detected" assert result["conflict"] is not None assert result["conflict"]["existing_target_term"] == "privet" # #endregion test_submit_correction_conflict_detected # #region test_submit_correction_overwrite [C:2] [TYPE Function] # @PURPOSE: Verify that correction overwrites existing entry. def test_submit_correction_overwrite(db_session): """Test that correction overwrites existing entry.""" dict_obj = DictionaryManager.create_dictionary( db_session, name="Dict", ) DictionaryManager.add_entry(db_session, dict_obj.id, "hello", "privet") result = DictionaryManager.submit_correction( db_session, dict_id=dict_obj.id, source_term="hello", incorrect_target_term="privet", corrected_target_term="hi_there", on_conflict="overwrite", ) assert result["action"] == "updated" entries, _ = DictionaryManager.list_entries(db_session, dict_obj.id) assert entries[0].target_term == "hi_there" # #endregion test_submit_correction_overwrite # #region test_bulk_corrections_atomic [C:2] [TYPE Function] # @PURPOSE: Verify bulk corrections are applied atomically. def test_bulk_corrections_atomic(db_session): """Test that bulk corrections are applied atomically.""" dict_obj = DictionaryManager.create_dictionary( db_session, name="Bulk Dict", ) corrections = [ {"source_term": "cat", "incorrect_target_term": "kot", "corrected_target_term": "koshka"}, {"source_term": "dog", "incorrect_target_term": "sobaka", "corrected_target_term": "pyos"}, ] result = DictionaryManager.submit_bulk_corrections( db_session, dict_id=dict_obj.id, corrections=corrections, origin_user_id="testuser", ) assert result["status"] == "completed" assert result["applied"] == 2 assert len(result["conflicts"]) == 0 entries, total = DictionaryManager.list_entries(db_session, dict_obj.id) assert total == 2 # #endregion test_bulk_corrections_atomic # #region test_api_correction_missing_dict [C:2] [TYPE Function] # @PURPOSE: Verify POST corrections without dictionary_id returns 422. def test_api_correction_missing_dict(client): """Test correction without dict_id returns 422.""" response = client.post("/api/translate/corrections", json={ "source_term": "hello", "incorrect_target_term": "privet", "corrected_target_term": "zdravstvuyte", }) assert response.status_code == 422 # #endregion test_api_correction_missing_dict # #region test_api_bulk_corrections [C:2] [TYPE Function] # @PURPOSE: Verify POST /corrections/bulk works. def test_api_bulk_corrections(client, mock_api_deps): """Test bulk corrections endpoint.""" session = mock_api_deps["session"] dict_obj = DictionaryManager.create_dictionary( session, name="API Dict", ) response = client.post("/api/translate/corrections/bulk", json={ "dictionary_id": dict_obj.id, "corrections": [ {"source_term": "hello", "incorrect_target_term": "privet", "corrected_target_term": "hi"}, ], }) assert response.status_code == 200 data = response.json() assert data["status"] == "completed" # #endregion test_api_bulk_corrections # #endregion TranslateCorrectionTests