104 lines
3.3 KiB
Python
104 lines
3.3 KiB
Python
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
|