# -*- coding: utf-8 -*- """ Tests für das Coqui TTS Plugin. """ import pytest from pathlib import Path from unittest.mock import MagicMock, AsyncMock, patch import importlib.util # Plugin-main.py direkt laden plugin_path = Path(__file__).parent.parent / "main.py" spec = importlib.util.spec_from_file_location("coqui_tts_plugin", plugin_path) coqui_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(coqui_module) CoquiTTSPlugin = coqui_module.CoquiTTSPlugin CoquiTTSProvider = coqui_module.CoquiTTSProvider COQUI_MODELS = coqui_module.COQUI_MODELS from trixy_core.audio.tts import TTSConfig, TTSState class TestCoquiModelsCatalog: """Tests für den Coqui-Modell-Katalog.""" def test_models_catalog_not_empty(self): """Prüft, dass der Modellkatalog nicht leer ist.""" assert len(COQUI_MODELS) > 0 def test_models_have_required_fields(self): """Prüft, dass alle Modelle die erforderlichen Felder haben.""" required_fields = ["name", "language", "gender", "description", "sample_rate"] for model_id, model_info in COQUI_MODELS.items(): for field in required_fields: assert field in model_info, f"Modell {model_id} fehlt Feld '{field}'" def test_german_models_available(self): """Prüft, dass deutsche Modelle verfügbar sind.""" german_models = [m for m, info in COQUI_MODELS.items() if info["language"] == "de-DE"] assert len(german_models) >= 1 def test_thorsten_models_available(self): """Prüft, dass Thorsten-Modelle (deutsche Stimme) verfügbar sind.""" thorsten_models = [m for m in COQUI_MODELS.keys() if "thorsten" in m] assert len(thorsten_models) >= 1 def test_model_ids_are_valid_paths(self): """Prüft, dass Modell-IDs gültige Coqui-Pfade sind.""" for model_id in COQUI_MODELS.keys(): assert model_id.startswith("tts_models/"), f"Modell {model_id} hat ungültiges Präfix" class TestCoquiTTSProvider: """Tests für den CoquiTTSProvider.""" @pytest.fixture def provider(self, tmp_path): """Erstellt einen Provider für Tests.""" config = TTSConfig(language="de-DE", voice_id="tts_models/de/thorsten/tacotron2-DDC") return CoquiTTSProvider( config=config, models_dir=tmp_path / "models", model_name="tts_models/de/thorsten/tacotron2-DDC", use_cuda=False, ) def test_provider_name(self, provider): """Prüft den Provider-Namen.""" assert provider.name == "coqui" def test_supported_languages(self, provider): """Prüft die unterstützten Sprachen.""" languages = provider.supported_languages assert "de-DE" in languages assert "en-US" in languages assert "fr-FR" in languages def test_supports_streaming(self, provider): """Prüft, dass Streaming nicht unterstützt wird.""" assert provider.supports_streaming is False def test_initial_state(self, provider): """Prüft den initialen Zustand.""" assert provider._model_loaded is False assert provider._tts is None def test_cuda_config(self): """Prüft die CUDA-Konfiguration.""" config = TTSConfig(language="de-DE") provider = CoquiTTSProvider( config=config, models_dir=Path("/tmp/models"), model_name="tts_models/de/thorsten/tacotron2-DDC", use_cuda=True, ) assert provider._use_cuda is True @pytest.mark.asyncio async def test_synthesize_without_init_raises(self, provider): """Prüft, dass synthesize ohne Initialisierung fehlschlägt.""" with pytest.raises(RuntimeError, match="Coqui TTS nicht geladen"): await provider.synthesize("Test") @pytest.mark.asyncio async def test_get_voices(self, provider): """Prüft die get_voices Methode (vor Initialisierung).""" # Vor Initialisierung ist der Voice-Katalog basierend auf COQUI_MODELS await provider._load_voices() voices = await provider.get_voices() assert len(voices) > 0 class TestCoquiTTSPlugin: """Tests für das CoquiTTSPlugin.""" @pytest.fixture def plugin(self, mock_application, tmp_path): """Erstellt ein Plugin für Tests.""" plugin_path = tmp_path / "tts_coqui" plugin_path.mkdir() (plugin_path / "models").mkdir() config = { "name": "Coqui TTS", "enabled": True, "model": "tts_models/de/thorsten/tacotron2-DDC", "language": "de-DE", "use_cuda": False, } return CoquiTTSPlugin(mock_application, plugin_path, config) def test_plugin_initialization(self, plugin): """Prüft die Plugin-Initialisierung.""" assert plugin._provider is None assert plugin.config.get("model") == "tts_models/de/thorsten/tacotron2-DDC" def test_plugin_path(self, plugin): """Prüft den Plugin-Pfad.""" assert plugin.plugin_path.exists() @pytest.mark.asyncio async def test_on_unload_without_provider(self, plugin): """Prüft on_unload ohne initialisierten Provider.""" await plugin.on_unload() assert plugin._provider is None class TestCoquiMultiSpeaker: """Tests für Multi-Speaker-Funktionalität.""" def test_voice_id_with_speaker(self): """Prüft Voice-ID mit Speaker-Extraktion.""" voice_id = "tts_models/multilingual/multi-dataset/your_tts:speaker_name" if ":" in voice_id: model_id, speaker = voice_id.split(":", 1) assert model_id == "tts_models/multilingual/multi-dataset/your_tts" assert speaker == "speaker_name" def test_multilingual_model_exists(self): """Prüft, dass ein multilinguales Modell verfügbar ist.""" multilingual = [m for m, info in COQUI_MODELS.items() if info["language"] == "multi"] assert len(multilingual) >= 1 # Integration Tests (benötigen echte Installation) @pytest.mark.integration class TestCoquiTTSIntegration: """Integrationstests für Coqui TTS (benötigen Installation).""" @pytest.fixture(autouse=True) def skip_if_not_installed(self): """Überspringt Tests wenn TTS nicht installiert ist.""" pytest.importorskip("TTS", reason="Coqui TTS nicht installiert") @pytest.fixture def provider_with_model(self, tmp_path): """Erstellt einen Provider mit echtem Modell.""" config = TTSConfig(language="de-DE", voice_id="tts_models/de/thorsten/tacotron2-DDC") return CoquiTTSProvider( config=config, models_dir=tmp_path / "models", model_name="tts_models/de/thorsten/tacotron2-DDC", use_cuda=False, ) @pytest.mark.asyncio @pytest.mark.slow async def test_full_synthesis_flow(self, provider_with_model): """Testet den vollständigen Synthese-Flow.""" await provider_with_model.initialize() result = await provider_with_model.synthesize("Hallo Welt") assert result.audio_data is not None assert len(result.audio_data) > 0 assert result.sample_rate > 0 assert result.duration_seconds > 0 assert result.provider == "coqui" await provider_with_model.shutdown()