Files
headquarter/apps/api/tests/tools/test_registry.py
T
Fusion f33a563003 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
2026-05-14 09:15:22 +02:00

160 lines
4.8 KiB
Python

"""Tests for the in-memory tool manifest registry."""
from __future__ import annotations
from pathlib import Path
import pytest
from pydantic import ValidationError
from app.tools.models import PortConfig, ToolManifest, TraefikConfig
from app.tools.registry import ToolRegistry
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def registry() -> ToolRegistry:
return ToolRegistry()
@pytest.fixture
def sample_manifest() -> ToolManifest:
return ToolManifest(
id="test-tool",
name="Test Tool",
image="test:latest",
ports=[PortConfig(container_port=8080, primary=True)],
traefik=TraefikConfig(enabled=False),
)
# ---------------------------------------------------------------------------
# Register / get round-trip
# ---------------------------------------------------------------------------
def test_register_and_get(registry: ToolRegistry, sample_manifest: ToolManifest) -> None:
registry.register(sample_manifest)
retrieved = registry.get("test-tool")
assert retrieved is not None
assert retrieved.id == "test-tool"
def test_get_missing_returns_none(registry: ToolRegistry) -> None:
assert registry.get("missing") is None
# ---------------------------------------------------------------------------
# List
# ---------------------------------------------------------------------------
def test_list_returns_all(registry: ToolRegistry) -> None:
m1 = ToolManifest(
id="tool-a",
name="Tool A",
image="a:latest",
ports=[PortConfig(container_port=8080, primary=True)],
traefik=TraefikConfig(enabled=False),
)
m2 = ToolManifest(
id="tool-b",
name="Tool B",
image="b:latest",
ports=[PortConfig(container_port=3000, primary=True)],
traefik=TraefikConfig(enabled=False),
)
registry.register(m1)
registry.register(m2)
assert len(registry.list()) == 2
ids = {m.id for m in registry.list()}
assert ids == {"tool-a", "tool-b"}
# ---------------------------------------------------------------------------
# Overwrite behavior
# ---------------------------------------------------------------------------
def test_register_overwrites_existing(
registry: ToolRegistry,
sample_manifest: ToolManifest,
) -> None:
registry.register(sample_manifest)
updated = ToolManifest(
id="test-tool",
name="Updated Tool",
image="updated:latest",
ports=[PortConfig(container_port=8080, primary=True)],
traefik=TraefikConfig(enabled=False),
)
registry.register(updated)
retrieved = registry.get("test-tool")
assert retrieved is not None
assert retrieved.name == "Updated Tool"
# ---------------------------------------------------------------------------
# Remove
# ---------------------------------------------------------------------------
def test_remove_returns_manifest(registry: ToolRegistry, sample_manifest: ToolManifest) -> None:
registry.register(sample_manifest)
removed = registry.remove("test-tool")
assert removed is not None
assert removed.id == "test-tool"
assert registry.get("test-tool") is None
def test_remove_missing_returns_none(registry: ToolRegistry) -> None:
assert registry.remove("missing") is None
# ---------------------------------------------------------------------------
# Load file
# ---------------------------------------------------------------------------
def test_load_valid_yaml_file(registry: ToolRegistry, tmp_path: Path) -> None:
yaml_path = tmp_path / "my-tool.yml"
yaml_path.write_text(
"""
id: my-tool
name: My Tool
image: my-tool:latest
ports:
- container_port: 8080
primary: true
traefik:
enabled: false
""",
encoding="utf-8",
)
manifest = registry.load_file(yaml_path)
assert manifest.id == "my-tool"
assert manifest.name == "My Tool"
assert manifest.ports[0].container_port == 8080
def test_load_invalid_yaml_raises(registry: ToolRegistry, tmp_path: Path) -> None:
yaml_path = tmp_path / "bad-tool.yml"
yaml_path.write_text(
"""
id: BAD ID
name: Bad Tool
image: bad:latest
""",
encoding="utf-8",
)
with pytest.raises(ValidationError):
registry.load_file(yaml_path)
# ---------------------------------------------------------------------------
# Load builtin manifests
# ---------------------------------------------------------------------------
def test_load_builtin_manifests(registry: ToolRegistry) -> None:
registry.load_builtin_manifests()
# Built-in manifests from Step 4 may not exist yet in isolation,
# but the method should not raise regardless of directory contents.
assert isinstance(registry.list(), list)