File size: 17,719 Bytes
b97b788
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
"""
tests/test_all.py โ€” Automated tests for MIC Translator
Tests: Speech Recognition, Translation, Language Detection,
       Text-to-Speech, History, Dataset Loading, Correction Engine
"""

import sys
import os
import json
import time

# Add parent directory to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from config import NLLB_LANGS, LANGUAGE_NAMES, NLLB_TO_ISO
from language_detector import LanguageDetector
from correction_engine import CorrectionEngine
from dataset_loader import DatasetLoader
from history import TranslationHistory


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TEST UTILITIES
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

PASS = 0
FAIL = 0
SKIP = 0


def test(name, condition, detail=""):
    global PASS, FAIL
    if condition:
        PASS += 1
        print(f"  [PASS] {name}")
    else:
        FAIL += 1
        print(f"  [FAIL] {name} {f'- {detail}' if detail else ''}")


def skip(name, reason=""):
    global SKIP
    SKIP += 1
    print(f"  [SKIP] {name} (skipped: {reason})")


def section(title):
    print(f"\n{'-'*50}")
    print(f"  {title}")
    print(f"{'-'*50}")


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TEST 1: Configuration
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def test_config():
    section("1. Configuration & Language Map")

    test("NLLB_LANGS has 100+ languages", len(NLLB_LANGS) >= 100,
         f"got {len(NLLB_LANGS)}")

    test("LANGUAGE_NAMES matches NLLB_LANGS count",
         len(LANGUAGE_NAMES) == len(NLLB_LANGS),
         f"names={len(LANGUAGE_NAMES)}, codes={len(NLLB_LANGS)}")

    test("NLLB_TO_ISO reverse lookup works",
         NLLB_TO_ISO.get("eng_Latn") == "en")

    # Key languages must be present
    key_langs = ["en", "fr", "de", "es", "hi", "ta", "zh", "ja",
                 "ko", "ar", "ru", "pt", "it", "bn", "ur"]
    for lang in key_langs:
        test(f"Language '{lang}' ({LANGUAGE_NAMES.get(lang, '?')}) is supported",
             lang in NLLB_LANGS)

    test("All NLLB codes have underscore format",
         all("_" in v for v in NLLB_LANGS.values()))

    test("No duplicate NLLB codes",
         len(set(NLLB_LANGS.values())) == len(NLLB_LANGS))


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TEST 2: Language Detection
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def test_language_detection():
    section("2. Language Detection")

    det = LanguageDetector()

    # English (Latin)
    r = det.detect_from_text("Hello, how are you today?")
    test("Detects English from Latin text", r["language"] == "en", f"got {r['language']}")

    # Hindi (Devanagari)
    r = det.detect_from_text("เคจเคฎเคธเฅเคคเฅ‡, เค†เคช เค•เฅˆเคธเฅ‡ เคนเฅˆเค‚?")
    test("Detects Hindi from Devanagari", r["language"] == "hi", f"got {r['language']}")

    # Tamil
    r = det.detect_from_text("เฎตเฎฃเฎ•เฏเฎ•เฎฎเฏ, เฎจเฏ€เฎ™เฏเฎ•เฎณเฏ เฎŽเฎชเฏเฎชเฎŸเฎฟ เฎ‡เฎฐเฏเฎ•เฏเฎ•เฎฟเฎฑเฏ€เฎฐเฏเฎ•เฎณเฏ?")
    test("Detects Tamil from Tamil script", r["language"] == "ta", f"got {r['language']}")

    # Arabic
    r = det.detect_from_text("ู…ุฑุญุจุง ูƒูŠู ุญุงู„ูƒ")
    test("Detects Arabic from Arabic script", r["language"] == "ar", f"got {r['language']}")

    # Japanese
    r = det.detect_from_text("ใ“ใ‚“ใซใกใฏใ€ๅ…ƒๆฐ—ใงใ™ใ‹๏ผŸ")
    test("Detects Japanese from Hiragana", r["language"] == "ja", f"got {r['language']}")

    # Korean
    r = det.detect_from_text("์•ˆ๋…•ํ•˜์„ธ์š”, ์–ด๋–ป๊ฒŒ ์ง€๋‚ด์„ธ์š”?")
    test("Detects Korean from Hangul", r["language"] == "ko", f"got {r['language']}")

    # Chinese
    r = det.detect_from_text("ไฝ ๅฅฝไธ–็•Œ")
    test("Detects Chinese from CJK", r["language"] == "zh", f"got {r['language']}")

    # Bengali
    r = det.detect_from_text("เฆนเงเฆฏเฆพเฆฒเง‹, เฆ•เง‡เฆฎเฆจ เฆ†เฆ›เง‡เฆจ?")
    test("Detects Bengali from Bengali script", r["language"] == "bn", f"got {r['language']}")

    # Telugu
    r = det.detect_from_text("เฐนเฐฒเฑ‹, เฐฎเฑ€เฐฐเฑ เฐŽเฐฒเฐพ เฐ‰เฐจเฑเฐจเฐพเฐฐเฑ?")
    test("Detects Telugu from Telugu script", r["language"] == "te", f"got {r['language']}")

    # Empty text
    r = det.detect_from_text("")
    test("Empty text defaults to English", r["language"] == "en")

    # NLLB code lookup
    test("get_nllb_code('en') -> eng_Latn", det.get_nllb_code("en") == "eng_Latn")
    test("get_nllb_code('ta') -> tam_Taml", det.get_nllb_code("ta") == "tam_Taml")
    test("get_nllb_code('ja') -> jpn_Jpan", det.get_nllb_code("ja") == "jpn_Jpan")

    test("is_supported('en') -> True", det.is_supported("en"))
    test("is_supported('xyz') -> False", not det.is_supported("xyz"))

    all_langs = det.get_all_languages()
    test("get_all_languages returns 100+", len(all_langs) >= 100)


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TEST 3: Correction Engine
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def test_correction_engine():
    section("3. Correction Engine")

    ce = CorrectionEngine()
    ce.load()

    test("Corrections loaded", ce.loaded)
    test("Slang entries > 30", len(ce.slang_map) > 30, f"got {len(ce.slang_map)}")
    test("Short forms > 20", len(ce.short_forms) > 20, f"got {len(ce.short_forms)}")
    test("Confused words > 10", len(ce.confused_words) > 10)

    # Pre-translation corrections
    r = ce.pre_translate("I gonna go to the store", "en")
    test("Slang: 'gonna' -> 'going to'", "going to" in r["text"], r["text"])

    r = ce.pre_translate("I wanna eat something", "en")
    test("Slang: 'wanna' -> 'want to'", "want to" in r["text"], r["text"])

    r = ce.pre_translate("idk what to do tbh", "en")
    test("Slang: 'idk' expanded", "don't know" in r["text"].lower(), r["text"])

    r = ce.pre_translate("hellooo how are you", "en")
    test("Pre-correction: 'hellooo' -> 'hello'", "hello" in r["text"].lower(), r["text"])

    r = ce.pre_translate("Normal sentence here.", "en")
    test("No corrections on clean text", not r["corrected"])

    # Stats
    stats = ce.get_stats()
    test("Stats returns total > 50", stats["total"] > 50, f"got {stats['total']}")

    # Add custom correction
    ok = ce.add_correction("slang", "yolo", "you only live once")
    test("Can add custom correction", ok)

    r = ce.pre_translate("yolo my friend", "en")
    test("Custom correction applied", "you only live once" in r["text"], r["text"])


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TEST 4: Dataset Loader
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def test_dataset_loader():
    section("4. Dataset Loader")

    dl = DatasetLoader()
    dl.load_all()

    stats = dl.get_stats()
    test("Dataset loader initializes", stats is not None)
    test("Datasets directory created",
         os.path.exists(os.path.join(os.path.dirname(os.path.dirname(__file__)), "datasets")))

    # Test with a sample custom dataset
    custom_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)),
                               "datasets", "custom")
    sample_file = os.path.join(custom_dir, "test_sample.json")

    sample_data = [
        {"source_lang": "en", "target_lang": "fr",
         "source_text": "Hello", "target_text": "Bonjour"},
        {"source_lang": "en", "target_lang": "fr",
         "source_text": "Good morning", "target_text": "Bonjour"},
    ]

    os.makedirs(custom_dir, exist_ok=True)
    with open(sample_file, "w", encoding="utf-8") as f:
        json.dump(sample_data, f)

    # Reload
    dl2 = DatasetLoader()
    dl2.load_all()

    ref = dl2.get_reference("Hello", "en", "fr")
    test("Reference lookup works", ref == "Bonjour", f"got {ref}")

    val = dl2.validate_translation("Hello", "Bonjour", "en", "fr")
    test("Validation: exact match", val["match"] is True)
    test("Validation: quality_score = 1.0", val["quality_score"] == 1.0)

    val2 = dl2.validate_translation("Hello", "Salut", "en", "fr")
    test("Validation: non-match detected", val2["match"] is False)

    # Cleanup
    try:
        os.unlink(sample_file)
    except OSError:
        pass


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TEST 5: History
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def test_history():
    section("5. Translation History")

    h = TranslationHistory()
    h.entries = []
    h.favorites = []
    h.recent_langs = []

    # Add entries
    h.add({
        "original": "Hello",
        "translated": "Bonjour",
        "source_lang": "en",
        "target_lang": "fr",
        "time_ms": 150
    })

    test("Add entry works", len(h.entries) == 1)
    test("Entry has timestamp", "timestamp" in h.entries[0])

    # Recent languages
    test("Recent langs updated", "fr" in h.recent_langs)

    # Favorites
    h.add_favorite("ta")
    test("Add favorite works", "ta" in h.get_favorites())

    h.remove_favorite("ta")
    test("Remove favorite works", "ta" not in h.get_favorites())

    # Search
    h.add({
        "original": "Good morning",
        "translated": "Buenos dรญas",
        "source_lang": "en",
        "target_lang": "es",
        "time_ms": 200
    })

    results = h.search("morning")
    test("Search works", len(results) == 1)

    results = h.search("xyz_not_found")
    test("Search returns empty for no match", len(results) == 0)

    # Export
    csv_str = h.export_csv()
    test("CSV export works", "Hello" in csv_str and "Bonjour" in csv_str)

    json_str = h.export_json()
    test("JSON export works", "Hello" in json_str)

    # Stats
    stats = h.get_stats()
    test("Stats: total = 2", stats["total_translations"] == 2)
    test("Stats: avg_time > 0", stats["avg_time_ms"] > 0)

    # Clear
    h.clear()
    test("Clear works", len(h.entries) == 0)


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TEST 6: Speech Recognition (requires Whisper model)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def test_speech():
    section("6. Speech Recognition (Whisper)")

    try:
        from speech import SpeechRecognizer
        sr = SpeechRecognizer()
        sr.load()

        test("Whisper model loads", sr.ready)

        # Test with existing test.wav if available
        test_wav = os.path.join(os.path.dirname(os.path.dirname(__file__)), "test.wav")
        if os.path.exists(test_wav) and sr.ready:
            result = sr.transcribe(test_wav)
            test("Transcription produces text", len(result.get("text", "")) > 0,
                 f"text='{result.get('text', '')}'")
            test("Language detected", result.get("language") is not None,
                 f"lang={result.get('language')}")
        else:
            skip("Transcription test", "no test.wav found or model not ready")

    except Exception as e:
        skip("Whisper tests", str(e))


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TEST 7: Translation (requires NLLB model)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def test_translation():
    section("7. Translation (NLLB-200)")

    try:
        from translator import Translator
        t = Translator()
        t.load()

        test("NLLB model loads", t.ready)

        if t.ready:
            # English -> French
            r = t.translate("Hello, how are you?", "en", "fr")
            test("EN->FR translation works", len(r["translated"]) > 0 and r["error"] is None,
                 r.get("translated", ""))
            test("Translation time recorded", r["time_ms"] > 0)

            # English -> Hindi
            r = t.translate("Good morning", "en", "hi")
            test("EN->HI translation works", len(r["translated"]) > 0 and r["error"] is None,
                 r.get("translated", ""))

            # English -> Tamil
            r = t.translate("Thank you", "en", "ta")
            test("EN->TA translation works", len(r["translated"]) > 0 and r["error"] is None,
                 r.get("translated", ""))

            # Same language (no-op)
            r = t.translate("Test", "en", "en")
            test("Same-lang returns original", r["translated"] == "Test")

            # Unsupported language
            r = t.translate("Test", "xyz", "en")
            test("Unsupported lang returns error", r["error"] is not None)

            # Language count
            test("200+ languages supported", t.get_language_count() >= 100)
        else:
            skip("Translation tests", "NLLB model not loaded")

    except Exception as e:
        skip("Translation tests", str(e))


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TEST 8: Text-to-Speech
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def test_tts():
    section("8. Text-to-Speech (Piper)")

    try:
        from tts import TextToSpeech
        t = TextToSpeech()
        t.load()

        test("TTS initializes", t.ready)

        voices = t.get_available_voices()
        test("Voices discovered", len(voices) >= 0, f"voices={list(voices.keys())}")

        stats = t.get_stats()
        test("Stats returns data", stats is not None)

        if t.piper_available and voices:
            r = t.synthesize("Hello world", "en")
            test("EN synthesis works", r.get("success") is True, r.get("error", ""))
        else:
            skip("TTS synthesis test", "Piper not installed or no voice models")

    except Exception as e:
        skip("TTS tests", str(e))


# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# RUN ALL TESTS
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

if __name__ == "__main__":
    print("\n" + "=" * 50)
    print("  MIC Translator - Automated Test Suite")
    print("=" * 50)

    start = time.time()

    test_config()
    test_language_detection()
    test_correction_engine()
    test_dataset_loader()
    test_history()
    test_speech()
    test_translation()
    test_tts()

    elapsed = round(time.time() - start, 1)

    print(f"\n{'='*50}")
    print(f"  RESULTS: {PASS} passed | {FAIL} failed | {SKIP} skipped")
    print(f"  Time: {elapsed}s")
    print(f"{'='*50}\n")

    sys.exit(1 if FAIL > 0 else 0)