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