fix(lang): pin the detected customer language explicitly (Spanish-heavy context was overriding plain mirror); None->mirror fallback
1316064 verified | from __future__ import annotations | |
| import pytest | |
| from app.lang import detect_language | |
| def test_spanish_detected(text): | |
| assert detect_language(text) == "español" | |
| def test_portuguese_detected(text): | |
| assert detect_language(text) == "portugués de Portugal (pt-PT)" | |
| def test_catalan_detected_not_portuguese(): | |
| # regression: "bon dia, què veneu?" was answered in Portuguese | |
| assert detect_language("bon dia, què veneu?") == "català" | |
| assert detect_language("bona tarda, voleu ajudar-me amb això?") == "català" | |
| def test_world_languages_detected(text, expected): | |
| assert detect_language(text) == expected | |
| def test_english_detected(): | |
| assert detect_language("where is my order, can you check the tracking?") == "English" | |
| def test_english_with_stray_spanish_word_stays_english(): | |
| # regression: "mi" must NOT flip a clearly-English sentence to Spanish | |
| assert detect_language("where is mi product please") == "English" | |
| assert detect_language("hello, can you help me find a product") == "English" | |
| def test_plain_spanish_without_accents_is_spanish(text): | |
| # regression: a strong Spanish word-score must beat the "a"/"to" English bias | |
| assert detect_language(text) == "español" | |
| def test_short_english_not_misdetected(text): | |
| # regression: short English must NOT be pinned to an exotic language | |
| assert detect_language(text) == "English" | |
| def test_english_misclassified_as_iberian_stays_english(text): | |
| assert detect_language(text) == "English" | |
| def test_short_nonlatin_detected_by_script(text, expected): | |
| assert detect_language(text) == expected | |
| def test_short_cyrillic_pins_a_cyrillic_language(): | |
| # exact Slavic language varies, but it must pin a Cyrillic language (not None) | |
| cyrillic = {"ruso (Russian)", "ucraniano (Ukrainian)", "búlgaro (Bulgarian)", | |
| "macedonio (Macedonian)"} | |
| assert detect_language("у вас есть садовые шланги?") in cyrillic | |
| def test_unsure_latin_text_not_forced(): | |
| # gibberish/ambiguous Latin → don't force a wrong language (let LLM decide) | |
| assert detect_language("zxcv qwer asdf zxcv") is None | |
| def test_short_greetings_pinned_by_lexicon(): | |
| # regression: "buenas" was answered in Portuguese | |
| assert detect_language("buenas") == "español" | |
| assert detect_language("hola") == "español" | |
| assert detect_language("bonjour") == "français" | |
| assert detect_language("hello") == "English" | |
| assert detect_language("olá") == "portugués de Portugal (pt-PT)" | |
| def test_unknown_short_text_not_pinned(): | |
| assert detect_language("xy") is None | |
| assert detect_language("") is None | |
| def test_language_guard_flags_drift(): | |
| from app.lang import needs_language_repair | |
| ko = "coreano (Korean)" | |
| zh = "chino simplificado (Chinese)" | |
| # drift: Korean pinned but answer is Spanish → must repair | |
| assert needs_language_repair("Sí, disponemos de mangueras para jardín.", ko) is True | |
| assert needs_language_repair("Sí, tenemos varias opciones.", zh) is True | |
| # correct: answer in the pinned script → no repair (Latin product names allowed) | |
| assert needs_language_repair("네, 정원용 호스가 있습니다. Gobeflat Medium 모델입니다.", ko) is False | |
| assert needs_language_repair("我们有花园用的水管。PVC 平面水管。", zh) is False | |
| # Latin-script languages are NOT force-repaired by design: the model mirrors the | |
| # customer's language (system prompt), and a heuristic Latin pin can be wrong — | |
| # repairing toward a wrong pin is exactly what caused the Hungarian/Catalan | |
| # misfire. So Latin always returns False (trust the model), only non-Latin repairs. | |
| assert needs_language_repair("Sí, tenemos varias mangueras de jardín disponibles para ti.", "français") is False | |
| assert needs_language_repair("Bonjour, nous avons plusieurs tuyaux d'arrosage disponibles.", "français") is False | |
| assert needs_language_repair("anything", None) is False | |
| def test_strip_emojis_removes_pictographs_keeps_text(): | |
| from app.lang import strip_emojis | |
| assert strip_emojis("¡Hola! 😊 ¿En qué te ayudo?") == "¡Hola! ¿En qué te ayudo?" | |
| assert strip_emojis("Tenemos mangueras 🌿✅ disponibles") == "Tenemos mangueras disponibles" | |
| # non-Latin text must survive untouched (emoji ranges don't overlap scripts) | |
| assert strip_emojis("정원용 호스 😀 있습니다") == "정원용 호스 있습니다" | |
| assert strip_emojis("我们有水管 🚀") == "我们有水管" | |
| assert strip_emojis("normal text") == "normal text" | |
| def test_strip_links_removes_fabricated_links(): | |
| from app.lang import strip_links | |
| # markdown link → plain text (the inline form is the real CTA, no 404 link) | |
| out = strip_links("Rellena el [Formulario de contacto](https://flexigobe.com/contacto) abajo.") | |
| assert "https://" not in out and "](" not in out | |
| assert "Formulario de contacto" in out # keep the words, drop the link | |
| # bare URL removed | |
| assert "http" not in strip_links("Visita https://flexigobe.com/contacto para más.") | |
| # clean text untouched | |
| assert strip_links("Rellena el formulario que aparece abajo.") == \ | |
| "Rellena el formulario que aparece abajo." | |
| def test_prompt_forbids_emojis(): | |
| from app.prompts import build_system_prompt | |
| assert "emoji" in build_system_prompt("X").lower() | |
| def test_prompt_pins_detected_language_else_mirrors(): | |
| # The store context is heavily Spanish and drags the model into Spanish even for | |
| # an English customer (observed live), so a detected language is pinned EXPLICITLY; | |
| # when detection is unsure (None) we mirror. The Hungarian misfire is fixed at the | |
| # DETECTOR (see test_spanish_detected), not by refusing to pin. | |
| from app.prompts import build_system_prompt | |
| pinned = build_system_prompt("Tienda", "English").lower() | |
| assert "english" in pinned and "entire reply" in pinned | |
| unsure = build_system_prompt("Tienda", None).lower() | |
| assert "same language" in unsure and "mirror" in unsure | |
| def test_prompt_forbids_redirecting_customer_to_third_parties(): | |
| """Regression: the bot once told a Spanish customer to 'visit Toorx's website | |
| / contact their customer service' instead of helping. The prompt must forbid | |
| sending the customer to a manufacturer/third party and keep them with the | |
| store's own team.""" | |
| from app.prompts import build_system_prompt | |
| p = build_system_prompt("Tienda X").lower() | |
| assert "third party" in p and "manufacturer" in p | |
| assert "escalate_to_human" in p | |