| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- # -*- coding: utf-8 -*-
- """
- Test-Fixtures für Plugin-Tests.
- Hilfsfunktionen zum Erstellen von Test-Umgebungen.
- """
- from __future__ import annotations
- import json
- import shutil
- import tempfile
- from dataclasses import dataclass, field
- from pathlib import Path
- from typing import Any
- def create_test_plugin_dir(
- name: str,
- base_dir: Path | None = None,
- with_manifest: bool = True,
- with_main: bool = True,
- manifest_data: dict[str, Any] | None = None,
- main_content: str | None = None,
- ) -> Path:
- """
- Erstellt ein Test-Plugin-Verzeichnis.
- Args:
- name: Plugin-Name
- base_dir: Basis-Verzeichnis (Standard: temp)
- with_manifest: config.json erstellen
- with_main: main.py erstellen
- manifest_data: Manifest-Daten
- main_content: Inhalt für main.py
- Returns:
- Pfad zum Plugin-Verzeichnis
- """
- if base_dir is None:
- base_dir = Path(tempfile.mkdtemp(prefix="trixy_plugin_test_"))
- plugin_dir = base_dir / name
- plugin_dir.mkdir(parents=True, exist_ok=True)
- if with_manifest:
- manifest = create_test_manifest(
- name=name,
- data=manifest_data,
- )
- manifest_path = plugin_dir / "config.json"
- with open(manifest_path, "w", encoding="utf-8") as f:
- json.dump(manifest, f, indent=2)
- if with_main:
- main_path = plugin_dir / "main.py"
- content = main_content or _generate_default_main(name)
- main_path.write_text(content, encoding="utf-8")
- # __init__.py erstellen
- init_path = plugin_dir / "__init__.py"
- init_path.write_text("# -*- coding: utf-8 -*-\n", encoding="utf-8")
- return plugin_dir
- def create_test_manifest(
- name: str,
- version: str = "1.0.0",
- description: str | None = None,
- author: str = "Test",
- data: dict[str, Any] | None = None,
- ) -> dict[str, Any]:
- """
- Erstellt ein Test-Manifest.
- Args:
- name: Plugin-Name
- version: Plugin-Version
- description: Beschreibung
- author: Autor
- data: Zusätzliche Daten
- Returns:
- Manifest-Dictionary
- """
- manifest = {
- "name": name,
- "version": version,
- "description": description or f"Test-Plugin {name}",
- "author": author,
- "enabled": True,
- "dependencies": {},
- "capabilities": [],
- "config": {},
- }
- if data:
- manifest.update(data)
- return manifest
- def cleanup_test_dir(path: Path) -> bool:
- """
- Löscht ein Test-Verzeichnis.
- Args:
- path: Zu löschendes Verzeichnis
- Returns:
- True bei Erfolg
- """
- try:
- if path.exists():
- shutil.rmtree(path)
- return True
- except Exception:
- return False
- def _generate_default_main(name: str) -> str:
- """Generiert eine Standard-main.py für Tests."""
- class_name = "".join(word.capitalize() for word in name.split("_"))
- return f'''# -*- coding: utf-8 -*-
- """
- Test-Plugin: {name}
- """
- from trixy_core.plugins.base import TrixyPlugin
- class {class_name}Plugin(TrixyPlugin):
- """Test-Plugin-Implementierung."""
- NAME = "{name}"
- VERSION = "1.0.0"
- DESCRIPTION = "Test-Plugin für {name}"
- AUTHOR = "Test"
- async def on_start(self) -> None:
- """Startet das Plugin."""
- pass
- async def on_stop(self) -> None:
- """Stoppt das Plugin."""
- pass
- # Plugin-Klasse für den Loader
- Plugin = {class_name}Plugin
- '''
- @dataclass
- class PluginTestFixture:
- """
- Fixture für Plugin-Tests.
- Verwaltet temporäre Test-Verzeichnisse und bietet
- Hilfsmethoden für Plugin-Tests.
- Example:
- # Als Context-Manager verwenden
- with PluginTestFixture() as fixture:
- plugin_dir = fixture.create_plugin("my_plugin")
- # Tests durchführen...
- # Aufräumung erfolgt automatisch
- # Oder manuell
- fixture = PluginTestFixture()
- fixture.setup()
- try:
- plugin_dir = fixture.create_plugin("my_plugin")
- # Tests...
- finally:
- fixture.teardown()
- """
- base_dir: Path | None = None
- plugins: dict[str, Path] = field(default_factory=dict)
- _temp_dir: Path | None = field(default=None, repr=False)
- _cleanup: bool = field(default=True, repr=False)
- def __enter__(self) -> "PluginTestFixture":
- """Context-Manager-Eintritt."""
- self.setup()
- return self
- def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
- """Context-Manager-Austritt."""
- self.teardown()
- def setup(self) -> None:
- """Initialisiert die Fixture."""
- if self.base_dir is None:
- self._temp_dir = Path(tempfile.mkdtemp(prefix="trixy_test_fixture_"))
- self.base_dir = self._temp_dir
- else:
- self.base_dir.mkdir(parents=True, exist_ok=True)
- def teardown(self) -> None:
- """Räumt die Fixture auf."""
- if self._cleanup and self._temp_dir and self._temp_dir.exists():
- cleanup_test_dir(self._temp_dir)
- self.plugins.clear()
- def create_plugin(
- self,
- name: str,
- manifest_data: dict[str, Any] | None = None,
- main_content: str | None = None,
- with_manifest: bool = True,
- with_main: bool = True,
- ) -> Path:
- """
- Erstellt ein Test-Plugin.
- Args:
- name: Plugin-Name
- manifest_data: Optionale Manifest-Daten
- main_content: Optionaler main.py-Inhalt
- with_manifest: config.json erstellen
- with_main: main.py erstellen
- Returns:
- Pfad zum Plugin-Verzeichnis
- """
- if self.base_dir is None:
- self.setup()
- plugin_dir = create_test_plugin_dir(
- name=name,
- base_dir=self.base_dir,
- with_manifest=with_manifest,
- with_main=with_main,
- manifest_data=manifest_data,
- main_content=main_content,
- )
- self.plugins[name] = plugin_dir
- return plugin_dir
- def get_plugin_path(self, name: str) -> Path | None:
- """Gibt den Pfad eines Plugins zurück."""
- return self.plugins.get(name)
- def plugin_exists(self, name: str) -> bool:
- """Prüft, ob ein Plugin existiert."""
- path = self.plugins.get(name)
- return path is not None and path.exists()
- def add_file(
- self,
- plugin_name: str,
- filename: str,
- content: str,
- ) -> Path | None:
- """
- Fügt eine Datei zu einem Plugin hinzu.
- Args:
- plugin_name: Plugin-Name
- filename: Dateiname
- content: Dateiinhalt
- Returns:
- Pfad zur Datei oder None
- """
- plugin_dir = self.plugins.get(plugin_name)
- if plugin_dir is None:
- return None
- file_path = plugin_dir / filename
- file_path.parent.mkdir(parents=True, exist_ok=True)
- file_path.write_text(content, encoding="utf-8")
- return file_path
- def add_json_file(
- self,
- plugin_name: str,
- filename: str,
- data: dict[str, Any],
- ) -> Path | None:
- """
- Fügt eine JSON-Datei zu einem Plugin hinzu.
- Args:
- plugin_name: Plugin-Name
- filename: Dateiname
- data: JSON-Daten
- Returns:
- Pfad zur Datei oder None
- """
- content = json.dumps(data, indent=2, ensure_ascii=False)
- return self.add_file(plugin_name, filename, content)
- def update_manifest(
- self,
- plugin_name: str,
- data: dict[str, Any],
- ) -> bool:
- """
- Aktualisiert das Manifest eines Plugins.
- Args:
- plugin_name: Plugin-Name
- data: Zu aktualisierende Daten
- Returns:
- True bei Erfolg
- """
- plugin_dir = self.plugins.get(plugin_name)
- if plugin_dir is None:
- return False
- manifest_path = plugin_dir / "config.json"
- if not manifest_path.exists():
- return False
- try:
- with open(manifest_path, "r", encoding="utf-8") as f:
- manifest = json.load(f)
- manifest.update(data)
- with open(manifest_path, "w", encoding="utf-8") as f:
- json.dump(manifest, f, indent=2)
- return True
- except Exception:
- return False
- def read_manifest(self, plugin_name: str) -> dict[str, Any] | None:
- """
- Liest das Manifest eines Plugins.
- Args:
- plugin_name: Plugin-Name
- Returns:
- Manifest-Daten oder None
- """
- plugin_dir = self.plugins.get(plugin_name)
- if plugin_dir is None:
- return None
- manifest_path = plugin_dir / "config.json"
- if not manifest_path.exists():
- return None
- try:
- with open(manifest_path, "r", encoding="utf-8") as f:
- return json.load(f)
- except Exception:
- return None
- def remove_plugin(self, name: str) -> bool:
- """
- Entfernt ein Plugin.
- Args:
- name: Plugin-Name
- Returns:
- True bei Erfolg
- """
- plugin_dir = self.plugins.pop(name, None)
- if plugin_dir:
- return cleanup_test_dir(plugin_dir)
- return False
- def clear(self) -> None:
- """Entfernt alle Plugins."""
- for name in list(self.plugins.keys()):
- self.remove_plugin(name)
- def create_mock_plugin_class(
- name: str,
- version: str = "1.0.0",
- handlers: dict[str, Any] | None = None,
- ) -> type:
- """
- Erstellt eine Mock-Plugin-Klasse dynamisch.
- Args:
- name: Plugin-Name
- version: Plugin-Version
- handlers: Event-Handler als Dict
- Returns:
- Die Plugin-Klasse
- """
- from trixy_core.plugins.testing.mocks import MockPlugin
- class_name = "".join(word.capitalize() for word in name.split("_")) + "Plugin"
- attrs = {
- "NAME": name,
- "VERSION": version,
- "DESCRIPTION": f"Dynamisch erstelltes Mock-Plugin: {name}",
- }
- if handlers:
- for event_name, handler in handlers.items():
- method_name = f"on_{event_name.replace('.', '_')}"
- attrs[method_name] = handler
- return type(class_name, (MockPlugin,), attrs)
- def create_plugin_with_dependencies(
- fixture: PluginTestFixture,
- name: str,
- dependencies: dict[str, str],
- **kwargs: Any,
- ) -> Path:
- """
- Erstellt ein Plugin mit Abhängigkeiten.
- Args:
- fixture: Test-Fixture
- name: Plugin-Name
- dependencies: Dict von Abhängigkeits-Name zu Version-Range
- **kwargs: Weitere Argumente für create_plugin
- Returns:
- Pfad zum Plugin-Verzeichnis
- """
- manifest_data = kwargs.pop("manifest_data", {})
- manifest_data["dependencies"] = dependencies
- return fixture.create_plugin(
- name=name,
- manifest_data=manifest_data,
- **kwargs,
- )
- def create_plugin_chain(
- fixture: PluginTestFixture,
- names: list[str],
- ) -> list[Path]:
- """
- Erstellt eine Kette von abhängigen Plugins.
- Jedes Plugin hängt vom vorherigen ab.
- Args:
- fixture: Test-Fixture
- names: Liste von Plugin-Namen
- Returns:
- Liste der Plugin-Pfade
- """
- paths = []
- for i, name in enumerate(names):
- dependencies = {}
- if i > 0:
- dependencies[names[i - 1]] = "^1.0.0"
- path = create_plugin_with_dependencies(
- fixture=fixture,
- name=name,
- dependencies=dependencies,
- )
- paths.append(path)
- return paths
|