Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

274 lines
9.7 KiB
Python

"""Unit tests for the groundcover MCP service client.
The transport is exercised by patching ``GroundcoverClient._session`` with a
fake MCP session, so these tests cover normalization, redaction, JSON/SSE-style
payload parsing, cached query-reference behavior, and verification logic without
any network access.
"""
from __future__ import annotations
from contextlib import asynccontextmanager
from types import SimpleNamespace
from typing import Any
import pytest
from mcp import types
import integrations.groundcover.client as gc_client
from integrations.config_models import GroundcoverIntegrationConfig
def _config(**overrides: Any) -> GroundcoverIntegrationConfig:
base: dict[str, Any] = {"api_key": "tok-secret", "mcp_url": "https://mcp.example.com/api/mcp"}
base.update(overrides)
return GroundcoverIntegrationConfig.model_validate(base)
def _text_result(text: str, *, is_error: bool = False) -> types.CallToolResult:
return types.CallToolResult(
content=[types.TextContent(type="text", text=text)],
isError=is_error,
)
def _patch_session(
monkeypatch: pytest.MonkeyPatch,
client: gc_client.GroundcoverClient,
*,
tools: list[str] | None = None,
results: dict[str, Any] | None = None,
) -> None:
"""Patch ``client._session`` to yield a fake MCP session."""
tool_names = tools or []
tool_results = results or {}
class _FakeSession:
async def initialize(self) -> None:
return None
async def list_tools(self) -> Any:
return SimpleNamespace(tools=[SimpleNamespace(name=name) for name in tool_names])
async def call_tool(self, name: str, _arguments: dict[str, Any]) -> Any:
outcome = tool_results.get(name)
if isinstance(outcome, Exception):
raise outcome
return outcome
@asynccontextmanager
async def _fake_session() -> Any:
yield _FakeSession()
monkeypatch.setattr(client, "_session", _fake_session)
@pytest.fixture(autouse=True)
def _clear_reference_cache() -> None:
gc_client._REFERENCE_CACHE.clear()
def test_not_configured_returns_unavailable_without_network() -> None:
client = gc_client.GroundcoverClient(_config(api_key=""))
result = client.call_tool("query_logs", {"query": "* | limit 10"})
assert result.success is False
assert "not configured" in (result.error or "")
def test_call_tool_parses_json_array_payload(monkeypatch: pytest.MonkeyPatch) -> None:
client = gc_client.GroundcoverClient(_config())
_patch_session(
monkeypatch,
client,
results={"query_logs": _text_result('[{"level":"error","msg":"boom"}]')},
)
result = client.call_tool("query_logs", {"query": "* | limit 10"})
assert result.success is True
assert result.data == [{"level": "error", "msg": "boom"}]
assert result.notes == []
def test_call_tool_keeps_trailing_guidance_as_notes(monkeypatch: pytest.MonkeyPatch) -> None:
payload = '[{"a":1}]\nResults truncated at 1 rows. Use more specific filters.'
client = gc_client.GroundcoverClient(_config())
_patch_session(monkeypatch, client, results={"query_logs": _text_result(payload)})
result = client.call_tool("query_logs", {"query": "* | limit 1"})
assert result.data == [{"a": 1}]
assert result.notes and "truncated" in result.notes[0].lower()
def test_call_tool_error_is_redacted(monkeypatch: pytest.MonkeyPatch) -> None:
leaky = "auth failed for Bearer tok-secret using tok-secret"
client = gc_client.GroundcoverClient(_config())
_patch_session(
monkeypatch,
client,
results={"query_logs": _text_result(leaky, is_error=True)},
)
result = client.call_tool("query_logs", {"query": "* | limit 1"})
assert result.success is False
assert "tok-secret" not in (result.error or "")
assert "Bearer ***" in (result.error or "")
def test_get_query_reference_caches(monkeypatch: pytest.MonkeyPatch) -> None:
calls = {"n": 0}
client = gc_client.GroundcoverClient(_config())
class _FakeSession:
async def initialize(self) -> None:
return None
async def call_tool(self, _name: str, _arguments: dict[str, Any]) -> Any:
calls["n"] += 1
return _text_result("# gcQL reference")
@asynccontextmanager
async def _fake_session() -> Any:
yield _FakeSession()
monkeypatch.setattr(client, "_session", _fake_session)
first = client.get_query_reference()
second = client.get_query_reference()
assert first["reference"] == "# gcQL reference"
assert first["cached"] is False
assert second["cached"] is True
assert calls["n"] == 1
def test_list_workspaces_normalizes(monkeypatch: pytest.MonkeyPatch) -> None:
workspaces = [{"tenant_uuid": "t1", "org_name": "Acme", "backends": ["prod"]}]
client = gc_client.GroundcoverClient(_config())
_patch_session(
monkeypatch,
client,
results={"list_workspaces": _text_result(_json(workspaces))},
)
result = client.list_workspaces()
assert result["success"] is True
assert result["total"] == 1
assert result["workspaces"][0]["org_name"] == "Acme"
def test_probe_missing_when_not_configured() -> None:
probe = gc_client.GroundcoverClient(_config(api_key="")).probe_access()
assert probe.status == "missing"
def test_probe_fails_when_required_tools_absent(monkeypatch: pytest.MonkeyPatch) -> None:
client = gc_client.GroundcoverClient(_config())
_patch_session(monkeypatch, client, tools=["query_logs"])
probe = client.probe_access()
assert probe.status == "failed"
assert "list_workspaces" in probe.detail
def test_probe_fails_when_no_workspaces(monkeypatch: pytest.MonkeyPatch) -> None:
client = gc_client.GroundcoverClient(_config())
_patch_session(
monkeypatch,
client,
tools=["list_workspaces", "get_gcql_reference"],
results={"list_workspaces": _text_result(_json([]))},
)
probe = client.probe_access()
assert probe.status == "failed"
assert "no workspaces" in probe.detail.lower()
def test_probe_reports_tenant_ambiguity(monkeypatch: pytest.MonkeyPatch) -> None:
workspaces = [
{"tenant_uuid": "t1", "org_name": "Acme", "backends": ["prod"]},
{"tenant_uuid": "t2", "org_name": "Beta", "backends": ["prod"]},
]
client = gc_client.GroundcoverClient(_config())
_patch_session(
monkeypatch,
client,
tools=["list_workspaces", "get_gcql_reference"],
results={"list_workspaces": _text_result(_json(workspaces))},
)
probe = client.probe_access()
assert probe.status == "failed"
assert "GROUNDCOVER_TENANT_UUID" in probe.detail
def test_probe_reports_backend_ambiguity(monkeypatch: pytest.MonkeyPatch) -> None:
workspaces = [{"tenant_uuid": "t1", "org_name": "Acme", "backends": ["prod", "staging"]}]
client = gc_client.GroundcoverClient(_config())
_patch_session(
monkeypatch,
client,
tools=["list_workspaces", "get_gcql_reference"],
results={"list_workspaces": _text_result(_json(workspaces))},
)
probe = client.probe_access()
assert probe.status == "failed"
assert "GROUNDCOVER_BACKEND_ID" in probe.detail
def test_probe_rejects_unknown_configured_tenant(monkeypatch: pytest.MonkeyPatch) -> None:
workspaces = [{"tenant_uuid": "t1", "org_name": "Acme", "backends": ["prod"]}]
client = gc_client.GroundcoverClient(_config(tenant_uuid="does-not-exist"))
_patch_session(
monkeypatch,
client,
tools=["list_workspaces", "get_gcql_reference"],
results={"list_workspaces": _text_result(_json(workspaces))},
)
probe = client.probe_access()
assert probe.status == "failed"
assert "GROUNDCOVER_TENANT_UUID" in probe.detail
assert "does-not-exist" in probe.detail
def test_probe_rejects_unknown_configured_backend(monkeypatch: pytest.MonkeyPatch) -> None:
workspaces = [{"tenant_uuid": "t1", "org_name": "Acme", "backends": ["prod", "staging"]}]
client = gc_client.GroundcoverClient(_config(tenant_uuid="t1", backend_id="typo"))
_patch_session(
monkeypatch,
client,
tools=["list_workspaces", "get_gcql_reference"],
results={"list_workspaces": _text_result(_json(workspaces))},
)
probe = client.probe_access()
assert probe.status == "failed"
assert "GROUNDCOVER_BACKEND_ID" in probe.detail
def test_probe_passes_with_valid_explicit_routing(monkeypatch: pytest.MonkeyPatch) -> None:
workspaces = [{"tenant_uuid": "t1", "org_name": "Acme", "backends": ["prod", "staging"]}]
client = gc_client.GroundcoverClient(_config(tenant_uuid="t1", backend_id="prod"))
_patch_session(
monkeypatch,
client,
tools=["list_workspaces", "get_gcql_reference", "query_logs"],
results={"list_workspaces": _text_result(_json(workspaces))},
)
probe = client.probe_access()
assert probe.status == "passed"
def test_probe_passes_single_workspace(monkeypatch: pytest.MonkeyPatch) -> None:
workspaces = [{"tenant_uuid": "t1", "org_name": "Acme", "backends": ["prod"]}]
client = gc_client.GroundcoverClient(_config())
_patch_session(
monkeypatch,
client,
tools=["list_workspaces", "get_gcql_reference", "query_logs"],
results={"list_workspaces": _text_result(_json(workspaces))},
)
probe = client.probe_access()
assert probe.status == "passed"
# Detail names the connection target + workspace; assert on stable, non-URL
# tokens (a host-substring check trips CodeQL's URL-sanitization heuristic).
assert probe.detail.startswith("Connected to ")
assert "Acme" in probe.detail
def _json(value: Any) -> str:
import json
return json.dumps(value)