""" Tests for services/quality/script_detector.py """ import pytest from services.quality import evaluate_chunk, evaluate_document, detect_arabic_variant # ---------- Happy path: correct script ---------- class TestCorrectScript: def test_russian_correct(self): r = evaluate_chunk("Hello world", "Привет мир", "ru") assert r.passed is True assert "wrong_script" not in r.issues def test_chinese_simplified_correct(self): r = evaluate_chunk("Hello", "你好世界", "zh") assert r.passed is True def test_arabic_correct(self): r = evaluate_chunk("Hello", "مرحبا بالعالم", "ar") assert r.passed is True def test_persian_correct(self): # Persian has unique chars چ پ ژ گ r = evaluate_chunk("Hello", "سلام چطوری؟ من پژوهشگر هستم", "fa") assert r.passed is True def test_french_correct(self): r = evaluate_chunk("Hello world", "Bonjour le monde", "fr") assert r.passed is True def test_hebrew_correct(self): r = evaluate_chunk("Hello", "שלום עולם", "he") assert r.passed is True def test_korean_correct(self): r = evaluate_chunk("Hello", "안녕하세요 세계", "ko") assert r.passed is True def test_japanese_correct(self): # Japanese must contain hiragana or katakana to be classified as ja. r = evaluate_chunk("Hello", "こんにちは世界", "ja") assert r.passed is True def test_hindi_correct(self): r = evaluate_chunk("Hello", "नमस्ते दुनिया", "hi") assert r.passed is True def test_thai_correct(self): r = evaluate_chunk("Hello", "สวัสดีชาวโลก", "th") assert r.passed is True def test_greek_correct(self): r = evaluate_chunk("Hello", "Γεια σας κόσμε", "el") assert r.passed is True # ---------- Wrong script: language confusion ---------- class TestWrongScript: def test_french_text_for_japanese_target(self): r = evaluate_chunk("Hello", "Bonjour le monde", "ja") assert r.passed is False assert "wrong_script" in r.issues def test_arabic_text_for_french_target(self): r = evaluate_chunk("Hello", "مرحبا بالعالم", "fr") assert r.passed is False assert "wrong_script" in r.issues def test_russian_text_for_english_target(self): r = evaluate_chunk("Hello", "Привет мир", "en") assert r.passed is False assert "wrong_script" in r.issues def test_chinese_text_for_korean_target(self): r = evaluate_chunk("Hello", "你好世界", "ko") # Hangul syllables are 0xAC00-0xD7AF — Chinese chars are not in that range. assert r.passed is False assert "wrong_script" in r.issues # ---------- Persian / Arabic discrimination ---------- class TestArabicVariantDiscrimination: def test_persian_for_arabic_target_fails(self): # Persian text with چ پ ژ گ should fail when target is Arabic. r = evaluate_chunk("Hello", "سلام چطوری؟", "ar") assert r.passed is False assert "wrong_arabic_variant" in r.issues def test_arabic_for_persian_target_fails(self): # Pure Arabic text (no Persian-specific chars) when target is Persian. r = evaluate_chunk("Hello", "السلام عليكم", "fa") # No Persian-specific chars, no other Arabic-script variant chars # → should pass the variant check (because no discrimination signal). # This is a known limitation: pure Arabic indistinguishable from pure Persian. # We accept that the variant check only flags MISMATCHES with discriminating chars. assert "wrong_arabic_variant" not in r.issues def test_urdu_for_persian_target_fails(self): # Urdu with ٹ ڈ should fail when target is Persian. r = evaluate_chunk("Hello", "السلام ٹڈ", "fa") assert r.passed is False assert "wrong_arabic_variant" in r.issues def test_pashto_for_arabic_target_fails(self): # Pashto with ټډړ should fail when target is Arabic. r = evaluate_chunk("Hello", "السلام ټډړ", "ar") assert r.passed is False assert "wrong_arabic_variant" in r.issues # ---------- Edge cases ---------- class TestEdgeCases: def test_empty_translation_passes(self): # Empty translations are skipped (the provider may have legitimately # produced nothing — e.g. an empty cell). r = evaluate_chunk("Hello", "", "fr") assert r.passed is True assert "empty_translation" in r.issues def test_whitespace_only_passes(self): r = evaluate_chunk("Hello", " \n ", "fr") assert r.passed is True assert "empty_translation" in r.issues def test_none_translation_passes(self): r = evaluate_chunk("Hello", None, "fr") assert r.passed is True def test_numbers_only_passes(self): r = evaluate_chunk("Price: 100", "100", "fr") assert r.passed is True def test_unknown_target_lang_falls_back(self): # Unknown lang → latin → no script check, just length/leak checks. r = evaluate_chunk("Hello", "Bonjour", "xx") assert r.passed is True def test_mixed_brands_latin_target(self): # "iPhone 15 Pro Max" is mostly Latin + digits — fine for fr. r = evaluate_chunk("Buy iPhone 15", "Acheter iPhone 15", "fr") assert r.passed is True # ---------- Length sanity ---------- class TestLengthIssues: def test_huge_translation_flagged_via_repetition(self): # Source too short for length ratio, but the "x"*1000 repetition # is caught by the pattern/repetition check, not length. r = evaluate_chunk("Short", "x" * 1000, "fr") assert "repetition_hallucination" in r.issues def test_huge_translation_with_long_source_flagged(self): # Long enough source → length ratio kicks in → length_outlier. src = "A" * 200 r = evaluate_chunk(src, "x" * 1000, "fr") assert "length_outlier" in r.issues def test_tiny_translation_flagged(self): r = evaluate_chunk("A" * 100, "ok", "fr") # ratio is 2/100 = 0.02, well below 0.15 assert "truncation_suspect" in r.issues def test_normal_translation_no_length_issue(self): r = evaluate_chunk("Hello world, how are you?", "Bonjour le monde, comment allez-vous?", "fr") assert "length_outlier" not in r.issues assert "truncation_suspect" not in r.issues def test_short_source_skips_ratio(self): r = evaluate_chunk("Hi", "ok", "fr") # source too short for ratio check assert "length_outlier" not in r.issues assert "truncation_suspect" not in r.issues def test_numeric_source_skips_length_check(self): # Source is mostly digits — translation can also be short. r = evaluate_chunk("Price: 100", "100", "fr") assert "truncation_suspect" not in r.issues assert "length_outlier" not in r.issues # ---------- Pattern leak / repetition ---------- class TestPatternLeak: def test_prompt_leak_english_detected(self): r = evaluate_chunk("Hello", "Translation: Bonjour le monde", "fr") assert "prompt_leak" in r.issues def test_prompt_leak_french_detected(self): r = evaluate_chunk("Hello", "Voici la traduction : Bonjour", "fr") assert "prompt_leak" in r.issues def test_prompt_leak_chinese_detected(self): r = evaluate_chunk("Hello", "翻译:你好", "zh") assert "prompt_leak" in r.issues def test_repetition_hallucination_detected(self): r = evaluate_chunk("Hello", "the the the the the the", "fr") assert "repetition_hallucination" in r.issues def test_normal_text_no_leak(self): r = evaluate_chunk("Hello", "Bonjour le monde", "fr") assert "prompt_leak" not in r.issues assert "repetition_hallucination" not in r.issues # ---------- Document-level aggregation ---------- class TestEvaluateDocument: def test_all_good(self): result = evaluate_document( ["Hello", "World", "Good morning"], ["Bonjour", "Monde", "Bonjour"], "fr", ) assert result.passed is True assert result.failed_chunk_count == 0 assert result.chunk_count == 3 def test_some_failures(self): result = evaluate_document( ["Hello", "World", "Good morning"], ["Bonjour", "مرحبا", "Bonjour"], "fr", ) assert result.passed is False assert result.failed_chunk_count == 1 assert "wrong_script" in result.issues assert len(result.samples) == 1 def test_empty_lists(self): result = evaluate_document([], [], "fr") assert result.passed is True assert result.chunk_count == 0 assert result.score == 0.0 def test_sample_size_caps_samples(self): # 10 failures, sample_size=3 result = evaluate_document( ["hi"] * 10, ["مرحبا"] * 10, "fr", sample_size=3, ) assert result.failed_chunk_count == 10 assert len(result.samples) == 3 def test_score_proportional(self): # 4 chunks, 1 fails → score should be ~0.75 (3/4 passed checks) result = evaluate_document( ["hi", "hi", "hi", "hi"], ["bonjour", "bonjour", "مرحبا", "bonjour"], "fr", ) assert result.failed_chunk_count == 1 assert result.score < 1.0 assert result.score > 0.0 # ---------- detect_arabic_variant direct ---------- class TestDetectArabicVariantDirect: def test_empty_text(self): r = detect_arabic_variant("", "fa") assert r["verdict"] == "skip" def test_pure_persian_for_persian(self): r = detect_arabic_variant("سلام چطوری", "fa") assert r["verdict"] == "pass" def test_pure_persian_for_arabic(self): r = detect_arabic_variant("سلام چطوری", "ar") assert r["verdict"] == "fail" assert "fa" in r["detected_variants"] def test_pure_urdu_for_persian(self): r = detect_arabic_variant("السلام ٹڈ", "fa") assert r["verdict"] == "fail" assert "ur" in r["detected_variants"] def test_non_arabic_text(self): r = detect_arabic_variant("Bonjour le monde", "fa") # Not in Arabic script → skip assert r["verdict"] == "skip"