from __future__ import annotations import json from pathlib import Path import pytest from services.runtime.connector_projectbrain import ( PUBLIC_TO_UPSTREAM, UPSTREAM_TO_PUBLIC, projectbrain_readiness_status, rewrite_request, rewrite_response, upstream_headers, ) def test_projectbrain_tool_catalog_is_bijective_and_namespaced() -> None: assert len(UPSTREAM_TO_PUBLIC) == len(PUBLIC_TO_UPSTREAM) == 57 assert all(name.startswith("projectbrain.") for name in PUBLIC_TO_UPSTREAM) assert PUBLIC_TO_UPSTREAM["projectbrain.workspace.render"] == "render_project_workspace" assert PUBLIC_TO_UPSTREAM["projectbrain.analysis.start"] == "start_repository_analysis" assert PUBLIC_TO_UPSTREAM["projectbrain.finding.issue.sync"] == "create_finding_issue" assert PUBLIC_TO_UPSTREAM["projectbrain.portfolio.get"] == "get_portfolio_control_plane" assert PUBLIC_TO_UPSTREAM["projectbrain.delivery.changeset.apply"] == "apply_feature_changeset" assert PUBLIC_TO_UPSTREAM["projectbrain.delivery.merge"] == "merge_feature_delivery" assert ( PUBLIC_TO_UPSTREAM["projectbrain.autonomy.executor.heartbeat"] == "heartbeat_auto_executor" ) assert PUBLIC_TO_UPSTREAM["projectbrain.autonomy.work.claim"] == "claim_auto_work" assert PUBLIC_TO_UPSTREAM["projectbrain.autonomy.work.report"] == "report_auto_work" def test_auto_work_claim_catalog_requires_executor_run_id() -> None: manifest_path = ( Path(__file__).parents[1] / "catalog" / "tools" / "projectbrain-autonomy-work-claim.yaml" ) manifest = json.loads(manifest_path.read_text(encoding="utf-8")) schema = manifest["spec"]["inputSchema"] assert set(schema["required"]) == {"project_id", "run_id"} assert schema["properties"]["run_id"] == { "type": "string", "minLength": 1, "maxLength": 160, } def test_auto_work_report_catalog_supports_blocker_classification() -> None: manifest = json.loads( ( Path(__file__).parents[1] / "catalog" / "tools" / "projectbrain-autonomy-work-report.yaml" ).read_text(encoding="utf-8") ) schema = manifest["spec"]["inputSchema"] assert schema["properties"]["blocker_type"] == { "type": "string", "enum": ["human", "retryable"], } assert schema["properties"]["retry_after_minutes"] == { "type": "integer", "minimum": 5, "maximum": 1440, } assert schema["additionalProperties"] is False def test_proxy_rewrites_only_registered_tool_calls() -> None: request = { "jsonrpc": "2.0", "id": 7, "method": "tools/call", "params": {"name": "projectbrain.context.get", "arguments": {"project_id": "demo"}}, } rewritten = rewrite_request(request) assert rewritten["params"]["name"] == "get_project_context" assert rewritten["params"]["arguments"] == {"project_id": "demo"} with pytest.raises(ValueError, match="not registered"): rewrite_request({"method": "tools/call", "params": {"name": "filesystem.delete"}}) def test_proxy_rewrites_tool_discovery_and_drops_unknown_upstream_tools() -> None: response = { "jsonrpc": "2.0", "id": 1, "result": { "tools": [ {"name": "list_projects", "description": "projects"}, {"name": "future_unsafe_tool", "description": "not catalogued"}, ] }, } rewritten = rewrite_response(response, "tools/list") assert rewritten["result"]["tools"] == [ {"name": "projectbrain.project.list", "description": "projects"} ] assert rewrite_response(response, "initialize") is response def test_proxy_forwards_the_bounded_streamable_http_context() -> None: headers = upstream_headers( { "accept": "*/*", "content-type": "application/json", "mcp-session-id": "session-7", "mcp-protocol-version": "2025-06-18", "last-event-id": "event-9", "x-itworx-client-id": "9efcd87b-bf6f-4bdc-8592-c62fa0e6da8d", "cookie": "must-not-leak", }, "service-token", has_body=True, ) assert headers == { "Authorization": "Bearer service-token", "Accept": "application/json, text/event-stream", "Content-Type": "application/json", "mcp-session-id": "session-7", "mcp-protocol-version": "2025-06-18", "last-event-id": "event-9", "x-itworx-client-id": "9efcd87b-bf6f-4bdc-8592-c62fa0e6da8d", } def test_proxy_omits_content_type_without_a_body() -> None: assert upstream_headers({}, "service-token", has_body=False) == { "Authorization": "Bearer service-token", "Accept": "application/json, text/event-stream", } @pytest.mark.parametrize( ("payload", "expected"), [ ({"status": "ready"}, "ready"), ({"data": {"status": "degraded"}}, "degraded"), ({"data": {"status": "blocked"}}, "blocked"), ({"data": None}, None), ], ) def test_projectbrain_readiness_status_accepts_api_envelopes(payload, expected) -> None: assert projectbrain_readiness_status(payload) == expected