Files
ITWorx_MCP_Hub-Public/tests/test_gitea_chatgpt_surface_contract.py

198 lines
6.6 KiB
Python

from __future__ import annotations
import fnmatch
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
# Explicit compatibility contract for the connected ITWorx Gitea Workspace app.
# New connector tools never become public through a wildcard; a surface change
# requires a reviewed catalog + contract update in the same change.
CHATGPT_GITEA_SURFACE = {
"gitea.branch.create",
"gitea.branch.delete",
"gitea.branch.list",
"gitea.capabilities.get",
"gitea.change.review",
"gitea.change.status",
"gitea.changeset.apply",
"gitea.changeset.preview",
"gitea.code.search",
"gitea.commit.get",
"gitea.commit.list",
"gitea.commitstatus.list",
"gitea.connection.check",
"gitea.file.create",
"gitea.file.delete",
"gitea.file.read",
"gitea.file.update",
"gitea.issue.comment.create",
"gitea.issue.create",
"gitea.issue.list",
"gitea.issue.search",
"gitea.issue.update",
"gitea.orchestration.plan",
"gitea.orchestration.start",
"gitea.portfolio.health",
"gitea.pullrequest.create",
"gitea.pullrequest.close",
"gitea.pullrequest.feedback",
"gitea.pullrequest.get",
"gitea.pullrequest.list",
"gitea.pullrequest.merge",
"gitea.pullrequest.sync",
"gitea.pullrequest.update",
"gitea.ref.compare",
"gitea.release.list",
"gitea.repository.context",
"gitea.repository.create",
"gitea.repository.get",
"gitea.repository.health",
"gitea.repository.list",
"gitea.tree.list",
"gitea.validation.failure.get",
"gitea.validation.get",
"gitea.validation.list",
"gitea.validation.run",
"gitea.workspace.run",
"gitea.workspace.status",
"search",
"fetch",
}
# Tools advertised by the connected app but missing from the old gitea-all
# virtual-server allowlist. They must already be authorized by existing packs.
RESTORED_GITEA_SURFACE = {
"gitea.capabilities.get",
"gitea.change.review",
"gitea.change.status",
"gitea.commit.get",
"gitea.commit.list",
"gitea.commitstatus.list",
"gitea.connection.check",
"gitea.orchestration.plan",
"gitea.orchestration.start",
"gitea.portfolio.health",
"gitea.pullrequest.feedback",
"gitea.ref.compare",
"gitea.release.list",
"gitea.repository.context",
"gitea.repository.create",
"gitea.repository.health",
"gitea.validation.list",
"gitea.workspace.run",
"gitea.workspace.status",
}
# These are the minimum primitives needed to finish long-running PR work without
# a human rebase/validation/merge bootstrap.
LONG_RUNNING_PR_WORKFLOW = {
"gitea.pullrequest.sync",
"gitea.ref.compare",
"gitea.change.review",
"gitea.change.status",
"gitea.commitstatus.list",
"gitea.validation.get",
"gitea.validation.list",
"gitea.validation.run",
"gitea.workspace.run",
}
ALIASES = {
"search": "gitea.research.search",
"fetch": "gitea.research.fetch",
}
def _load(path: Path) -> dict[str, object]:
return json.loads(path.read_text(encoding="utf-8"))
def _matches(name: str, patterns: list[str]) -> bool:
return any(fnmatch.fnmatchcase(name, pattern) for pattern in patterns)
def test_connected_chatgpt_surface_is_explicit_published_and_pre_authorized() -> None:
profile = _load(ROOT / "catalog/projects/gitea-all.yaml")
spec = profile["spec"]
assert isinstance(spec, dict)
virtual_servers = spec["virtualServers"]
assert isinstance(virtual_servers, list) and len(virtual_servers) == 1
server = virtual_servers[0]
assert isinstance(server, dict)
published = server["toolAllowPatterns"]
assert isinstance(published, list)
assert all(isinstance(name, str) for name in published)
published_names = list(published)
assert server.get("defaultDecision") == "deny"
assert len(published_names) == len(set(published_names)), "duplicate gitea-all tool publication"
assert not any(
any(token in name for token in ("*", "?", "["))
for name in published_names
if name.startswith("gitea.")
), "gitea-all must not use wildcard Gitea publication"
missing = CHATGPT_GITEA_SURFACE - set(published_names)
assert not missing, f"connected ChatGPT tools missing from gitea-all: {sorted(missing)}"
assert LONG_RUNNING_PR_WORKFLOW <= set(published_names)
assert RESTORED_GITEA_SURFACE <= CHATGPT_GITEA_SURFACE
manifests: dict[str, dict[str, object]] = {}
for path in (ROOT / "catalog/tools").glob("*.yaml"):
document = _load(path)
manifest_spec = document.get("spec")
if not isinstance(manifest_spec, dict) or manifest_spec.get("connectorType") != "gitea":
continue
canonical_name = manifest_spec.get("canonicalName")
assert isinstance(canonical_name, str)
manifests[canonical_name] = manifest_spec
canonical_surface = {ALIASES.get(name, name) for name in CHATGPT_GITEA_SURFACE}
missing_manifests = canonical_surface - manifests.keys()
assert not missing_manifests, (
f"connected ChatGPT tools missing canonical manifests: {sorted(missing_manifests)}"
)
capability_packs = spec["capabilityPacks"]
assert capability_packs == ["project-reader", "gitea-controlled-writer"]
packs: dict[str, dict[str, object]] = {}
for slug in capability_packs:
assert isinstance(slug, str)
document = _load(ROOT / f"catalog/capability-packs/{slug}.yaml")
pack_spec = document["spec"]
assert isinstance(pack_spec, dict)
packs[slug] = pack_spec
reader_patterns = packs["project-reader"]["toolAllowPatterns"]
writer_patterns = packs["gitea-controlled-writer"]["toolAllowPatterns"]
assert isinstance(reader_patterns, list) and all(
isinstance(pattern, str) for pattern in reader_patterns
)
assert isinstance(writer_patterns, list) and all(
isinstance(pattern, str) for pattern in writer_patterns
)
for name in sorted(RESTORED_GITEA_SURFACE):
manifest = manifests[name]
read_only = manifest.get("readOnly")
assert isinstance(read_only, bool), name
if read_only:
assert _matches(name, reader_patterns), f"repair would bypass project-reader: {name}"
else:
assert _matches(name, writer_patterns), (
f"repair would bypass gitea-controlled-writer: {name}"
)
assert _matches("gitea.pullrequest.sync", writer_patterns), (
"long-running PR recovery must remain writer-pack governed"
)
forbidden = {
"gitea.repository.delete",
"gitea.repository.admin",
"gitea.branch.forcepush",
}
assert forbidden.isdisjoint(published_names)