test_plugins.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. # -*- coding: utf-8 -*-
  2. """
  3. Unit-Tests für trixy_core.plugins.
  4. """
  5. import json
  6. import pytest
  7. from pathlib import Path
  8. from unittest.mock import MagicMock, AsyncMock
  9. from trixy_core.plugins.trixy_plugin import TrixyPlugin
  10. from trixy_core.plugins.plugin_manager import PluginManager
  11. class ConcretePlugin(TrixyPlugin):
  12. """Konkrete Plugin-Implementierung für Tests."""
  13. NAME = "TestPlugin"
  14. VERSION = "1.0.0"
  15. DESCRIPTION = "Ein Test-Plugin"
  16. AUTHOR = "Test Author"
  17. def __init__(self, application, plugin_path, config=None):
  18. super().__init__(application, plugin_path, config)
  19. self.loaded = False
  20. self.unloaded = False
  21. async def on_load(self):
  22. self.loaded = True
  23. async def on_unload(self):
  24. self.unloaded = True
  25. class TestTrixyPlugin:
  26. """Tests für TrixyPlugin-Basisklasse."""
  27. @pytest.fixture
  28. def mock_application(self):
  29. """Erstellt eine Mock-Anwendung."""
  30. return MagicMock()
  31. @pytest.fixture
  32. def plugin_path(self, temp_dir):
  33. """Erstellt einen Plugin-Pfad."""
  34. plugin_dir = temp_dir / "test_plugin"
  35. plugin_dir.mkdir()
  36. return plugin_dir
  37. @pytest.fixture
  38. def plugin(self, mock_application, plugin_path):
  39. """Erstellt ein Test-Plugin."""
  40. config = {"setting1": "value1", "nested": {"key": "value"}}
  41. return ConcretePlugin(mock_application, plugin_path, config)
  42. def test_plugin_initialization(self, plugin, mock_application, plugin_path):
  43. """Testet die Plugin-Initialisierung."""
  44. assert plugin.application == mock_application
  45. assert plugin.plugin_path == plugin_path
  46. assert plugin.enabled is True
  47. def test_plugin_name(self, plugin):
  48. """Testet den Plugin-Namen."""
  49. assert plugin.name == "TestPlugin"
  50. def test_plugin_version(self, plugin):
  51. """Testet die Plugin-Version."""
  52. assert plugin.VERSION == "1.0.0"
  53. def test_plugin_config(self, plugin):
  54. """Testet die Plugin-Konfiguration."""
  55. assert plugin.config["setting1"] == "value1"
  56. def test_plugin_enabled(self, plugin):
  57. """Testet enabled-Eigenschaft."""
  58. assert plugin.enabled is True
  59. assert plugin.is_enabled() is True
  60. plugin.enabled = False
  61. assert plugin.enabled is False
  62. assert plugin.is_enabled() is False
  63. def test_get_config_value(self, plugin):
  64. """Testet get_config_value."""
  65. assert plugin.get_config_value("setting1") == "value1"
  66. assert plugin.get_config_value("nested.key") == "value"
  67. assert plugin.get_config_value("nonexistent", "default") == "default"
  68. def test_set_config_value(self, plugin):
  69. """Testet set_config_value."""
  70. plugin.set_config_value("new_setting", "new_value")
  71. assert plugin.config["new_setting"] == "new_value"
  72. plugin.set_config_value("nested.new_key", "nested_value")
  73. assert plugin.config["nested"]["new_key"] == "nested_value"
  74. def test_reload_config(self, plugin, plugin_path):
  75. """Testet reload_config."""
  76. # Erstelle config.json
  77. config_file = plugin_path / "config.json"
  78. config_file.write_text(json.dumps({"reloaded": True}))
  79. result = plugin.reload_config()
  80. assert result is True
  81. assert plugin.config["reloaded"] is True
  82. def test_reload_config_no_file(self, plugin):
  83. """Testet reload_config ohne Datei."""
  84. result = plugin.reload_config()
  85. assert result is False
  86. def test_save_config(self, plugin, plugin_path):
  87. """Testet save_config."""
  88. plugin.config["saved_setting"] = "saved_value"
  89. result = plugin.save_config()
  90. assert result is True
  91. config_file = plugin_path / "config.json"
  92. assert config_file.exists()
  93. data = json.loads(config_file.read_text())
  94. assert data["saved_setting"] == "saved_value"
  95. @pytest.mark.asyncio
  96. async def test_on_load(self, plugin):
  97. """Testet on_load."""
  98. await plugin.on_load()
  99. assert plugin.loaded is True
  100. @pytest.mark.asyncio
  101. async def test_on_unload(self, plugin):
  102. """Testet on_unload."""
  103. await plugin.on_unload()
  104. assert plugin.unloaded is True
  105. @pytest.mark.asyncio
  106. async def test_lifecycle_hooks(self, plugin):
  107. """Testet Lifecycle-Hooks."""
  108. # Diese sollten ohne Fehler durchlaufen
  109. await plugin.on_enable()
  110. await plugin.on_disable()
  111. await plugin.on_config_change({})
  112. def test_repr(self, plugin):
  113. """Testet __repr__."""
  114. repr_str = repr(plugin)
  115. assert "Plugin" in repr_str
  116. assert "TestPlugin" in repr_str
  117. assert "1.0.0" in repr_str
  118. class TestPluginManager:
  119. """Tests für PluginManager."""
  120. @pytest.fixture
  121. def mock_application(self):
  122. """Erstellt eine Mock-Anwendung mit EventManager."""
  123. app = MagicMock()
  124. app.events = MagicMock()
  125. app.events.register_object = MagicMock()
  126. app.events.unregister_object = MagicMock()
  127. return app
  128. @pytest.fixture
  129. def plugins_dir(self, temp_dir):
  130. """Erstellt ein Plugin-Verzeichnis."""
  131. plugins = temp_dir / "plugins"
  132. plugins.mkdir()
  133. return plugins
  134. @pytest.fixture
  135. def manager(self, mock_application, plugins_dir):
  136. """Erstellt einen PluginManager."""
  137. return PluginManager(mock_application, directory=plugins_dir)
  138. @pytest.fixture
  139. def test_plugin_dir(self, plugins_dir):
  140. """Erstellt ein Test-Plugin-Verzeichnis."""
  141. plugin_dir = plugins_dir / "test_plugin"
  142. plugin_dir.mkdir()
  143. # main.py erstellen
  144. main_py = plugin_dir / "main.py"
  145. main_py.write_text('''
  146. from trixy_core.plugins.trixy_plugin import TrixyPlugin
  147. class TestPlugin(TrixyPlugin):
  148. NAME = "TestPlugin"
  149. VERSION = "1.0.0"
  150. async def on_load(self):
  151. pass
  152. async def on_unload(self):
  153. pass
  154. ''')
  155. # config.json erstellen
  156. config_json = plugin_dir / "config.json"
  157. config_json.write_text('{"enabled": true}')
  158. return plugin_dir
  159. def test_manager_initialization(self, manager):
  160. """Testet die Manager-Initialisierung."""
  161. assert manager.count == 0
  162. assert manager.enabled_count == 0
  163. @pytest.mark.asyncio
  164. async def test_load_plugin(self, manager, test_plugin_dir):
  165. """Testet das Laden eines Plugins."""
  166. result = await manager.load("test_plugin")
  167. assert result is True
  168. assert manager.count == 1
  169. assert "TestPlugin" in manager
  170. @pytest.mark.asyncio
  171. async def test_load_nonexistent_plugin(self, manager):
  172. """Testet das Laden eines nicht existierenden Plugins."""
  173. result = await manager.load("nonexistent")
  174. assert result is False
  175. assert "nonexistent" in manager.get_failed()
  176. @pytest.mark.asyncio
  177. async def test_unload_plugin(self, manager, test_plugin_dir):
  178. """Testet das Entladen eines Plugins."""
  179. await manager.load("test_plugin")
  180. result = await manager.unload("TestPlugin")
  181. assert result is True
  182. assert manager.count == 0
  183. @pytest.mark.asyncio
  184. async def test_reload_plugin(self, manager, test_plugin_dir):
  185. """Testet das Neuladen eines Plugins."""
  186. await manager.load("test_plugin")
  187. result = await manager.reload("test_plugin")
  188. assert result is True
  189. assert manager.count == 1
  190. @pytest.mark.asyncio
  191. async def test_get_plugin(self, manager, test_plugin_dir):
  192. """Testet get."""
  193. await manager.load("test_plugin")
  194. plugin = manager.get("TestPlugin")
  195. assert plugin is not None
  196. assert plugin.name == "TestPlugin"
  197. @pytest.mark.asyncio
  198. async def test_get_all(self, manager, test_plugin_dir):
  199. """Testet get_all."""
  200. await manager.load("test_plugin")
  201. plugins = manager.get_all()
  202. assert len(plugins) == 1
  203. @pytest.mark.asyncio
  204. async def test_enable_plugin(self, manager, test_plugin_dir):
  205. """Testet enable."""
  206. await manager.load("test_plugin")
  207. plugin = manager.get("TestPlugin")
  208. plugin.enabled = False
  209. result = await manager.enable("TestPlugin")
  210. assert result is True
  211. assert plugin.enabled is True
  212. @pytest.mark.asyncio
  213. async def test_disable_plugin(self, manager, test_plugin_dir):
  214. """Testet disable."""
  215. await manager.load("test_plugin")
  216. result = await manager.disable("TestPlugin")
  217. plugin = manager.get("TestPlugin")
  218. assert result is True
  219. assert plugin.enabled is False
  220. @pytest.mark.asyncio
  221. async def test_load_all(self, manager, test_plugin_dir):
  222. """Testet load_all."""
  223. count = await manager.load_all()
  224. assert count == 1
  225. assert manager.count == 1
  226. @pytest.mark.asyncio
  227. async def test_unload_all(self, manager, test_plugin_dir):
  228. """Testet unload_all."""
  229. await manager.load("test_plugin")
  230. count = await manager.unload_all()
  231. assert count == 1
  232. assert manager.count == 0
  233. @pytest.mark.asyncio
  234. async def test_contains(self, manager, test_plugin_dir):
  235. """Testet __contains__."""
  236. await manager.load("test_plugin")
  237. assert "TestPlugin" in manager
  238. assert "NonExistent" not in manager
  239. @pytest.mark.asyncio
  240. async def test_getitem(self, manager, test_plugin_dir):
  241. """Testet __getitem__."""
  242. await manager.load("test_plugin")
  243. plugin = manager["TestPlugin"]
  244. assert plugin.name == "TestPlugin"
  245. @pytest.mark.asyncio
  246. async def test_getitem_raises_keyerror(self, manager):
  247. """Testet, dass __getitem__ KeyError wirft."""
  248. with pytest.raises(KeyError):
  249. _ = manager["NonExistent"]
  250. @pytest.mark.asyncio
  251. async def test_iter(self, manager, test_plugin_dir):
  252. """Testet __iter__."""
  253. await manager.load("test_plugin")
  254. plugins = list(manager)
  255. assert len(plugins) == 1
  256. @pytest.mark.asyncio
  257. async def test_len(self, manager, test_plugin_dir):
  258. """Testet __len__."""
  259. assert len(manager) == 0
  260. await manager.load("test_plugin")
  261. assert len(manager) == 1
  262. @pytest.mark.asyncio
  263. async def test_get_enabled(self, manager, test_plugin_dir):
  264. """Testet get_enabled."""
  265. await manager.load("test_plugin")
  266. enabled = manager.get_enabled()
  267. assert len(enabled) == 1
  268. assert manager.enabled_count == 1
  269. def test_skip_hidden_directories(self, manager, plugins_dir):
  270. """Testet, dass versteckte Verzeichnisse übersprungen werden."""
  271. # Erstelle verstecktes Verzeichnis
  272. hidden = plugins_dir / "_hidden"
  273. hidden.mkdir()
  274. # Sollte nicht versuchen zu laden
  275. # (load_all würde es überspringen)
  276. @pytest.mark.asyncio
  277. async def test_plugin_without_main_py(self, manager, plugins_dir):
  278. """Testet Plugin ohne main.py."""
  279. # Erstelle Verzeichnis ohne main.py
  280. empty = plugins_dir / "empty_plugin"
  281. empty.mkdir()
  282. result = await manager.load("empty_plugin")
  283. assert result is False
  284. assert "empty_plugin" in manager.get_failed()