"""Tests unitaires des validations sans télécharger les modèles Hugging Face.""" import wave # Module standard pour écrire de petits WAV déterministes dans les tests. import numpy as np import pytest from src.audio import TARGET_SAMPLE_RATE, load_and_preprocess_audio from src.errors import AudioValidationError def write_wave(path, samples: np.ndarray) -> None: """Écrit un petit WAV PCM pour isoler la couche de prétraitement.""" # Le contexte ferme toujours le fichier, même si un test échoue. with wave.open(str(path), "wb") as wav: # La configuration reproduit un WAV mono PCM cohérent avec l'entrée du pipeline. wav.setnchannels(1) wav.setsampwidth(2) wav.setframerate(TARGET_SAMPLE_RATE) # WAV PCM 16 bits attend des entiers [-32768, 32767], pas des flottants NumPy [-1, 1]. wav.writeframes((samples * 32767).astype(np.int16).tobytes()) def test_loads_and_normalizes_valid_wav(tmp_path): # tmp_path isole chaque test : aucun fichier de démonstration du dépôt n'est modifié. path = tmp_path / "voice.wav" # Une sinusoïde est suffisante pour vérifier format, rééchantillonnage et normalisation. samples = 0.2 * np.sin(2 * np.pi * 300 * np.arange(TARGET_SAMPLE_RATE) / TARGET_SAMPLE_RATE) write_wave(path, samples) # L'appel ne charge aucun modèle ; il teste seulement la première étape du pipeline. result = load_and_preprocess_audio(path) assert result.dtype == np.float32 assert np.max(np.abs(result)) == pytest.approx(1.0) @pytest.mark.parametrize("filename", ["empty.wav", "invalid.txt"]) def test_rejects_empty_or_unsupported_file(tmp_path, filename): # Le test couvre à la fois une extension interdite et un WAV vide. path = tmp_path / filename path.touch() with pytest.raises(AudioValidationError): load_and_preprocess_audio(path) def test_rejects_silent_audio(tmp_path): # Un tableau de zéros correspond à une piste audio parfaitement silencieuse. path = tmp_path / "silence.wav" write_wave(path, np.zeros(TARGET_SAMPLE_RATE)) with pytest.raises(AudioValidationError, match="silencieux"): load_and_preprocess_audio(path)