feat(FN-003): add tool manifest registry with FastAPI CRUD and built-in manifests
- Add ToolManifest Pydantic models with validators for ports, mounts, health checks, and traefik config - Implement in-memory ToolRegistry with YAML loading and built-in manifest scanning - Add FastAPI CRUD routes for listing, retrieving, and creating tool manifests - Include built-in manifests for runfusion and code-server - Harden web Dockerfile with unprivileged nginx and port 8080 - Add tool manifest specification documentation and architecture updates Fusion-Task-Id: FN-003
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"""Tests for the FastAPI tool manifest router."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import app
|
||||
from app.tools.registry import registry
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_registry() -> None:
|
||||
registry._manifests.clear()
|
||||
registry.load_builtin_manifests()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /api/v1/tools
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_list_tools_includes_builtins() -> None:
|
||||
response = client.get("/api/v1/tools")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
ids = {item["id"] for item in data}
|
||||
assert "runfusion" in ids
|
||||
assert "code-server" in ids
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /api/v1/tools/{tool_id}
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_get_tool_runfusion() -> None:
|
||||
response = client.get("/api/v1/tools/runfusion")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == "runfusion"
|
||||
assert data["name"] == "RunFusion"
|
||||
|
||||
|
||||
def test_get_tool_not_found() -> None:
|
||||
response = client.get("/api/v1/tools/nonexistent")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# POST /api/v1/tools
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_create_tool_success() -> None:
|
||||
payload = {
|
||||
"id": "new-tool",
|
||||
"name": "New Tool",
|
||||
"image": "new-tool:latest",
|
||||
"ports": [{"container_port": 3000, "primary": True}],
|
||||
"traefik": {"enabled": False},
|
||||
}
|
||||
response = client.post("/api/v1/tools", json=payload)
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["id"] == "new-tool"
|
||||
assert data["name"] == "New Tool"
|
||||
|
||||
|
||||
def test_create_tool_duplicate() -> None:
|
||||
payload = {
|
||||
"id": "runfusion",
|
||||
"name": "Duplicate",
|
||||
"image": "dup:latest",
|
||||
"ports": [{"container_port": 3000, "primary": True}],
|
||||
"traefik": {"enabled": False},
|
||||
}
|
||||
response = client.post("/api/v1/tools", json=payload)
|
||||
assert response.status_code == 409
|
||||
|
||||
|
||||
def test_create_tool_invalid_id() -> None:
|
||||
payload = {
|
||||
"id": "Bad ID",
|
||||
"name": "Bad Tool",
|
||||
"image": "bad:latest",
|
||||
"ports": [{"container_port": 3000, "primary": True}],
|
||||
"traefik": {"enabled": False},
|
||||
}
|
||||
response = client.post("/api/v1/tools", json=payload)
|
||||
assert response.status_code == 422
|
||||
Reference in New Issue
Block a user