test_deepspeech_stt.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. # -*- coding: utf-8 -*-
  2. """
  3. Tests für das DeepSpeech STT Plugin.
  4. """
  5. import pytest
  6. from pathlib import Path
  7. from unittest.mock import MagicMock, AsyncMock, patch
  8. import importlib.util
  9. # Plugin-main.py direkt laden
  10. plugin_path = Path(__file__).parent.parent / "main.py"
  11. spec = importlib.util.spec_from_file_location("deepspeech_plugin", plugin_path)
  12. deepspeech_module = importlib.util.module_from_spec(spec)
  13. spec.loader.exec_module(deepspeech_module)
  14. DeepSpeechSTTPlugin = deepspeech_module.DeepSpeechSTTPlugin
  15. DeepSpeechSTTProvider = deepspeech_module.DeepSpeechSTTProvider
  16. DEEPSPEECH_MODELS = deepspeech_module.DEEPSPEECH_MODELS
  17. from trixy_core.audio.stt import STTConfig, STTState
  18. class TestDeepSpeechModelsCatalog:
  19. """Tests für den DeepSpeech-Modell-Katalog."""
  20. def test_models_catalog_not_empty(self):
  21. """Prüft, dass der Modellkatalog nicht leer ist."""
  22. assert len(DEEPSPEECH_MODELS) > 0
  23. def test_models_have_required_fields(self):
  24. """Prüft, dass alle Modelle die erforderlichen Felder haben."""
  25. required_fields = ["name", "model_url", "scorer_url", "model_file", "scorer_file", "size", "description"]
  26. for model_id, model_info in DEEPSPEECH_MODELS.items():
  27. for field in required_fields:
  28. assert field in model_info, f"Modell {model_id} fehlt Feld '{field}'"
  29. def test_german_model_available(self):
  30. """Prüft, dass das deutsche Modell verfügbar ist."""
  31. assert "de" in DEEPSPEECH_MODELS
  32. def test_english_model_available(self):
  33. """Prüft, dass das englische Modell verfügbar ist."""
  34. assert "en" in DEEPSPEECH_MODELS
  35. def test_model_urls_are_valid(self):
  36. """Prüft, dass alle Modell-URLs gültige URLs sind."""
  37. for model_id, model_info in DEEPSPEECH_MODELS.items():
  38. model_url = model_info["model_url"]
  39. scorer_url = model_info["scorer_url"]
  40. assert model_url.startswith("https://"), f"Modell {model_id} hat ungültige Model-URL"
  41. assert scorer_url.startswith("https://"), f"Modell {model_id} hat ungültige Scorer-URL"
  42. class TestDeepSpeechSTTProvider:
  43. """Tests für den DeepSpeechSTTProvider."""
  44. @pytest.fixture
  45. def provider(self, tmp_path):
  46. """Erstellt einen Provider für Tests."""
  47. config = STTConfig(language="de-DE")
  48. return DeepSpeechSTTProvider(
  49. config=config,
  50. models_dir=tmp_path / "models",
  51. model_name="de",
  52. auto_download=False,
  53. )
  54. def test_provider_name(self, provider):
  55. """Prüft den Provider-Namen."""
  56. assert provider.name == "deepspeech"
  57. def test_supported_languages(self, provider):
  58. """Prüft die unterstützten Sprachen."""
  59. languages = provider.supported_languages
  60. assert "de-DE" in languages
  61. assert "en-US" in languages
  62. def test_supports_streaming(self, provider):
  63. """Prüft Streaming-Unterstützung."""
  64. assert provider.supports_streaming is True
  65. def test_supports_word_timings(self, provider):
  66. """Prüft Wort-Timings-Unterstützung."""
  67. assert provider.supports_word_timings is True
  68. def test_initial_state(self, provider):
  69. """Prüft den initialen Zustand."""
  70. assert provider._model_loaded is False
  71. assert provider._model is None
  72. @pytest.mark.asyncio
  73. async def test_initialize_without_model_raises(self, provider):
  74. """Prüft, dass initialize ohne Modell fehlschlägt."""
  75. with pytest.raises(RuntimeError):
  76. await provider.initialize()
  77. @pytest.mark.asyncio
  78. async def test_transcribe_without_init_raises(self, provider, sample_audio_16khz):
  79. """Prüft, dass transcribe ohne Initialisierung fehlschlägt."""
  80. with pytest.raises(RuntimeError, match="DeepSpeech-Modell nicht geladen"):
  81. await provider.transcribe(sample_audio_16khz)
  82. class TestDeepSpeechSTTPlugin:
  83. """Tests für das DeepSpeechSTTPlugin."""
  84. @pytest.fixture
  85. def plugin(self, mock_application, tmp_path):
  86. """Erstellt ein Plugin für Tests."""
  87. plugin_path = tmp_path / "stt_deepspeech"
  88. plugin_path.mkdir()
  89. (plugin_path / "models").mkdir()
  90. config = {
  91. "name": "DeepSpeech STT",
  92. "enabled": True,
  93. "model": "de",
  94. "language": "de-DE",
  95. "auto_download": False,
  96. }
  97. return DeepSpeechSTTPlugin(mock_application, plugin_path, config)
  98. def test_plugin_initialization(self, plugin):
  99. """Prüft die Plugin-Initialisierung."""
  100. assert plugin._provider is None
  101. assert plugin.config.get("model") == "de"
  102. def test_plugin_path(self, plugin):
  103. """Prüft den Plugin-Pfad."""
  104. assert plugin.plugin_path.exists()
  105. @pytest.mark.asyncio
  106. async def test_on_unload_without_provider(self, plugin):
  107. """Prüft on_unload ohne initialisierten Provider."""
  108. await plugin.on_unload()
  109. assert plugin._provider is None
  110. class TestDeepSpeechDownload:
  111. """Tests für die Download-Funktionalität."""
  112. @pytest.fixture
  113. def mock_event_manager(self):
  114. """Erstellt einen Mock-EventManager."""
  115. em = MagicMock()
  116. em.emit = AsyncMock()
  117. return em
  118. @pytest.mark.asyncio
  119. async def test_download_unknown_model_raises(self, tmp_path):
  120. """Prüft, dass unbekannte Modelle einen Fehler werfen."""
  121. config = STTConfig(language="de-DE")
  122. provider = DeepSpeechSTTProvider(
  123. config=config,
  124. models_dir=tmp_path / "models",
  125. model_name="unknown_model",
  126. auto_download=True,
  127. )
  128. # Entweder "Unbekanntes Modell" (wenn deepspeech installiert)
  129. # oder "nicht installiert" (wenn deepspeech fehlt)
  130. with pytest.raises(RuntimeError, match="(Unbekanntes Modell|nicht installiert)"):
  131. await provider.initialize()
  132. def test_model_paths_configured(self, tmp_path):
  133. """Prüft, dass Modellpfade korrekt konfiguriert werden."""
  134. config = STTConfig(language="de-DE")
  135. provider = DeepSpeechSTTProvider(
  136. config=config,
  137. models_dir=tmp_path / "models",
  138. model_name="de",
  139. auto_download=False,
  140. )
  141. # Nach dem Setzen der model_name sollten die Pfade noch None sein
  142. # bis initialize() aufgerufen wird
  143. assert provider._model_path is None
  144. assert provider._scorer_path is None
  145. class TestDeepSpeechEventHandling:
  146. """Tests für die Event-Verarbeitung."""
  147. @pytest.fixture
  148. def plugin_with_provider(self, mock_application, tmp_path):
  149. """Erstellt ein Plugin mit Mock-Provider."""
  150. plugin_path = tmp_path / "stt_deepspeech"
  151. plugin_path.mkdir()
  152. (plugin_path / "models").mkdir()
  153. config = {
  154. "name": "DeepSpeech STT",
  155. "enabled": True,
  156. "model": "de",
  157. "language": "de-DE",
  158. "auto_download": False,
  159. }
  160. plugin = DeepSpeechSTTPlugin(mock_application, plugin_path, config)
  161. # Mock-Provider setzen
  162. mock_provider = MagicMock()
  163. mock_provider.is_ready = True
  164. mock_provider.transcribe = AsyncMock(return_value=MagicMock(
  165. text="test text",
  166. confidence=0.95,
  167. language="de-DE",
  168. duration_seconds=1.0,
  169. processing_time_ms=100,
  170. word_timings=[],
  171. ))
  172. plugin._provider = mock_provider
  173. return plugin
  174. # Integration Tests (benötigen echte Installation)
  175. @pytest.mark.integration
  176. class TestDeepSpeechIntegration:
  177. """Integrationstests für DeepSpeech STT (benötigen Installation)."""
  178. @pytest.fixture(autouse=True)
  179. def skip_if_not_installed(self):
  180. """Überspringt Tests wenn deepspeech nicht installiert ist."""
  181. pytest.importorskip("deepspeech", reason="deepspeech nicht installiert")
  182. @pytest.fixture
  183. def provider_with_model(self, tmp_path):
  184. """Erstellt einen Provider mit echtem Modell."""
  185. config = STTConfig(language="de-DE")
  186. return DeepSpeechSTTProvider(
  187. config=config,
  188. models_dir=tmp_path / "models",
  189. model_name="de",
  190. auto_download=True,
  191. )
  192. @pytest.mark.asyncio
  193. @pytest.mark.slow
  194. async def test_full_transcription_flow(self, provider_with_model, sample_audio_16khz):
  195. """Testet den vollständigen Transkriptions-Flow."""
  196. await provider_with_model.initialize()
  197. result = await provider_with_model.transcribe(sample_audio_16khz)
  198. assert result.text is not None
  199. assert result.provider == "deepspeech"
  200. assert result.processing_time_ms > 0
  201. await provider_with_model.shutdown()