250 lines
8.4 KiB
Python
250 lines
8.4 KiB
Python
# #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]
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
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", source_dialect="en", target_dialect="ru",
|
|
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", source_dialect="en", target_dialect="ru",
|
|
)
|
|
|
|
# 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", source_dialect="en", target_dialect="ru",
|
|
)
|
|
|
|
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", source_dialect="en", target_dialect="ru",
|
|
)
|
|
|
|
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", source_dialect="en", target_dialect="ru",
|
|
)
|
|
|
|
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
|