mocks.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. # -*- coding: utf-8 -*-
  2. """
  3. Mock-Objekte für Plugin-Tests.
  4. Bietet Mock-Implementierungen der Trixy-Kernkomponenten.
  5. """
  6. from __future__ import annotations
  7. import asyncio
  8. from dataclasses import dataclass, field
  9. from datetime import datetime
  10. from typing import Any, Callable
  11. from enum import Enum, auto
  12. class MockServiceState(Enum):
  13. """Zustand eines Mock-Services."""
  14. STOPPED = auto()
  15. STARTING = auto()
  16. RUNNING = auto()
  17. STOPPING = auto()
  18. @dataclass
  19. class MockEvent:
  20. """Ein Mock-Event."""
  21. name: str
  22. data: dict[str, Any]
  23. timestamp: datetime = field(default_factory=datetime.now)
  24. cancelled: bool = False
  25. def cancel(self) -> None:
  26. """Bricht das Event ab."""
  27. self.cancelled = True
  28. class MockEventManager:
  29. """
  30. Mock-Implementierung des EventManagers.
  31. Zeichnet alle Events auf und ermöglicht das Testen
  32. von Event-basierten Interaktionen.
  33. Example:
  34. manager = MockEventManager()
  35. # Callback für Tests registrieren
  36. manager.set_emit_callback(my_callback)
  37. # Events werden aufgezeichnet
  38. manager.emit("test_event", {"key": "value"})
  39. # Events abrufen
  40. events = manager.get_events("test_event")
  41. """
  42. def __init__(self) -> None:
  43. """Initialisiert den Mock-EventManager."""
  44. self._events: list[MockEvent] = []
  45. self._handlers: dict[str, list[Callable]] = {}
  46. self._emit_callback: Callable[[str, dict], None] | None = None
  47. self._running = False
  48. @property
  49. def events(self) -> list[MockEvent]:
  50. """Alle aufgezeichneten Events."""
  51. return self._events.copy()
  52. def set_emit_callback(
  53. self,
  54. callback: Callable[[str, dict], None],
  55. ) -> None:
  56. """Setzt einen Callback für emittierte Events."""
  57. self._emit_callback = callback
  58. def emit(
  59. self,
  60. name: str,
  61. data: dict[str, Any] | None = None,
  62. ) -> MockEvent:
  63. """
  64. Emittiert ein Event.
  65. Args:
  66. name: Event-Name
  67. data: Event-Daten
  68. Returns:
  69. Das erstellte Event
  70. """
  71. event = MockEvent(name=name, data=data or {})
  72. self._events.append(event)
  73. # Callback benachrichtigen
  74. if self._emit_callback:
  75. self._emit_callback(name, data or {})
  76. # Handler aufrufen
  77. if name in self._handlers:
  78. for handler in self._handlers[name]:
  79. try:
  80. handler(name, data or {})
  81. except Exception:
  82. pass
  83. return event
  84. async def emit_async(
  85. self,
  86. name: str,
  87. data: dict[str, Any] | None = None,
  88. ) -> MockEvent:
  89. """Emittiert ein Event asynchron."""
  90. return self.emit(name, data)
  91. def subscribe(
  92. self,
  93. event_name: str,
  94. handler: Callable,
  95. ) -> None:
  96. """Registriert einen Event-Handler."""
  97. if event_name not in self._handlers:
  98. self._handlers[event_name] = []
  99. self._handlers[event_name].append(handler)
  100. def unsubscribe(
  101. self,
  102. event_name: str,
  103. handler: Callable,
  104. ) -> bool:
  105. """Entfernt einen Event-Handler."""
  106. if event_name in self._handlers:
  107. try:
  108. self._handlers[event_name].remove(handler)
  109. return True
  110. except ValueError:
  111. pass
  112. return False
  113. def get_events(self, name: str | None = None) -> list[MockEvent]:
  114. """
  115. Gibt Events zurück.
  116. Args:
  117. name: Optionaler Filter nach Event-Name
  118. Returns:
  119. Liste der Events
  120. """
  121. if name is None:
  122. return self._events.copy()
  123. return [e for e in self._events if e.name == name]
  124. def clear_events(self) -> None:
  125. """Löscht alle aufgezeichneten Events."""
  126. self._events.clear()
  127. def has_event(self, name: str) -> bool:
  128. """Prüft, ob ein Event existiert."""
  129. return any(e.name == name for e in self._events)
  130. def event_count(self, name: str | None = None) -> int:
  131. """Gibt die Anzahl der Events zurück."""
  132. return len(self.get_events(name))
  133. class MockSatelliteManager:
  134. """
  135. Mock-Implementierung des SatelliteManagers.
  136. Simuliert Satellite-Verbindungen und -Operationen.
  137. Example:
  138. manager = MockSatelliteManager()
  139. # Satellites hinzufügen
  140. manager.add_satellite("living_room", {"alias": "Wohnzimmer"})
  141. # Operationen testen
  142. satellite = manager.get("living_room")
  143. """
  144. def __init__(self) -> None:
  145. """Initialisiert den Mock-SatelliteManager."""
  146. self._satellites: dict[str, dict[str, Any]] = {}
  147. self._connected: set[str] = set()
  148. def add_satellite(
  149. self,
  150. room_id: str,
  151. data: dict[str, Any] | None = None,
  152. ) -> None:
  153. """Fügt einen Mock-Satellite hinzu."""
  154. self._satellites[room_id] = {
  155. "room_id": room_id,
  156. "connected": False,
  157. **(data or {}),
  158. }
  159. def remove_satellite(self, room_id: str) -> bool:
  160. """Entfernt einen Satellite."""
  161. if room_id in self._satellites:
  162. del self._satellites[room_id]
  163. self._connected.discard(room_id)
  164. return True
  165. return False
  166. def get(self, room_id: str) -> dict[str, Any] | None:
  167. """Gibt einen Satellite zurück."""
  168. return self._satellites.get(room_id)
  169. def get_all(self) -> list[dict[str, Any]]:
  170. """Gibt alle Satellites zurück."""
  171. return list(self._satellites.values())
  172. def connect(self, room_id: str) -> bool:
  173. """Simuliert eine Verbindung."""
  174. if room_id in self._satellites:
  175. self._connected.add(room_id)
  176. self._satellites[room_id]["connected"] = True
  177. return True
  178. return False
  179. def disconnect(self, room_id: str) -> bool:
  180. """Simuliert eine Trennung."""
  181. if room_id in self._connected:
  182. self._connected.discard(room_id)
  183. if room_id in self._satellites:
  184. self._satellites[room_id]["connected"] = False
  185. return True
  186. return False
  187. def is_connected(self, room_id: str) -> bool:
  188. """Prüft, ob ein Satellite verbunden ist."""
  189. return room_id in self._connected
  190. def get_connected(self) -> list[str]:
  191. """Gibt alle verbundenen Satellites zurück."""
  192. return list(self._connected)
  193. def clear(self) -> None:
  194. """Entfernt alle Satellites."""
  195. self._satellites.clear()
  196. self._connected.clear()
  197. class MockApplication:
  198. """
  199. Mock-Implementierung der Trixy-Application.
  200. Bietet Zugriff auf alle Mock-Komponenten.
  201. Example:
  202. app = MockApplication()
  203. # Services verwenden
  204. event_manager = app.event_manager
  205. satellite_manager = app.satellite_manager
  206. # Konfiguration setzen
  207. app.set_config("key", "value")
  208. """
  209. def __init__(
  210. self,
  211. event_manager: MockEventManager | None = None,
  212. satellite_manager: MockSatelliteManager | None = None,
  213. config: dict[str, Any] | None = None,
  214. ) -> None:
  215. """
  216. Initialisiert die Mock-Application.
  217. Args:
  218. event_manager: Optionaler EventManager
  219. satellite_manager: Optionaler SatelliteManager
  220. config: Optionale Konfiguration
  221. """
  222. self._event_manager = event_manager or MockEventManager()
  223. self._satellite_manager = satellite_manager or MockSatelliteManager()
  224. self._config = config or {}
  225. self._services: dict[str, Any] = {}
  226. self._state = MockServiceState.STOPPED
  227. @property
  228. def event_manager(self) -> MockEventManager:
  229. """Der EventManager."""
  230. return self._event_manager
  231. @property
  232. def satellite_manager(self) -> MockSatelliteManager:
  233. """Der SatelliteManager."""
  234. return self._satellite_manager
  235. @property
  236. def config(self) -> dict[str, Any]:
  237. """Die Konfiguration."""
  238. return self._config
  239. @property
  240. def state(self) -> MockServiceState:
  241. """Der Zustand der Application."""
  242. return self._state
  243. @property
  244. def is_running(self) -> bool:
  245. """Läuft die Application?"""
  246. return self._state == MockServiceState.RUNNING
  247. def set_config(self, key: str, value: Any) -> None:
  248. """Setzt einen Konfigurationswert."""
  249. self._config[key] = value
  250. def get_config(self, key: str, default: Any = None) -> Any:
  251. """Gibt einen Konfigurationswert zurück."""
  252. return self._config.get(key, default)
  253. def register_service(self, name: str, service: Any) -> None:
  254. """Registriert einen Service."""
  255. self._services[name] = service
  256. def get_service(self, name: str) -> Any:
  257. """Gibt einen Service zurück."""
  258. return self._services.get(name)
  259. def has_service(self, name: str) -> bool:
  260. """Prüft, ob ein Service existiert."""
  261. return name in self._services
  262. async def start(self) -> None:
  263. """Startet die Application."""
  264. self._state = MockServiceState.STARTING
  265. await asyncio.sleep(0) # Simuliert async-Start
  266. self._state = MockServiceState.RUNNING
  267. async def stop(self) -> None:
  268. """Stoppt die Application."""
  269. self._state = MockServiceState.STOPPING
  270. await asyncio.sleep(0) # Simuliert async-Stop
  271. self._state = MockServiceState.STOPPED
  272. def emit(self, name: str, data: dict[str, Any] | None = None) -> MockEvent:
  273. """Emittiert ein Event."""
  274. return self._event_manager.emit(name, data)
  275. class MockPlugin:
  276. """
  277. Mock-Implementierung eines Plugins.
  278. Kann als Basis für Test-Plugins oder zum Testen
  279. von Plugin-Interaktionen verwendet werden.
  280. Example:
  281. plugin = MockPlugin("test_plugin")
  282. # Lifecycle simulieren
  283. await plugin.on_start()
  284. # Methoden aufrufen
  285. plugin.set_state("key", "value")
  286. # Aufräumen
  287. await plugin.on_stop()
  288. """
  289. # Plugin-Metadaten
  290. NAME = "mock_plugin"
  291. VERSION = "1.0.0"
  292. DESCRIPTION = "Ein Mock-Plugin für Tests"
  293. AUTHOR = "Test"
  294. def __init__(
  295. self,
  296. name: str | None = None,
  297. application: MockApplication | None = None,
  298. config: dict[str, Any] | None = None,
  299. ) -> None:
  300. """
  301. Initialisiert das Mock-Plugin.
  302. Args:
  303. name: Plugin-Name
  304. application: Application-Referenz
  305. config: Plugin-Konfiguration
  306. """
  307. self._name = name or self.NAME
  308. self._application = application
  309. self._config = config or {}
  310. self._enabled = True
  311. self._state: dict[str, Any] = {}
  312. self._event_handlers: dict[str, list[Callable]] = {}
  313. self._started = False
  314. @property
  315. def name(self) -> str:
  316. """Plugin-Name."""
  317. return self._name
  318. @property
  319. def application(self) -> MockApplication | None:
  320. """Application-Referenz."""
  321. return self._application
  322. @property
  323. def config(self) -> dict[str, Any]:
  324. """Plugin-Konfiguration."""
  325. return self._config
  326. @config.setter
  327. def config(self, value: dict[str, Any]) -> None:
  328. """Setzt die Konfiguration."""
  329. self._config = value
  330. @property
  331. def enabled(self) -> bool:
  332. """Plugin aktiviert?"""
  333. return self._enabled
  334. @enabled.setter
  335. def enabled(self, value: bool) -> None:
  336. """Aktiviert/Deaktiviert das Plugin."""
  337. self._enabled = value
  338. @property
  339. def is_started(self) -> bool:
  340. """Wurde das Plugin gestartet?"""
  341. return self._started
  342. def get_state(self) -> dict[str, Any]:
  343. """Gibt den Plugin-Zustand zurück."""
  344. return self._state.copy()
  345. def set_state(self, state: dict[str, Any] | str, value: Any = None) -> None:
  346. """
  347. Setzt den Plugin-Zustand.
  348. Args:
  349. state: Dictionary oder einzelner Key
  350. value: Wert wenn state ein Key ist
  351. """
  352. if isinstance(state, dict):
  353. self._state.update(state)
  354. else:
  355. self._state[state] = value
  356. def register_event_handler(
  357. self,
  358. event_name: str,
  359. handler: Callable,
  360. ) -> None:
  361. """Registriert einen Event-Handler."""
  362. if event_name not in self._event_handlers:
  363. self._event_handlers[event_name] = []
  364. self._event_handlers[event_name].append(handler)
  365. async def on_pre_start(self) -> None:
  366. """Pre-Start Hook."""
  367. pass
  368. async def on_start(self) -> None:
  369. """Start Hook."""
  370. self._started = True
  371. async def on_post_start(self) -> None:
  372. """Post-Start Hook."""
  373. pass
  374. async def on_pre_stop(self) -> None:
  375. """Pre-Stop Hook."""
  376. pass
  377. async def on_stop(self) -> None:
  378. """Stop Hook."""
  379. self._started = False
  380. async def on_post_stop(self) -> None:
  381. """Post-Stop Hook."""
  382. pass
  383. def emit(self, name: str, data: dict[str, Any] | None = None) -> None:
  384. """Emittiert ein Event."""
  385. if self._application:
  386. self._application.emit(name, data)
  387. def reload_config(self) -> None:
  388. """Lädt die Konfiguration neu (Mock)."""
  389. pass
  390. def save_config(self) -> None:
  391. """Speichert die Konfiguration (Mock)."""
  392. pass
  393. class MockConfigManager:
  394. """
  395. Mock-Implementierung des ConfigManagers.
  396. Example:
  397. config = MockConfigManager({"key": "value"})
  398. value = config.get("key")
  399. """
  400. def __init__(self, data: dict[str, Any] | None = None) -> None:
  401. """Initialisiert den Mock-ConfigManager."""
  402. self._data = data or {}
  403. def get(self, key: str, default: Any = None) -> Any:
  404. """Gibt einen Wert zurück."""
  405. keys = key.split(".")
  406. value = self._data
  407. for k in keys:
  408. if isinstance(value, dict) and k in value:
  409. value = value[k]
  410. else:
  411. return default
  412. return value
  413. def set(self, key: str, value: Any) -> None:
  414. """Setzt einen Wert."""
  415. keys = key.split(".")
  416. data = self._data
  417. for k in keys[:-1]:
  418. if k not in data:
  419. data[k] = {}
  420. data = data[k]
  421. data[keys[-1]] = value
  422. def has(self, key: str) -> bool:
  423. """Prüft, ob ein Key existiert."""
  424. return self.get(key) is not None
  425. def to_dict(self) -> dict[str, Any]:
  426. """Gibt alle Daten als Dict zurück."""
  427. return self._data.copy()
  428. class MockScheduler:
  429. """
  430. Mock-Implementierung des Schedulers.
  431. Example:
  432. scheduler = MockScheduler()
  433. scheduler.schedule("task", cron="0 * * * *")
  434. """
  435. def __init__(self) -> None:
  436. """Initialisiert den Mock-Scheduler."""
  437. self._tasks: dict[str, dict[str, Any]] = {}
  438. self._triggered: list[str] = []
  439. def schedule(
  440. self,
  441. name: str,
  442. cron: str | None = None,
  443. interval: float | None = None,
  444. handler: Callable | None = None,
  445. ) -> None:
  446. """Plant eine Aufgabe."""
  447. self._tasks[name] = {
  448. "cron": cron,
  449. "interval": interval,
  450. "handler": handler,
  451. }
  452. def unschedule(self, name: str) -> bool:
  453. """Entfernt eine Aufgabe."""
  454. if name in self._tasks:
  455. del self._tasks[name]
  456. return True
  457. return False
  458. def trigger(self, name: str) -> bool:
  459. """Triggert eine Aufgabe manuell."""
  460. if name in self._tasks:
  461. self._triggered.append(name)
  462. handler = self._tasks[name].get("handler")
  463. if handler:
  464. try:
  465. handler()
  466. except Exception:
  467. pass
  468. return True
  469. return False
  470. def get_tasks(self) -> list[str]:
  471. """Gibt alle Task-Namen zurück."""
  472. return list(self._tasks.keys())
  473. def get_triggered(self) -> list[str]:
  474. """Gibt alle getriggerten Tasks zurück."""
  475. return self._triggered.copy()
  476. def clear_triggered(self) -> None:
  477. """Löscht die Trigger-Historie."""
  478. self._triggered.clear()