Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020
This commit is contained in:
commit
cb5be898ea
663 files changed
+82674
No files matched your search
@@ -0,0 +1,371 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import fnmatch
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from services.control_api.catalog.loader import CatalogLoader
|
||||
from services.control_api.catalog.reconciler import CatalogReconciler
|
||||
from services.control_api.catalog.router import build_catalog_router
|
||||
from services.control_api.catalog.store import MemoryReconcileRunStore
|
||||
from services.control_api.contextforge.client import ContextForgeClient
|
||||
from services.control_api.contextforge.fake import FakeContextForge
|
||||
from services.control_api.contextforge.models import ContextForgeSettings
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def adapter_for(fake: FakeContextForge) -> ContextForgeClient:
|
||||
return ContextForgeClient(
|
||||
ContextForgeSettings(base_url="https://gateway.invalid", auth_token_env="TOKEN"), # noqa: S106
|
||||
transport=fake.transport(),
|
||||
environ={"TOKEN": "fake-token"},
|
||||
)
|
||||
|
||||
|
||||
class CatalogLoaderTests(unittest.TestCase):
|
||||
def test_committed_catalog_validates_without_network_or_secret_values(self) -> None:
|
||||
report = CatalogLoader(ROOT / "catalog").load()
|
||||
self.assertTrue(report.valid, report.issues)
|
||||
self.assertEqual(len(report.documents), 277)
|
||||
self.assertEqual(len(report.issues), 0)
|
||||
self.assertIsNotNone(report.desired_hash)
|
||||
|
||||
def test_openchamber_catalog_stops_at_external_codex_review(self) -> None:
|
||||
project = json.loads(
|
||||
(ROOT / "catalog/projects/openchamber-agent.yaml").read_text(encoding="utf-8")
|
||||
)
|
||||
allowed = project["spec"]["virtualServers"][0]["toolAllowPatterns"]
|
||||
manifests = {
|
||||
json.loads(path.read_text(encoding="utf-8"))["spec"]["canonicalName"]
|
||||
for path in (ROOT / "catalog/tools").glob("*.yaml")
|
||||
}
|
||||
|
||||
self.assertNotIn("opencode.delivery.control.merge", allowed)
|
||||
self.assertNotIn("opencode.delivery.control.merge", manifests)
|
||||
self.assertIn("opencode.delivery.control.result", allowed)
|
||||
|
||||
def test_stitch_controlled_writes_are_exact_and_policy_bound(self) -> None:
|
||||
policy = json.loads(
|
||||
(ROOT / "catalog/policies/042-stitch-governed-design-writes.yaml").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
)
|
||||
match = policy["spec"]["match"]
|
||||
self.assertEqual(match["project"], "stitch-design")
|
||||
self.assertEqual(match["connectors"], ["stitch"])
|
||||
self.assertEqual(match["riskClasses"], ["R2"])
|
||||
self.assertEqual(
|
||||
set(match["toolPatterns"]),
|
||||
{
|
||||
"stitch.project.create",
|
||||
"stitch.screen.edit",
|
||||
"stitch.screen.generate",
|
||||
"stitch.screen.variants",
|
||||
},
|
||||
)
|
||||
self.assertTrue(policy["spec"]["conditions"]["requireEnabledClient"])
|
||||
self.assertTrue(policy["spec"]["conditions"]["requireBinding"])
|
||||
|
||||
def test_gitea_analysis_profile_stays_small_and_job_only(self) -> None:
|
||||
profile = json.loads(
|
||||
(ROOT / "catalog/projects/gitea-analysis.yaml").read_text(encoding="utf-8")
|
||||
)
|
||||
server = profile["spec"]["virtualServers"][0]
|
||||
|
||||
self.assertEqual(server["endpointAlias"], "/mcp/gitea-analysis/prod")
|
||||
self.assertEqual(
|
||||
server["toolAllowPatterns"],
|
||||
[
|
||||
"projectbrain.analysis.start",
|
||||
"projectbrain.analysis.list",
|
||||
"projectbrain.analysis.status",
|
||||
"projectbrain.analysis.results",
|
||||
"projectbrain.analysis.cancel",
|
||||
"projectbrain.analysis.render",
|
||||
],
|
||||
)
|
||||
|
||||
def test_gitea_loop_profile_is_focused_and_closed_loop_complete(self) -> None:
|
||||
profile = json.loads(
|
||||
(ROOT / "catalog/projects/gitea-loop.yaml").read_text(encoding="utf-8")
|
||||
)
|
||||
server = profile["spec"]["virtualServers"][0]
|
||||
|
||||
self.assertEqual(server["endpointAlias"], "/mcp/gitea-loop/prod")
|
||||
self.assertLessEqual(len(server["toolAllowPatterns"]), 16)
|
||||
self.assertTrue(
|
||||
{
|
||||
"gitea.repository.create",
|
||||
"gitea.change.review",
|
||||
"gitea.change.reconcile",
|
||||
"gitea.validation.failure.get",
|
||||
"gitea.pullrequest.sync",
|
||||
"gitea.pullrequest.merge",
|
||||
"gitea.pullrequest.validation.ensure",
|
||||
"gitea.workspace.status",
|
||||
"gitea.workspace.run",
|
||||
"projectbrain.*",
|
||||
}.issubset(server["toolAllowPatterns"])
|
||||
)
|
||||
self.assertEqual(len(server["toolAllowPatterns"]), 16)
|
||||
self.assertIn("gitea-controlled-writer", profile["spec"]["capabilityPacks"])
|
||||
self.assertEqual(
|
||||
{binding["ref"] for binding in profile["spec"]["connectorBindings"]},
|
||||
{"gitea-default", "projectbrain-default"},
|
||||
)
|
||||
|
||||
def test_gitea_loop_tools_have_explicit_browser_safe_manifests(self) -> None:
|
||||
profile = json.loads(
|
||||
(ROOT / "catalog/projects/gitea-loop.yaml").read_text(encoding="utf-8")
|
||||
)
|
||||
patterns = set(profile["spec"]["virtualServers"][0]["toolAllowPatterns"])
|
||||
manifests = {}
|
||||
for path in (ROOT / "catalog/tools").glob("*.yaml"):
|
||||
document = json.loads(path.read_text(encoding="utf-8"))
|
||||
manifests[document["spec"]["canonicalName"]] = document["spec"]
|
||||
|
||||
allowed = {
|
||||
name
|
||||
for name in manifests
|
||||
if any(fnmatch.fnmatchcase(name, pattern) for pattern in patterns)
|
||||
}
|
||||
explicit = {pattern for pattern in patterns if not any(char in pattern for char in "*?[")}
|
||||
self.assertEqual(explicit - manifests.keys(), set())
|
||||
self.assertTrue({name for name in manifests if name.startswith("projectbrain.")} <= allowed)
|
||||
for name in allowed:
|
||||
limits = manifests[name]["limits"]
|
||||
self.assertLessEqual(limits["maxOutputBytes"], 524_288, name)
|
||||
self.assertLessEqual(limits["maxItems"], 100, name)
|
||||
for name in ("gitea.workspace.status", "projectbrain.analysis.status"):
|
||||
self.assertIn("pollAfterMs", manifests[name]["description"])
|
||||
|
||||
def test_server_ops_profile_is_small_and_appops_only(self) -> None:
|
||||
profile = json.loads(
|
||||
(ROOT / "catalog/projects/server-ops.yaml").read_text(encoding="utf-8")
|
||||
)
|
||||
server = profile["spec"]["virtualServers"][0]
|
||||
|
||||
self.assertEqual(server["endpointAlias"], "/mcp/server-ops/prod")
|
||||
self.assertEqual(server["toolAllowPatterns"], ["server.*"])
|
||||
self.assertEqual(profile["spec"]["capabilityPacks"], ["appops-operator"])
|
||||
self.assertEqual(
|
||||
{binding["ref"] for binding in profile["spec"]["connectorBindings"]},
|
||||
{"appops-default"},
|
||||
)
|
||||
|
||||
def test_invalid_field_and_secret_report_file_and_pointer(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory) / "catalog"
|
||||
project_dir = root / "projects"
|
||||
project_dir.mkdir(parents=True)
|
||||
payload = json.loads(
|
||||
(ROOT / "catalog/projects/general-infrastructure.yaml").read_text()
|
||||
)
|
||||
payload["spec"]["virtualServers"][0]["endpointAlias"] = "not-an-alias"
|
||||
payload["spec"]["credentialToken"] = "plain-secret-value"
|
||||
(project_dir / "bad.yaml").write_text(json.dumps(payload), encoding="utf-8")
|
||||
report = CatalogLoader(root, schema_root=ROOT / "contracts/schemas").load()
|
||||
self.assertFalse(report.valid)
|
||||
self.assertTrue(
|
||||
any(
|
||||
issue.code == "schema_error" and issue.source.file == "projects/bad.yaml"
|
||||
for issue in report.issues
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
any(
|
||||
issue.code == "secret_value" and issue.source.pointer == "/spec/credentialToken"
|
||||
for issue in report.issues
|
||||
)
|
||||
)
|
||||
|
||||
self.assertNotIn(
|
||||
"plain-secret-value", json.dumps([document.payload for document in report.documents])
|
||||
)
|
||||
|
||||
def test_duplicate_identity_is_rejected(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory) / "catalog/projects"
|
||||
root.mkdir(parents=True)
|
||||
source = json.loads((ROOT / "catalog/projects/general-infrastructure.yaml").read_text())
|
||||
(root / "one.yaml").write_text(json.dumps(source), encoding="utf-8")
|
||||
(root / "two.yaml").write_text(json.dumps(source), encoding="utf-8")
|
||||
report = CatalogLoader(root.parent, schema_root=ROOT / "contracts/schemas").load()
|
||||
self.assertFalse(report.valid)
|
||||
self.assertTrue(any(issue.code == "duplicate_identity" for issue in report.issues))
|
||||
|
||||
def test_semantically_identical_key_order_has_same_hash(self) -> None:
|
||||
with (
|
||||
tempfile.TemporaryDirectory() as first_directory,
|
||||
tempfile.TemporaryDirectory() as second_directory,
|
||||
):
|
||||
first_root = Path(first_directory) / "catalog"
|
||||
second_root = Path(second_directory) / "catalog"
|
||||
import shutil
|
||||
|
||||
shutil.copytree(ROOT / "catalog", first_root)
|
||||
shutil.copytree(ROOT / "catalog", second_root)
|
||||
source = json.loads((ROOT / "catalog/projects/general-infrastructure.yaml").read_text())
|
||||
(first_root / "projects/general-infrastructure.yaml").write_text(
|
||||
json.dumps(source), encoding="utf-8"
|
||||
)
|
||||
(second_root / "projects/general-infrastructure.yaml").write_text(
|
||||
json.dumps(source, sort_keys=True), encoding="utf-8"
|
||||
)
|
||||
first = CatalogLoader(first_root, schema_root=ROOT / "contracts/schemas").load()
|
||||
second = CatalogLoader(second_root, schema_root=ROOT / "contracts/schemas").load()
|
||||
self.assertTrue(first.valid, first.issues)
|
||||
self.assertEqual(first.desired_hash, second.desired_hash)
|
||||
|
||||
|
||||
class CatalogReconcilerTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.fake = FakeContextForge()
|
||||
self.adapter = adapter_for(self.fake)
|
||||
self.loader = CatalogLoader(ROOT / "catalog", public_base_url="https://mcp.example.com")
|
||||
self.reconciler = CatalogReconciler(
|
||||
self.loader, self.adapter, store=MemoryReconcileRunStore()
|
||||
)
|
||||
self.workspace_id = uuid4()
|
||||
self.project_id = uuid4()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.adapter.close()
|
||||
|
||||
def test_apply_then_apply_again_is_noop(self) -> None:
|
||||
first = self.reconciler.apply(self.workspace_id, self.project_id)
|
||||
self.assertEqual(first.status, "succeeded")
|
||||
self.assertTrue(any(item["action"] == "create" for item in first.applied_changes))
|
||||
requests_after_first = len(self.fake.requests)
|
||||
second = self.reconciler.apply(self.workspace_id, self.project_id)
|
||||
self.assertEqual(second.status, "succeeded")
|
||||
self.assertEqual(second.applied_changes, ())
|
||||
self.assertEqual(len(self.fake.requests), requests_after_first + 2)
|
||||
|
||||
def test_partial_gateway_failure_is_truthful_and_retryable(self) -> None:
|
||||
class FailingAdapter:
|
||||
def __init__(self, client: ContextForgeClient) -> None:
|
||||
self.client = client
|
||||
self.failed = False
|
||||
|
||||
def list_gateways(self, **kwargs: object):
|
||||
return self.client.list_gateways(**kwargs)
|
||||
|
||||
def ensure_gateway(self, desired):
|
||||
if not self.failed:
|
||||
self.failed = True
|
||||
raise RuntimeError("simulated partial failure")
|
||||
return self.client.ensure_gateway(desired)
|
||||
|
||||
failing = FailingAdapter(self.adapter)
|
||||
failing_reconciler = CatalogReconciler(
|
||||
self.loader, failing, store=MemoryReconcileRunStore()
|
||||
)
|
||||
failed = failing_reconciler.apply(self.workspace_id, self.project_id)
|
||||
self.assertEqual(failed.status, "failed")
|
||||
self.assertTrue(failed.verification["resumable"])
|
||||
self.assertEqual(failed.applied_changes[0]["status"], "failed")
|
||||
recovered = failing_reconciler.apply(self.workspace_id, self.project_id)
|
||||
self.assertEqual(recovered.status, "succeeded")
|
||||
|
||||
def test_unknown_gateway_is_drift_and_not_deleted(self) -> None:
|
||||
from services.control_api.contextforge.models import GatewayDesiredState
|
||||
|
||||
self.adapter.ensure_gateway(
|
||||
GatewayDesiredState(
|
||||
slug="operator-owned", url="https://example.com/mcp", description="unknown"
|
||||
)
|
||||
)
|
||||
plan = self.reconciler.preview()
|
||||
self.assertTrue(any(item.slug == "operator-owned" for item in plan.drift))
|
||||
self.reconciler.apply(self.workspace_id, self.project_id)
|
||||
self.assertIn("operator-owned", {item["name"] for item in self.fake.gateways.values()})
|
||||
|
||||
def test_lock_recovers_after_exception_and_authoritative_run_store_survives_restart(
|
||||
self,
|
||||
) -> None:
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from packages.hub_core.domain import Project, ReconcileRun, Scope, Workspace
|
||||
from services.control_api.catalog.reconciler import ReconciliationLock
|
||||
from services.control_api.catalog.store import SqlAlchemyReconcileRunStore
|
||||
from services.control_api.persistence.db import create_engine_from_url, session_factory
|
||||
from services.control_api.persistence.models import Base
|
||||
from services.control_api.persistence.repositories import (
|
||||
ProjectRepository,
|
||||
WorkspaceRepository,
|
||||
)
|
||||
|
||||
engine = create_engine_from_url("sqlite:///:memory:")
|
||||
Base.metadata.create_all(engine)
|
||||
sessions = session_factory(engine)
|
||||
now = datetime.now(UTC)
|
||||
with self.assertRaises(RuntimeError):
|
||||
with ReconciliationLock("wp060-recovery"):
|
||||
raise RuntimeError("simulated redis/cache loss")
|
||||
with ReconciliationLock("wp060-recovery"):
|
||||
pass
|
||||
with sessions.begin() as session:
|
||||
WorkspaceRepository(session, Scope.workspace(self.workspace_id)).create(
|
||||
Workspace(
|
||||
id=self.workspace_id,
|
||||
slug="itworx",
|
||||
name="ITWorx",
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
)
|
||||
ProjectRepository(session, Scope.workspace(self.workspace_id)).create(
|
||||
Project(
|
||||
id=self.project_id,
|
||||
workspace_id=self.workspace_id,
|
||||
slug="catalog",
|
||||
name="Catalog",
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
)
|
||||
store = SqlAlchemyReconcileRunStore(
|
||||
session, Scope.project(self.workspace_id, self.project_id)
|
||||
)
|
||||
run = ReconcileRun(
|
||||
workspace_id=self.workspace_id,
|
||||
project_id=self.project_id,
|
||||
desired_hash="a" * 64,
|
||||
status="created",
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
stored = store.create(run)
|
||||
updated = stored.model_copy(
|
||||
update={"status": "planning", "revision": 2, "updated_at": now}
|
||||
)
|
||||
store.save(updated)
|
||||
engine.dispose()
|
||||
|
||||
def test_router_requires_authorization_and_exposes_scoped_operations(self) -> None:
|
||||
app = FastAPI()
|
||||
app.include_router(build_catalog_router(self.reconciler, authorize=lambda _: None))
|
||||
client = TestClient(app)
|
||||
denied_app = FastAPI()
|
||||
denied_app.include_router(build_catalog_router(self.reconciler))
|
||||
denied = TestClient(denied_app).post("/catalog/validate")
|
||||
self.assertEqual(denied.status_code, 403)
|
||||
valid = client.post("/catalog/validate")
|
||||
self.assertEqual(valid.status_code, 200)
|
||||
body = {"workspace_id": str(self.workspace_id), "project_id": str(self.project_id)}
|
||||
applied = client.post("/catalog/apply", json=body)
|
||||
self.assertEqual(applied.status_code, 200)
|
||||
run = client.get(f"/catalog/status/{applied.json()['id']}")
|
||||
self.assertEqual(run.status_code, 200)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user