| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- # -*- coding: utf-8 -*-
- """
- Tests für das Piper TTS Plugin.
- """
- import pytest
- from pathlib import Path
- from unittest.mock import MagicMock, AsyncMock, patch
- # Plugin-Module importieren
- import sys
- import importlib.util
- # Plugin-main.py direkt laden
- plugin_path = Path(__file__).parent.parent / "main.py"
- spec = importlib.util.spec_from_file_location("piper_plugin", plugin_path)
- piper_module = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(piper_module)
- PiperTTSPlugin = piper_module.PiperTTSPlugin
- PiperTTSProvider = piper_module.PiperTTSProvider
- PIPER_VOICES = piper_module.PIPER_VOICES
- HUGGINGFACE_REPO = piper_module.HUGGINGFACE_REPO
- from trixy_core.audio.tts import TTSConfig, TTSState
- class TestPiperVoicesCatalog:
- """Tests für den Piper-Stimmen-Katalog."""
- def test_voices_catalog_not_empty(self):
- """Prüft, dass der Stimmenkatalog nicht leer ist."""
- assert len(PIPER_VOICES) > 0
- def test_voices_have_required_fields(self):
- """Prüft, dass alle Stimmen die erforderlichen Felder haben."""
- required_fields = ["name", "language", "gender", "description", "sample_rate", "hf_path"]
- for voice_id, voice_info in PIPER_VOICES.items():
- for field in required_fields:
- assert field in voice_info, f"Stimme {voice_id} fehlt Feld '{field}'"
- def test_german_voices_available(self):
- """Prüft, dass deutsche Stimmen verfügbar sind."""
- german_voices = [v for v in PIPER_VOICES.values() if v["language"] == "de-DE"]
- assert len(german_voices) >= 1
- def test_default_voice_exists(self):
- """Prüft, dass die Standard-Stimme existiert."""
- assert "de_DE-thorsten-medium" in PIPER_VOICES
- class TestPiperTTSProvider:
- """Tests für den PiperTTSProvider."""
- @pytest.fixture
- def provider(self, tmp_path):
- """Erstellt einen Provider für Tests."""
- config = TTSConfig(language="de-DE", voice_id="de_DE-thorsten-medium")
- return PiperTTSProvider(
- config=config,
- models_dir=tmp_path / "models",
- voice_id="de_DE-thorsten-medium",
- auto_download=False,
- )
- def test_provider_name(self, provider):
- """Prüft den Provider-Namen."""
- assert provider.name == "piper"
- 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
- def test_supports_streaming(self, provider):
- """Prüft Streaming-Unterstützung."""
- assert provider.supports_streaming is True
- def test_initial_state(self, provider):
- """Prüft den initialen Zustand."""
- assert provider._model_loaded is False
- assert provider._piper is None
- @pytest.mark.asyncio
- async def test_get_voices(self, provider):
- """Prüft die get_voices Methode."""
- all_voices = await provider.get_voices()
- assert len(all_voices) > 0
- german_voices = await provider.get_voices(language="de-DE")
- assert all(v.language == "de-DE" for v in german_voices)
- @pytest.mark.asyncio
- async def test_initialize_without_model_raises(self, provider):
- """Prüft, dass initialize ohne Modell fehlschlägt."""
- # Ohne echte piper-Installation oder Modell sollte es fehlschlagen
- with pytest.raises(RuntimeError):
- await provider.initialize()
- @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="Piper nicht geladen"):
- await provider.synthesize("Test")
- class TestPiperTTSPlugin:
- """Tests für das PiperTTSPlugin."""
- @pytest.fixture
- def plugin(self, mock_application, tmp_path):
- """Erstellt ein Plugin für Tests."""
- plugin_path = tmp_path / "tts_piper"
- plugin_path.mkdir()
- (plugin_path / "models").mkdir()
- config = {
- "name": "Piper TTS",
- "enabled": True,
- "voice": "de_DE-thorsten-medium",
- "language": "de-DE",
- "auto_download": False,
- }
- return PiperTTSPlugin(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("voice") == "de_DE-thorsten-medium"
- def test_plugin_path(self, plugin, tmp_path):
- """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."""
- # Sollte nicht fehlschlagen
- await plugin.on_unload()
- assert plugin._provider is None
- class TestPiperDownload:
- """Tests für die Download-Funktionalität."""
- @pytest.mark.asyncio
- async def test_download_model_unknown_voice_raises(self, tmp_path):
- """Prüft, dass unbekannte Stimmen einen Fehler werfen."""
- config = TTSConfig(language="de-DE")
- provider = PiperTTSProvider(
- config=config,
- models_dir=tmp_path / "models",
- voice_id="unknown_voice",
- auto_download=True,
- )
- with pytest.raises(RuntimeError, match="Unbekannte Stimme"):
- await provider._download_model()
- def test_huggingface_repo_configured(self):
- """Prüft, dass das Hugging Face Repository konfiguriert ist."""
- assert HUGGINGFACE_REPO == "rhasspy/piper-voices"
- # Integration Tests (benötigen echte Installation)
- @pytest.mark.integration
- @pytest.mark.requires_piper
- class TestPiperIntegration:
- """Integrationstests für Piper TTS (benötigen Installation)."""
- @pytest.fixture
- def provider_with_model(self, tmp_path):
- """Erstellt einen Provider mit echtem Modell."""
- config = TTSConfig(language="de-DE", voice_id="de_DE-thorsten-medium")
- return PiperTTSProvider(
- config=config,
- models_dir=tmp_path / "models",
- voice_id="de_DE-thorsten-medium",
- auto_download=True,
- )
- @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 == "piper"
- await provider_with_model.shutdown()
|