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.
215 lines
6.9 KiB
Python
215 lines
6.9 KiB
Python
# #region Test.LanguageDetectService [C:2] [TYPE Module] [SEMANTICS test,language,detection]
|
|
# @BRIEF Tests for _lang_detect.py — language detection.
|
|
# @RELATION BINDS_TO -> [LanguageDetectService]
|
|
|
|
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, patch
|
|
|
|
from lingua import LanguageDetectorBuilder
|
|
|
|
from src.plugins.translate._lang_detect import (
|
|
_detector_cache_key,
|
|
build_detector,
|
|
get_detector,
|
|
detect_language,
|
|
_character_block_fallback,
|
|
batch_detect,
|
|
)
|
|
|
|
|
|
class TestDetectorCacheKey:
|
|
"""_detector_cache_key."""
|
|
|
|
def test_none(self):
|
|
"""None returns '__default'."""
|
|
assert _detector_cache_key(None) == "__default"
|
|
|
|
def test_empty_list(self):
|
|
"""Empty list returns '__default'."""
|
|
assert _detector_cache_key([]) == "__default"
|
|
|
|
def test_sorted_key(self):
|
|
"""Languages are sorted for deterministic key."""
|
|
key = _detector_cache_key(["fr", "en", "de"])
|
|
assert key == "de,en,fr"
|
|
|
|
def test_duplicates(self):
|
|
"""Duplicates are removed."""
|
|
key = _detector_cache_key(["en", "en", "fr"])
|
|
assert key == "en,fr"
|
|
|
|
|
|
class TestBuildDetector:
|
|
"""build_detector."""
|
|
|
|
def test_with_target_languages(self):
|
|
"""Builds detector with specific languages."""
|
|
detector = build_detector(["ru", "de"])
|
|
assert detector is not None
|
|
|
|
def test_no_target_languages(self):
|
|
"""No target languages builds with common source codes."""
|
|
detector = build_detector(None)
|
|
assert detector is not None
|
|
|
|
def test_empty_target_languages(self):
|
|
"""Empty list builds with common source codes."""
|
|
detector = build_detector([])
|
|
assert detector is not None
|
|
|
|
def test_unknown_language_codes(self):
|
|
"""Unknown codes are ignored."""
|
|
detector = build_detector(["xx", "yy"])
|
|
# Should try to build; if no lingua_langs found, falls back to all spoken
|
|
assert detector is not None
|
|
|
|
def test_fallback_to_all_spoken(self):
|
|
"""When _CODE_TO_LANG has no matches, falls back to all spoken languages (line 93)."""
|
|
with patch("src.plugins.translate._lang_detect._CODE_TO_LANG", {}):
|
|
detector = build_detector(["en"])
|
|
assert detector is not None
|
|
|
|
|
|
class TestGetDetector:
|
|
"""get_detector — cached detector."""
|
|
|
|
def test_cached(self):
|
|
"""Same key returns same detector."""
|
|
with patch("src.plugins.translate._lang_detect._detector_cache", {}):
|
|
d1 = get_detector(["en", "fr"])
|
|
d2 = get_detector(["en", "fr"])
|
|
assert d1 is d2
|
|
|
|
def test_different_keys(self):
|
|
"""Different keys return different detectors."""
|
|
with patch("src.plugins.translate._lang_detect._detector_cache", {}):
|
|
d1 = get_detector(["en"])
|
|
d2 = get_detector(["fr"])
|
|
assert d1 is not d2
|
|
|
|
def test_default_key(self):
|
|
"""None returns a detector."""
|
|
with patch("src.plugins.translate._lang_detect._detector_cache", {}):
|
|
d = get_detector(None)
|
|
assert d is not None
|
|
|
|
|
|
class TestDetectLanguage:
|
|
"""detect_language."""
|
|
|
|
def test_empty_text(self):
|
|
"""Empty text returns 'und'."""
|
|
detector = build_detector(["en"])
|
|
assert detect_language("", detector) == "und"
|
|
|
|
def test_whitespace_only(self):
|
|
"""Whitespace-only returns 'und'."""
|
|
detector = build_detector(["en"])
|
|
assert detect_language(" ", detector) == "und"
|
|
|
|
def test_english_text(self):
|
|
"""English text detected."""
|
|
detector = build_detector(["en", "ru"])
|
|
result = detect_language("Hello, how are you today?", detector)
|
|
assert result == "en"
|
|
|
|
def test_russian_text(self):
|
|
"""Russian text detected."""
|
|
detector = build_detector(["ru", "en"])
|
|
result = detect_language("Привет, как дела?", detector)
|
|
assert result == "ru"
|
|
|
|
def test_exception_returns_und(self):
|
|
"""Exception in detection returns 'und'."""
|
|
detector = MagicMock()
|
|
detector.detect_language_of.side_effect = Exception("error")
|
|
result = detect_language("hello", detector)
|
|
assert result == "und"
|
|
|
|
def test_none_result(self):
|
|
"""None result from detector returns 'und'."""
|
|
detector = MagicMock()
|
|
detector.detect_language_of.return_value = None
|
|
result = detect_language("hello", detector)
|
|
assert result == "und"
|
|
|
|
|
|
class TestCharacterBlockFallback:
|
|
"""_character_block_fallback."""
|
|
|
|
def test_no_target_languages(self):
|
|
"""No target languages returns results unchanged."""
|
|
results = _character_block_fallback(["text"], ["und"], None)
|
|
assert results == ["und"]
|
|
|
|
def test_non_cyrillic_targets(self):
|
|
"""No Cyrillic targets returns results unchanged."""
|
|
results = _character_block_fallback(["text"], ["und"], ["en", "fr"])
|
|
assert results == ["und"]
|
|
|
|
def test_no_und_results(self):
|
|
"""No 'und' results returns unchanged."""
|
|
results = _character_block_fallback(["text"], ["en"], ["ru"])
|
|
assert results == ["en"]
|
|
|
|
def test_cyrillic_text_detected(self):
|
|
"""Cyrillic-dominant text gets assigned ru."""
|
|
results = _character_block_fallback(
|
|
["привет мир текст"],
|
|
["und"],
|
|
["ru", "en"],
|
|
)
|
|
assert results[0] == "ru"
|
|
|
|
def test_non_cyrillic_text_stays_und(self):
|
|
"""Non-Cyrillic text stays 'und'."""
|
|
results = _character_block_fallback(
|
|
["hello world 123"],
|
|
["und"],
|
|
["ru", "en"],
|
|
)
|
|
assert results[0] == "und"
|
|
|
|
def test_empty_text_stays_und(self):
|
|
"""Empty text stays 'und'."""
|
|
results = _character_block_fallback(
|
|
[""],
|
|
["und"],
|
|
["ru"],
|
|
)
|
|
assert results[0] == "und"
|
|
|
|
|
|
class TestBatchDetect:
|
|
"""batch_detect."""
|
|
|
|
def test_basic_batch(self):
|
|
"""Detects languages for multiple texts."""
|
|
detector = build_detector(["en", "ru", "fr"])
|
|
results = batch_detect(["Hello", "Привет", "Bonjour"],
|
|
detector=detector)
|
|
assert len(results) == 3
|
|
|
|
def test_builds_detector_when_none(self):
|
|
"""Builds detector when not provided."""
|
|
results = batch_detect(["Hello"], target_languages=["en"])
|
|
assert len(results) == 1
|
|
|
|
def test_empty_texts(self):
|
|
"""Empty texts list returns empty list."""
|
|
results = batch_detect([])
|
|
assert results == []
|
|
|
|
def test_applies_fallback(self):
|
|
"""Cyrillic fallback is applied."""
|
|
results = batch_detect(
|
|
["привет мир"],
|
|
target_languages=["ru"],
|
|
)
|
|
assert len(results) == 1
|
|
# #endregion Test.LanguageDetectService
|