"""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()