mirror of
https://github.com/MervinPraison/PraisonAIUI.git
synced 2026-11-10 17:29:50 -10:00
* feat: standardize bootstrap entrypoint to 'aiui run app.py' across examples Standardizes platform bootstrap patterns across examples per issue #71: - Update examples README table to use 'aiui run app.py' consistently - Update docstrings in examples 10, 12, 19-21 to show 'aiui run' instead of 'python app.py' - Remove fragmented bootstrap approaches (uvicorn.run, asyncio patterns) - Enforce single bootstrap SSOT as designed in CLI Co-authored-by: Mervin Praison <[email protected]> * feat: consolidate bootstrap entrypoints - auto-detect gateway apps (fixes #71) - Add bootstrap detection in CLI run command - Auto-detect gateway pattern (async main + AIUIGateway imports) - Auto-switch to praisonai backend for gateway apps - Delegate to user-defined main() when present - Remove sys.path hacks from example 14 - Update examples README to show unified aiui run entrypoint - Add BOOTSTRAP.md documentation for migration patterns Co-authored-by: Mervin Praison <[email protected]> * fix: resolve critical bootstrap entrypoint issues - Fix AST parsing bug in gateway auto-detection (cli.py:1419) - Revert background bots (email-bot, agentmail-bot) to python app.py - Revert custom UI apps (agent-dashboard) to python app.py - Update README.md to reflect correct run commands - Fix import formatting and remove unused imports These changes ensure that: 1. Gateway auto-detection works correctly by properly checking ast.alias.name 2. Background bots execute their __main__ blocks and don't start unnecessary web servers 3. Custom UI apps register their custom routes in __main__ blocks 4. Documentation matches actual working run commands 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Mervin Praison <[email protected]> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Mervin Praison <[email protected]> Co-authored-by: Mervin Praison <[email protected]> Co-authored-by: Cursor <[email protected]>
159 lines
4.4 KiB
Python
159 lines
4.4 KiB
Python
"""Tests for plugin system."""
|
|
|
|
from typing import Any, List
|
|
|
|
import pytest
|
|
|
|
from praisonaiui.plugins import (
|
|
BasePlugin,
|
|
PluginManager,
|
|
get_plugin_manager,
|
|
register_plugin,
|
|
unregister_plugin,
|
|
)
|
|
|
|
|
|
class TestPluginManager:
|
|
"""Tests for PluginManager."""
|
|
|
|
def test_register_plugin(self):
|
|
"""Test registering a plugin."""
|
|
manager = PluginManager()
|
|
|
|
class TestPlugin(BasePlugin):
|
|
@property
|
|
def name(self) -> str:
|
|
return "test-plugin"
|
|
|
|
plugin = TestPlugin()
|
|
manager.register(plugin)
|
|
|
|
assert "test-plugin" in manager.list_plugins()
|
|
assert manager.get_plugin("test-plugin") is plugin
|
|
|
|
def test_register_duplicate_raises(self):
|
|
"""Test registering duplicate plugin raises error."""
|
|
manager = PluginManager()
|
|
|
|
class TestPlugin(BasePlugin):
|
|
@property
|
|
def name(self) -> str:
|
|
return "duplicate"
|
|
|
|
manager.register(TestPlugin())
|
|
|
|
with pytest.raises(ValueError, match="already registered"):
|
|
manager.register(TestPlugin())
|
|
|
|
def test_unregister_plugin(self):
|
|
"""Test unregistering a plugin."""
|
|
manager = PluginManager()
|
|
|
|
class TestPlugin(BasePlugin):
|
|
@property
|
|
def name(self) -> str:
|
|
return "to-remove"
|
|
|
|
manager.register(TestPlugin())
|
|
assert "to-remove" in manager.list_plugins()
|
|
|
|
manager.unregister("to-remove")
|
|
assert "to-remove" not in manager.list_plugins()
|
|
|
|
def test_unregister_unknown_raises(self):
|
|
"""Test unregistering unknown plugin raises error."""
|
|
manager = PluginManager()
|
|
|
|
with pytest.raises(ValueError, match="not found"):
|
|
manager.unregister("nonexistent")
|
|
|
|
def test_run_hook_chain(self):
|
|
"""Test hooks are called in chain, modifying data."""
|
|
manager = PluginManager()
|
|
|
|
class DoublePlugin(BasePlugin):
|
|
@property
|
|
def name(self) -> str:
|
|
return "double"
|
|
|
|
def on_scan_complete(self, pages: List[Any]) -> List[Any]:
|
|
return pages + pages # Double the list
|
|
|
|
class FilterPlugin(BasePlugin):
|
|
@property
|
|
def name(self) -> str:
|
|
return "filter"
|
|
|
|
def on_scan_complete(self, pages: List[Any]) -> List[Any]:
|
|
return [p for p in pages if p != "remove"]
|
|
|
|
manager.register(DoublePlugin())
|
|
manager.register(FilterPlugin())
|
|
|
|
result = manager.run_hook("scan_complete", ["a", "remove", "b"])
|
|
# Both plugins run, filter then double (or vice versa depending on order)
|
|
assert "remove" not in result
|
|
|
|
def test_run_hook_unknown_raises(self):
|
|
"""Test running unknown hook raises error."""
|
|
manager = PluginManager()
|
|
|
|
with pytest.raises(ValueError, match="Unknown hook"):
|
|
manager.run_hook("nonexistent", {})
|
|
|
|
|
|
class TestBasePlugin:
|
|
"""Tests for BasePlugin."""
|
|
|
|
def test_default_hooks_pass_through(self):
|
|
"""Test default hook implementations pass data through."""
|
|
|
|
class MinimalPlugin(BasePlugin):
|
|
@property
|
|
def name(self) -> str:
|
|
return "minimal"
|
|
|
|
plugin = MinimalPlugin()
|
|
|
|
# All defaults should return input unchanged
|
|
pages = [1, 2, 3]
|
|
assert plugin.on_scan_complete(pages) is pages
|
|
|
|
nav = {"items": []}
|
|
assert plugin.on_nav_built(nav) is nav
|
|
|
|
result = {"data": "test"}
|
|
assert plugin.on_compile_complete(result) is result
|
|
|
|
|
|
class TestGlobalManager:
|
|
"""Tests for global plugin manager functions."""
|
|
|
|
def test_get_plugin_manager_singleton(self):
|
|
"""Test global manager is singleton."""
|
|
# Reset global
|
|
import praisonaiui.plugins as pm
|
|
|
|
pm._plugin_manager = None
|
|
|
|
m1 = get_plugin_manager()
|
|
m2 = get_plugin_manager()
|
|
assert m1 is m2
|
|
|
|
def test_register_unregister_global(self):
|
|
"""Test global register/unregister functions."""
|
|
import praisonaiui.plugins as pm
|
|
|
|
pm._plugin_manager = None # Reset
|
|
|
|
class GlobalTestPlugin(BasePlugin):
|
|
@property
|
|
def name(self) -> str:
|
|
return "global-test"
|
|
|
|
register_plugin(GlobalTestPlugin())
|
|
assert "global-test" in get_plugin_manager().list_plugins()
|
|
|
|
unregister_plugin("global-test")
|
|
assert "global-test" not in get_plugin_manager().list_plugins()
|