import asyncio import json from types import SimpleNamespace import pytest from src import addons @pytest.fixture def addon_store(tmp_path, monkeypatch): monkeypatch.setattr(addons, "ADDONS_DIR", str(tmp_path / "addons")) monkeypatch.setattr(addons, "ADDONS_FILE", str(tmp_path / "addons.json")) return tmp_path def test_addon_template_is_valid(addon_store): manifest = addons.addon_template("demo-addon") saved = addons.save_addon_manifest(manifest) assert saved["id"] == "demo-addon" assert saved["enabled"] is False assert saved["contributions"][0]["type"] == "agent_prompt" assert addons.get_addon("demo-addon")["name"] == "Demo Addon" def test_enable_addon_exposes_prompt_contribution(addon_store): addons.save_addon_manifest({ "id": "prompt-addon", "name": "Prompt Addon", "contributions": [ {"type": "agent_prompt", "text": "Prefer compact summaries."}, {"type": "unknown", "text": "ignored"}, ], }) assert addons.get_addons_prompt() == "" addons.set_addon_enabled("prompt-addon", True) prompt = addons.get_addons_prompt() assert "Prompt Addon" in prompt assert "Prefer compact summaries." in prompt assert "unknown" not in prompt def test_addon_id_rejects_path_traversal(addon_store): with pytest.raises(ValueError): addons.save_addon_manifest({"id": "../bad", "name": "Bad"}) @pytest.fixture def addon_routes(addon_store): fastapi = pytest.importorskip("fastapi") from routes.addon_routes import setup_addon_routes class _AuthManager: def get_username_for_token(self, token): return "admin" if token == "session-token" else None def is_admin(self, user): return user == "admin" router = setup_addon_routes(_AuthManager()) def endpoint(path, method): for route in router.routes: if getattr(route, "path", "") == path and method in getattr(route, "methods", set()): return route.endpoint raise AssertionError(f"{method} {path} route not registered") return endpoint, fastapi.HTTPException class _Request(SimpleNamespace): def __init__(self, token="session-token"): super().__init__(cookies={"odysseus_session": token}) def test_addon_routes_require_admin(addon_routes): endpoint, http_exception = addon_routes list_all = endpoint("/api/addons", "GET") with pytest.raises(http_exception) as exc: asyncio.run(list_all(_Request(token="bad"))) assert exc.value.status_code == 401 def test_addon_routes_create_and_enable(addon_routes): endpoint, _ = addon_routes create = endpoint("/api/addons", "POST") set_enabled = endpoint("/api/addons/{addon_id}/enabled", "POST") list_all = endpoint("/api/addons", "GET") body = SimpleNamespace(manifest={"id": "route-addon", "name": "Route Addon"}) result = asyncio.run(create(body, _Request())) assert result["addon"]["id"] == "route-addon" enabled_body = SimpleNamespace(enabled=True) asyncio.run(set_enabled("route-addon", enabled_body, _Request())) listed = asyncio.run(list_all(_Request())) assert listed["addons"][0]["enabled"] is True assert json.loads((addons._addon_dir("route-addon") / "addon.json").read_text())["enabled"] is True