85 lines
3.7 KiB
Python
85 lines
3.7 KiB
Python
"""Validation and text-handling tests for the aligner; they never load the model."""
|
|
import io
|
|
import json
|
|
import os
|
|
import sys
|
|
import unittest
|
|
import wave
|
|
|
|
import numpy as np
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
from fastapi.testclient import TestClient # noqa: E402
|
|
|
|
import app as aligner # noqa: E402
|
|
|
|
|
|
def wav_bytes(seconds=1.0, sr=16000):
|
|
buf = io.BytesIO()
|
|
with wave.open(buf, "wb") as w:
|
|
w.setnchannels(1); w.setsampwidth(2); w.setframerate(sr)
|
|
w.writeframes((np.zeros(int(seconds * sr))).astype("<i2").tobytes())
|
|
return buf.getvalue()
|
|
|
|
|
|
class NormalizeTests(unittest.TestCase):
|
|
def test_romanizes_german(self):
|
|
self.assertEqual(aligner.normalize_word("Größe,"), "grosse")
|
|
self.assertEqual(aligner.normalize_word("Über"), "uber")
|
|
self.assertEqual(aligner.normalize_word("2024."), "")
|
|
self.assertEqual(aligner.normalize_word("don't"), "don't")
|
|
|
|
def test_digits_get_the_gap_between_neighbours(self):
|
|
fake = aligner.align_samples(np.zeros(16000, dtype=np.float32), ["2024", "3"])
|
|
self.assertEqual([(a, ok) for a, _, ok in fake], [(0.0, False), (0.0, False)])
|
|
self.assertAlmostEqual(fake[0][1], 1.0)
|
|
|
|
|
|
class ValidationTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.c = TestClient(aligner.app)
|
|
|
|
def post(self, url="/align", audio=None, **data):
|
|
files = {"audio": ("a.wav", audio if audio is not None else wav_bytes(), "audio/wav")}
|
|
return self.c.post(url, files=files, data=data)
|
|
|
|
def test_health_works_without_the_model(self):
|
|
r = self.c.get("/health")
|
|
self.assertEqual(r.status_code, 200)
|
|
self.assertFalse(r.json()["model_loaded"])
|
|
|
|
def test_bad_language_text_and_audio_are_rejected(self):
|
|
self.assertEqual(self.post(text="Hallo Welt", language="../x").status_code, 422)
|
|
self.assertEqual(self.post(text=" ", language="de").status_code, 422)
|
|
self.assertEqual(self.post(text="1234 5678", language="de").status_code, 422)
|
|
self.assertEqual(self.post(text="x" * (aligner.MAX_TEXT_CHARS + 1), language="de").status_code, 422)
|
|
self.assertEqual(self.post(audio=b"not audio", text="Hallo", language="de").status_code, 415)
|
|
|
|
def test_span_must_lie_inside_the_audio(self):
|
|
self.assertEqual(self.post(text="Hallo", language="de", start="5", end="6").status_code, 422)
|
|
self.assertEqual(self.post(text="Hallo", language="de", start="0.5", end="0.2").status_code, 422)
|
|
|
|
def test_oversized_audio_is_rejected(self):
|
|
old = aligner.MAX_AUDIO_BYTES
|
|
aligner.MAX_AUDIO_BYTES = 100
|
|
try:
|
|
self.assertEqual(self.post(text="Hallo", language="de").status_code, 413)
|
|
finally:
|
|
aligner.MAX_AUDIO_BYTES = old
|
|
|
|
def test_batch_validates_spans(self):
|
|
self.assertEqual(self.post("/align/batch", spans="nonsense", language="de").status_code, 422)
|
|
self.assertEqual(self.post("/align/batch", spans="[]", language="de").status_code, 422)
|
|
self.assertEqual(self.post("/align/batch", spans=json.dumps([{"start": 0, "end": 1}]), language="de").status_code, 422)
|
|
many = json.dumps([{"start": 0, "end": 1, "text": "a"}] * (aligner.MAX_SPANS + 1))
|
|
self.assertEqual(self.post("/align/batch", spans=many, language="de").status_code, 422)
|
|
|
|
def test_letterless_sentence_in_a_batch_needs_no_model(self):
|
|
# no letters: handled by gap interpolation, but the model still loads first; only check validation passes
|
|
r = self.post("/align/batch", spans=json.dumps([{"start": 0, "end": 1, "text": "2024"}]), language="de")
|
|
self.assertNotEqual(r.status_code, 422)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|