File size: 9,936 Bytes
4ebc6e6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47a6953
 
 
 
4ebc6e6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7cf80b
 
 
 
 
 
eb0b69b
 
 
 
 
 
 
 
 
 
 
47a6953
 
eb0b69b
 
 
 
 
 
4ebc6e6
 
 
 
5b8e6d8
 
 
 
 
 
d183415
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60c3455
 
 
 
 
 
 
 
 
 
 
 
 
 
1961ced
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60c3455
 
 
 
 
e15c2f9
 
 
 
 
 
 
 
 
 
 
4ebc6e6
e15c2f9
 
593d015
 
 
 
 
 
 
 
 
 
 
47a6953
 
 
 
 
b81fe79
593d015
 
 
b81fe79
 
 
 
 
 
 
 
 
 
 
1f7aa34
 
 
 
 
 
 
 
 
 
 
 
 
 
e15c2f9
 
 
 
d183415
 
1316064
 
 
 
 
47a6953
 
1316064
 
 
 
47a6953
 
d183415
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
from __future__ import annotations

import pytest

from app.lang import detect_language


@pytest.mark.parametrize(
    "text",
    [
        # The live regression: langdetect scores this pt at 99.9%.
        "hola, ¿hacéis envíos a Portugal?",
        "que productos vendeis y de que material son",
        "quiero devolver mi pedido",
        # langdetect scores this one as Catalan.
        "envianme a Lisboa el paquete por favor",
        "¿cuánto tarda el envío a Canarias?",
        # live bug: this all-caps Spanish was answered in HUNGARIAN (langdetect
        # misfired 'hu'); the expanded markers ("soy","mandais") now win.
        "SOY MELILLA, MANDAIS A MELILLA",
        "soy de melilla, mandais cosas a melilla?",
    ],
)
def test_spanish_detected(text):
    assert detect_language(text) == "español"


@pytest.mark.parametrize(
    "text",
    [
        "bom dia, que produtos tendes?",
        "qual é o prazo de entrega para Lisboa?",
        "obrigado, quero devolver o meu pedido",
        "não recebi a minha encomenda",
    ],
)
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à"


@pytest.mark.parametrize(
    "text,expected",
    [
        ("مرحبا، ماذا تبيعون في متجركم؟", "árabe (Arabic)"),
        ("你们卖什么产品?我想了解一下", "chino simplificado (Chinese)"),
        ("こんにちは、どんな商品がありますか?", "japonés (Japanese)"),
        ("안녕하세요, 어떤 제품을 판매하나요?", "coreano (Korean)"),
        ("Здравствуйте, какие товары у вас есть?", "ruso (Russian)"),
        ("नमस्ते, आप क्या बेचते हैं?", "hindi (Hindi)"),
        ("Merhaba, hangi ürünleri satıyorsunuz?", "turco (Turkish)"),
        ("Hallo, welke producten verkoopt u?", "neerlandés (Dutch)"),
        # genuine Hungarian must STILL detect (the Melilla fix must not over-suppress hu)
        ("Szeretném tudni a szállítási költségeket Budapestre kérem", "húngaro (Hungarian)"),
    ],
)
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"


@pytest.mark.parametrize(
    "text",
    [
        # the real production bug: plain Spanish WITHOUT ¿¡ñ accents was pinned
        # English because the Spanish preposition "a" tripped _looks_english.
        "Hola, voy a comprar la Toorx msx70 y quiero saber los plazos de entrega "
        "para envios a la peninsula.",
        # same defect without a leading greeting (exercises the score path)
        "quiero comprar la bici y saber los plazos de envios a la peninsula",
        "necesito ayuda para elegir y comprar productos para mi pedido",
    ],
)
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"


@pytest.mark.parametrize(
    "text",
    [
        "i want to talk to a human",          # was detected Swahili
        "hello can you help me",               # was detected Welsh
        "i need help with my order please",
        "can you tell me where my product is",
    ],
)
def test_short_english_not_misdetected(text):
    # regression: short English must NOT be pinned to an exotic language
    assert detect_language(text) == "English"


@pytest.mark.parametrize(
    "text",
    [
        # regression: langdetect guessed Iberian, no markers → was pinned español
        "do you have garden hoses?",
        "do you sell garden products?",
    ],
)
def test_english_misclassified_as_iberian_stays_english(text):
    assert detect_language(text) == "English"


@pytest.mark.parametrize(
    "text,expected",
    [
        # regression: dense non-Latin under 12 chars returned None (no pin → drift)
        ("你们卖什么?", "chino simplificado (Chinese)"),
        ("你们有花园用的水管吗?", "chino simplificado (Chinese)"),
        ("정원용 호스 있나요?", "coreano (Korean)"),
        ("ガーデンホース", "japonés (Japanese)"),
        ("لديكم خراطيم؟", "árabe (Arabic)"),
    ],
)
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