Files
ss-tools/backend/tests/plugins/translate/test_lang_detect.py
busya 8a4310169b v4: 7643 tests passing, 83% raw / 96.9% real coverage (excl __tests__).
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.
2026-06-16 09:34:10 +03:00

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