Files
AegisGitea-MCP/tests/test_catalog_generator.py
Latte 041b3ea7ff feat: generate the tool catalog from the pinned swagger
Adds scripts/generate_catalog.py (with --check), the naming rules, schema conversion, the committed tools.json for all 537 operations and the load_catalog() loader.
2026-10-14 15:51:01 +02:00

152 lines
5.1 KiB
Python

"""Tests for the generator script (--check drift detection) and the loader."""
from __future__ import annotations
import importlib.util
import json
import shutil
from pathlib import Path
from types import ModuleType
import pytest
from aegis_gitea_mcp.catalog import DOMAINS, Catalog, Operation, load_catalog
from aegis_gitea_mcp.catalog.build import (
diff_summary,
generate,
render_catalog,
spec_sha256,
)
REPO_ROOT = Path(__file__).resolve().parents[1]
SPEC_PATH = REPO_ROOT / "spec" / "gitea-swagger.v1.json"
CATALOG_PATH = REPO_ROOT / "src" / "aegis_gitea_mcp" / "catalog" / "tools.json"
SCRIPT_PATH = REPO_ROOT / "scripts" / "generate_catalog.py"
@pytest.fixture(scope="module")
def script() -> ModuleType:
spec = importlib.util.spec_from_file_location("generate_catalog_script", SCRIPT_PATH)
assert spec is not None
assert spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
@pytest.fixture(scope="module")
def generated() -> str:
text, _ = generate(SPEC_PATH)
return text
def test_committed_catalog_is_up_to_date(generated: str) -> None:
assert CATALOG_PATH.read_text("utf-8") == generated
def test_catalog_header_matches_pinned_spec() -> None:
document = json.loads(CATALOG_PATH.read_text("utf-8"))
assert document["spec"] == {
"title": "Gitea API",
"version": "28.1.0",
"sha256": spec_sha256(SPEC_PATH.read_bytes()),
}
assert len(document["operations"]) == 537
def test_check_passes_on_fresh_file(
script: ModuleType, generated: str, tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
target = tmp_path / "tools.json"
target.write_text(generated, "utf-8")
assert script.main(["--check"], SPEC_PATH, target) == 0
assert "up to date" in capsys.readouterr().out
def test_check_detects_drift(
script: ModuleType, generated: str, tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
target = tmp_path / "tools.json"
target.write_text(
generated.replace("gitea_repo_list_branches", "gitea_repo_list_branch"), "utf-8"
)
assert script.main(["--check"], SPEC_PATH, target) == 1
err = capsys.readouterr().err
assert "out of date" in err
assert "changed lines" in err
def test_check_fails_when_file_is_missing(script: ModuleType, tmp_path: Path) -> None:
assert script.main(["--check"], SPEC_PATH, tmp_path / "missing.json") == 1
def test_check_detects_spec_change(script: ModuleType, generated: str, tmp_path: Path) -> None:
target = tmp_path / "tools.json"
target.write_text(generated, "utf-8")
changed_spec = tmp_path / "spec.json"
spec = json.loads(SPEC_PATH.read_text("utf-8"))
spec["info"]["version"] = "99.0.0"
changed_spec.write_text(json.dumps(spec), "utf-8")
assert script.main(["--check"], changed_spec, target) == 1
def test_write_mode_creates_identical_file(
script: ModuleType, generated: str, tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
target = tmp_path / "out" / "tools.json"
target.parent.mkdir()
assert script.main([], SPEC_PATH, target) == 0
assert target.read_text("utf-8") == generated
out = capsys.readouterr().out
assert "operations per domain" in out
assert "total 537" in out
assert "override:" in out
shutil.rmtree(target.parent)
def test_diff_summary_truncates() -> None:
old = "\n".join(f"line {i}" for i in range(100))
new = "\n".join(f"changed {i}" for i in range(100))
summary = diff_summary(old, new, limit=5)
assert summary.splitlines()[0] == "200 changed lines"
assert "more changed lines" in summary
def test_loader_round_trip() -> None:
catalog = load_catalog()
assert isinstance(catalog, Catalog)
assert catalog.spec_version == "28.1.0"
assert len(catalog.operations) == 537
document = json.loads(CATALOG_PATH.read_text("utf-8"))
assert [operation.to_dict() for operation in catalog.operations] == document["operations"]
assert render_catalog(document) == CATALOG_PATH.read_text("utf-8")
for operation in catalog.operations:
assert Operation.from_dict(operation.to_dict()) == operation
def test_loader_is_cached_and_indexed() -> None:
assert load_catalog() is load_catalog()
catalog = load_catalog()
operation = catalog.get("gitea_repo_list_branches")
assert operation is not None
assert operation.operation_id == "repoListBranches"
assert operation.path == "/repos/{owner}/{repo}/branches"
assert operation.method == "GET"
assert catalog.get("gitea_nope_nothing") is None
names = [item.tool_name for item in catalog.operations]
assert names == sorted(names)
def test_loader_domains() -> None:
assert load_catalog().domains() == sorted(DOMAINS, key=DOMAINS.index)
assert set(load_catalog().domains()) == set(DOMAINS)
empty = Catalog(spec_version="0", operations=())
assert empty.domains() == []
assert empty.get("gitea_repo_get") is None
def test_tools_json_ships_in_the_package() -> None:
from importlib import resources
assert resources.files("aegis_gitea_mcp.catalog").joinpath("tools.json").is_file()