Files
ITWorx_MCP_Hub-Public/tests/test_wp140_clients.py
T

159 lines
6.1 KiB
Python

from __future__ import annotations
from datetime import UTC, datetime, timedelta
from typing import Any
import pytest
from fastapi import FastAPI, HTTPException, Request
from fastapi.testclient import TestClient
from packages.hub_clients import (
ClientKind,
ClientRegistry,
FakeVirtualServer,
StdioBridge,
run_matrix,
)
from services.control_api.clients.router import build_client_router
from services.control_api.clients.service import ClientService
NOW = datetime(2026, 8, 1, 18, 0, tzinfo=UTC)
def test_client_profiles_are_independent_and_configs_are_secret_free() -> None:
registry = ClientRegistry()
first = registry.create(
name="Codex", project="ludarium", environment="prod", kind=ClientKind.NATIVE_2026, now=NOW
)
second = registry.create(
name="Inspector", project="ludarium", environment="prod", kind=ClientKind.INSPECTOR, now=NOW
)
assert first.id != second.id
assert registry.authorize(first.id, project="ludarium", environment="prod").id == first.id
with pytest.raises(PermissionError):
registry.authorize(first.id, project="other", environment="prod")
native = __import__("packages.hub_clients", fromlist=["generate_config"]).generate_config(first)
assert native.protocol_version == "2026-07-28"
assert "token" not in native.model_dump_json().lower()
assert "secret" not in native.model_dump_json().lower()
def test_disable_revoke_and_one_time_bootstrap_are_bounded() -> None:
registry = ClientRegistry()
client = registry.create(
name="Bridge", project="ludarium", environment="prod", kind=ClientKind.STDIO_BRIDGE, now=NOW
)
bootstrap = registry.issue_bootstrap(client.id, now=NOW)
consumed = registry.consume_bootstrap(bootstrap.reference, now=NOW + timedelta(seconds=1))
assert consumed.consumed is True
with pytest.raises(PermissionError):
registry.consume_bootstrap(bootstrap.reference, now=NOW + timedelta(seconds=2))
registry.disable(client.id, now=NOW + timedelta(seconds=3))
with pytest.raises(PermissionError):
registry.authorize(client.id, project="ludarium", environment="prod")
registry.revoke(client.id, now=NOW + timedelta(seconds=4))
assert registry.get(client.id).revoked_at == NOW + timedelta(seconds=4)
class _Transport:
def __init__(self) -> None:
self.tokens: list[str] = []
def send(self, request: dict[str, Any], *, token: str, endpoint: str) -> dict[str, Any]:
self.tokens.append(token)
return {"jsonrpc": "2.0", "id": request.get("id"), "result": {"endpoint": endpoint}}
def test_stdio_bridge_allows_bounded_mcp_methods_without_token_output() -> None:
transport = _Transport()
bridge = StdioBridge(endpoint="https://mcp.example.com/mcp/ludarium/prod")
output = tuple(
bridge.forward(
['{"jsonrpc":"2.0","id":1,"method":"tools/list"}', '{"method":"exec"}', "not-json"],
transport,
environ={"ITWORX_MCP_ACCESS_TOKEN": "fixture-token"},
)
)
assert '"endpoint"' in output[0]
assert "fixture-token" not in "".join(output)
assert transport.tokens == ["fixture-token"]
assert "unsupported_method" in output[1]
assert "invalid_json" in output[2]
def test_discovery_invocation_matrix_covers_all_profiles_and_connectors() -> None:
results = run_matrix(FakeVirtualServer())
assert len(results) == 12
assert {result.client_kind for result in results} == set(ClientKind)
assert all(result.discovered and result.invoked for result in results)
server = FakeVirtualServer()
server.revoked.add(ClientKind.NATIVE_2026.value)
with pytest.raises(PermissionError):
server.discover(ClientKind.NATIVE_2026.value, "2026-07-28")
def test_client_router_is_denied_by_default_and_supports_safe_lifecycle() -> None:
service = ClientService()
denied_app = FastAPI()
denied_app.include_router(build_client_router(service))
denied = TestClient(denied_app)
denied_response = denied.post(
"/clients",
json={"name": "x", "project": "ludarium", "environment": "prod", "kind": "native-2026"},
)
assert denied_response.status_code == 403
def authenticate_any(_: Request) -> str:
return "test-principal"
def allow_all(_: Any, __: str) -> None:
return None
allowed_app = FastAPI()
allowed_app.include_router(
build_client_router(service, authenticate=authenticate_any, authorize=allow_all)
)
allowed = TestClient(allowed_app)
created = allowed.post(
"/clients",
json={"name": "x", "project": "ludarium", "environment": "prod", "kind": "native-2026"},
)
assert created.status_code == 201
body = created.json()
assert "access_token" not in created.text.lower()
assert "secret" not in created.text.lower()
assert body["config"]["protocolVersion"] == "2026-07-28"
assert "expiresAt" in body["bootstrap"]
client_id = body["client"]["id"]
config = allowed.get(f"/clients/{client_id}/config")
assert config.status_code == 200
revoked = allowed.post(f"/clients/{client_id}/revoke")
assert revoked.status_code == 200
assert revoked.json()["status"] == "revoked"
def test_client_router_enforces_per_action_authorization() -> None:
"""A principal authenticated but denied a *specific* action gets 403 for
that action only -- distinct from the router being entirely unwired."""
service = ClientService()
def authenticate_any(_: Request) -> str:
return "test-principal"
def allow_reads_only(_: Any, action: str) -> None:
if action != "client.read":
raise HTTPException(status_code=403, detail=f"role does not permit {action}")
app = FastAPI()
app.include_router(
build_client_router(service, authenticate=authenticate_any, authorize=allow_reads_only)
)
client = TestClient(app)
assert client.get("/clients").status_code == 200
create_response = client.post(
"/clients",
json={"name": "x", "project": "ludarium", "environment": "prod", "kind": "native-2026"},
)
assert create_response.status_code == 403