test_application.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. # -*- coding: utf-8 -*-
  2. """
  3. Unit-Tests für trixy_core.application.
  4. """
  5. import asyncio
  6. import json
  7. import pytest
  8. from pathlib import Path
  9. from unittest.mock import MagicMock, AsyncMock, patch
  10. from trixy_core.application import IApplication
  11. from trixy_core.server import ServerApplication
  12. from trixy_core.client import ClientApplication
  13. from trixy_core.standalone import StandaloneApplication
  14. from trixy_core.trainer import TrainerApplication, TrainingTarget
  15. from trixy_core.service.service_container import ServiceContainer
  16. from trixy_core.events.eventmanager import EventManager
  17. from trixy_core.config.config_manager import ConfigManager
  18. class ConcreteApplication(IApplication):
  19. """Konkrete Anwendungsimplementierung für Tests."""
  20. def __init__(self, debug=False):
  21. super().__init__(debug)
  22. self.initialized = False
  23. self.started = False
  24. self.stopped = False
  25. async def initialize(self):
  26. self.initialized = True
  27. async def start(self):
  28. self.started = True
  29. async def stop(self):
  30. self.stopped = True
  31. class TestIApplication:
  32. """Tests für IApplication-Basisklasse."""
  33. @pytest.fixture
  34. def app(self):
  35. """Erstellt eine Test-Anwendung."""
  36. return ConcreteApplication(debug=True)
  37. def test_app_initialization(self, app):
  38. """Testet die Anwendungs-Initialisierung."""
  39. assert app.debug is True
  40. assert app.running is False
  41. assert isinstance(app.services, ServiceContainer)
  42. assert isinstance(app.events, EventManager)
  43. assert isinstance(app.config_manager, ConfigManager)
  44. def test_config_alias(self, app):
  45. """Testet den config-Alias."""
  46. assert app.config == app.config_manager
  47. def test_base_path(self, app):
  48. """Testet den Basis-Pfad."""
  49. assert isinstance(app.base_path, Path)
  50. def test_get_path(self, app):
  51. """Testet get_path."""
  52. path = app.get_path("subdir", "file.txt")
  53. assert path.name == "file.txt"
  54. assert "subdir" in str(path)
  55. def test_shutdown(self, app):
  56. """Testet shutdown."""
  57. app.shutdown()
  58. assert app._shutdown_event.is_set()
  59. class TestServerApplication:
  60. """Tests für ServerApplication."""
  61. @pytest.fixture
  62. def config_file(self, temp_dir):
  63. """Erstellt eine Server-Konfigurationsdatei."""
  64. config = {
  65. "network": {
  66. "command_port": 0, # Ephemeral port
  67. "audio_input_port": 0,
  68. "audio_output_port": 0,
  69. "music_port": 0
  70. },
  71. "plugins": {"enabled": False},
  72. "satellites_directory": str(temp_dir / "satellites")
  73. }
  74. path = temp_dir / "server_config.json"
  75. path.write_text(json.dumps(config))
  76. return path
  77. @pytest.fixture
  78. def server(self, config_file):
  79. """Erstellt eine ServerApplication."""
  80. return ServerApplication(config_path=config_file, debug=True)
  81. @pytest.mark.asyncio
  82. async def test_server_initialization(self, server):
  83. """Testet die Server-Initialisierung."""
  84. await server.initialize()
  85. assert server._server_config is not None
  86. assert server.satellites is not None
  87. assert server.registration is not None
  88. assert server.plugins is not None
  89. @pytest.mark.asyncio
  90. async def test_server_config_access(self, server):
  91. """Testet den Konfigurationszugriff."""
  92. await server.initialize()
  93. # Port 0 = ephemeral, wird vom System zugewiesen
  94. assert server.server_config.network.command_port == 0
  95. @pytest.mark.asyncio
  96. async def test_server_config_not_initialized(self, server):
  97. """Testet Zugriff vor Initialisierung."""
  98. with pytest.raises(RuntimeError, match="nicht initialisiert"):
  99. _ = server.server_config
  100. @pytest.mark.asyncio
  101. async def test_server_start(self, server):
  102. """Testet den Server-Start."""
  103. await server.initialize()
  104. await server.start()
  105. # Services sollten gestartet sein
  106. @pytest.mark.asyncio
  107. async def test_server_stop(self, server):
  108. """Testet das Server-Stoppen."""
  109. await server.initialize()
  110. await server.start()
  111. await server.stop()
  112. # Sollte ohne Fehler durchlaufen
  113. class TestClientApplication:
  114. """Tests für ClientApplication."""
  115. @pytest.fixture
  116. def config_file(self, temp_dir):
  117. """Erstellt eine Client-Konfigurationsdatei."""
  118. config = {
  119. "server": {"host": "localhost", "port": 2101},
  120. "identity": {"room": "test", "alias": "TestClient"}
  121. }
  122. path = temp_dir / "client_config.json"
  123. path.write_text(json.dumps(config))
  124. return path
  125. @pytest.fixture
  126. def client(self, config_file):
  127. """Erstellt eine ClientApplication."""
  128. return ClientApplication(
  129. host="192.168.1.100",
  130. port=3000,
  131. room="wohnzimmer",
  132. alias="TestEcho",
  133. config_path=config_file,
  134. debug=True
  135. )
  136. @pytest.mark.asyncio
  137. async def test_client_initialization(self, client):
  138. """Testet die Client-Initialisierung."""
  139. await client.initialize()
  140. assert client._client_config is not None
  141. def test_client_cli_params_override(self, client):
  142. """Testet CLI-Parameter-Überschreibung."""
  143. assert client.server_host == "192.168.1.100"
  144. assert client.server_port == 3000
  145. assert client.room == "wohnzimmer"
  146. assert client.alias == "TestEcho"
  147. @pytest.mark.asyncio
  148. async def test_client_config_access(self, client):
  149. """Testet den Konfigurationszugriff."""
  150. await client.initialize()
  151. assert client.client_config is not None
  152. @pytest.mark.asyncio
  153. async def test_client_start(self, client):
  154. """Testet den Client-Start (ohne echten Server)."""
  155. await client.initialize()
  156. await client.start()
  157. # Ohne echten Server bleibt is_connected False
  158. # Der eigentliche Verbindungstest ist in test_network_transport.py
  159. # _running wird nur in run() gesetzt, nicht in start()
  160. assert client.is_connected is False # Kein Server erreichbar
  161. @pytest.mark.asyncio
  162. async def test_client_stop(self, client):
  163. """Testet das Client-Stoppen."""
  164. await client.initialize()
  165. await client.start()
  166. await client.stop()
  167. assert client.is_connected is False
  168. class TestStandaloneApplication:
  169. """Tests für StandaloneApplication."""
  170. @pytest.fixture
  171. def config_file(self, temp_dir):
  172. """Erstellt eine Standalone-Konfigurationsdatei."""
  173. config = {
  174. "room": "standalone_test",
  175. "alias": "TrixyTest",
  176. "plugins": {"enabled": False, "plugin_directory": str(temp_dir / "plugins")}
  177. }
  178. path = temp_dir / "standalone_config.json"
  179. path.write_text(json.dumps(config))
  180. return path
  181. @pytest.fixture
  182. def standalone(self, config_file):
  183. """Erstellt eine StandaloneApplication."""
  184. return StandaloneApplication(config_path=config_file, debug=True)
  185. @pytest.mark.asyncio
  186. async def test_standalone_initialization(self, standalone):
  187. """Testet die Standalone-Initialisierung."""
  188. await standalone.initialize()
  189. assert standalone._standalone_config is not None
  190. assert standalone.plugins is not None
  191. @pytest.mark.asyncio
  192. async def test_standalone_config_access(self, standalone):
  193. """Testet den Konfigurationszugriff."""
  194. await standalone.initialize()
  195. assert standalone.standalone_config.room == "standalone_test"
  196. assert standalone.standalone_config.alias == "TrixyTest"
  197. def test_standalone_room_alias(self, standalone):
  198. """Testet room und alias Eigenschaften."""
  199. # Vor Initialisierung
  200. assert standalone.room == "standalone" # Default
  201. assert standalone.alias == "Trixy" # Default
  202. @pytest.mark.asyncio
  203. async def test_standalone_start(self, standalone):
  204. """Testet den Standalone-Start."""
  205. await standalone.initialize()
  206. await standalone.start()
  207. @pytest.mark.asyncio
  208. async def test_standalone_stop(self, standalone):
  209. """Testet das Standalone-Stoppen."""
  210. await standalone.initialize()
  211. await standalone.start()
  212. await standalone.stop()
  213. class TestTrainerApplication:
  214. """Tests für TrainerApplication."""
  215. @pytest.fixture
  216. def config_file(self, temp_dir):
  217. """Erstellt eine Trainer-Konfigurationsdatei."""
  218. config = {
  219. "wakeword": {"model_name": "test", "epochs": 10},
  220. "data": {
  221. "raw_data_directory": str(temp_dir / "data"),
  222. "processed_data_directory": str(temp_dir / "processed"),
  223. "cache_directory": str(temp_dir / "cache")
  224. },
  225. "output": {"models_directory": str(temp_dir / "models")}
  226. }
  227. path = temp_dir / "trainer_config.json"
  228. path.write_text(json.dumps(config))
  229. return path
  230. @pytest.fixture
  231. def trainer(self, config_file):
  232. """Erstellt eine TrainerApplication."""
  233. return TrainerApplication(
  234. target=TrainingTarget.WAKEWORD,
  235. config_path=config_file,
  236. debug=True
  237. )
  238. def test_training_targets(self):
  239. """Testet TrainingTarget-Enum."""
  240. assert hasattr(TrainingTarget, "WAKEWORD")
  241. assert hasattr(TrainingTarget, "VOICE_RECOGNITION")
  242. assert hasattr(TrainingTarget, "ALL")
  243. @pytest.mark.asyncio
  244. async def test_trainer_initialization(self, trainer):
  245. """Testet die Trainer-Initialisierung."""
  246. await trainer.initialize()
  247. assert trainer._trainer_config is not None
  248. @pytest.mark.asyncio
  249. async def test_trainer_config_access(self, trainer):
  250. """Testet den Konfigurationszugriff."""
  251. await trainer.initialize()
  252. assert trainer.trainer_config.wakeword.model_name == "test"
  253. assert trainer.trainer_config.wakeword.epochs == 10
  254. def test_trainer_target(self, trainer):
  255. """Testet die target-Eigenschaft."""
  256. assert trainer.target == TrainingTarget.WAKEWORD
  257. @pytest.mark.asyncio
  258. async def test_trainer_start_creates_directories(self, trainer, temp_dir):
  259. """Testet, dass Start Verzeichnisse erstellt."""
  260. await trainer.initialize()
  261. await trainer.start()
  262. # Verzeichnisse sollten erstellt worden sein
  263. assert (temp_dir / "data").exists()
  264. assert (temp_dir / "processed").exists()
  265. assert (temp_dir / "cache").exists()
  266. assert (temp_dir / "models").exists()
  267. @pytest.mark.asyncio
  268. async def test_trainer_stop(self, trainer):
  269. """Testet das Trainer-Stoppen."""
  270. await trainer.initialize()
  271. await trainer.stop()
  272. class TestApplicationIntegration:
  273. """Integrationstests für Anwendungsklassen."""
  274. @pytest.fixture
  275. def config_dir(self, temp_dir):
  276. """Erstellt ein Konfigurations-Verzeichnis."""
  277. config_dir = temp_dir / "config"
  278. config_dir.mkdir()
  279. # Server-Konfiguration
  280. (config_dir / "server_config.json").write_text(json.dumps({
  281. "network": {"command_port": 2101},
  282. "plugins": {"enabled": False},
  283. "satellites_directory": str(temp_dir / "satellites")
  284. }))
  285. # Client-Konfiguration
  286. (config_dir / "client_config.json").write_text(json.dumps({
  287. "server": {"host": "localhost", "port": 2101}
  288. }))
  289. # Standalone-Konfiguration
  290. (config_dir / "standalone_config.json").write_text(json.dumps({
  291. "plugins": {"enabled": False}
  292. }))
  293. return config_dir
  294. @pytest.mark.asyncio
  295. async def test_server_client_config_compatibility(self, config_dir):
  296. """Testet Konfigurationskompatibilität zwischen Server und Client."""
  297. server = ServerApplication(
  298. config_path=config_dir / "server_config.json",
  299. debug=True
  300. )
  301. await server.initialize()
  302. client = ClientApplication(
  303. host="localhost",
  304. port=server.server_config.network.command_port,
  305. room="test",
  306. alias="test",
  307. config_path=config_dir / "client_config.json",
  308. debug=True
  309. )
  310. await client.initialize()
  311. assert client.server_port == server.server_config.network.command_port
  312. await server.stop()
  313. await client.stop()