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
133 lines
5.0 KiB
Python
133 lines
5.0 KiB
Python
"""Config, catalog, env-loading, and verification tests for the groundcover provider."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from integrations.config_models import GroundcoverIntegrationConfig
|
|
from integrations.groundcover.verifier import verify_groundcover as _verify_groundcover
|
|
from integrations.probes import ProbeResult
|
|
from integrations.verify import resolve_effective_integrations
|
|
|
|
|
|
def test_config_defaults_and_normalization() -> None:
|
|
config = GroundcoverIntegrationConfig.model_validate(
|
|
{"api_key": "Bearer tok", "mcp_url": "", "timezone": "", "tenant_uuid": " "}
|
|
)
|
|
assert config.api_key == "tok" # Bearer prefix stripped
|
|
assert config.mcp_url == "https://mcp.groundcover.com/api/mcp"
|
|
assert config.timezone == "UTC"
|
|
assert config.tenant_uuid == ""
|
|
assert config.is_configured is True
|
|
|
|
|
|
def test_config_request_headers_only_include_configured_routing() -> None:
|
|
minimal = GroundcoverIntegrationConfig.model_validate({"api_key": "tok"})
|
|
assert minimal.request_headers == {
|
|
"Authorization": "Bearer tok",
|
|
"X-Timezone": "UTC",
|
|
}
|
|
routed = GroundcoverIntegrationConfig.model_validate(
|
|
{"api_key": "tok", "tenant_uuid": "t1", "backend_id": "b1"}
|
|
)
|
|
assert routed.request_headers["X-Tenant-UUID"] == "t1"
|
|
assert routed.request_headers["X-Backend-Id"] == "b1"
|
|
|
|
|
|
def test_config_rejects_unknown_field_with_suggestion() -> None:
|
|
with pytest.raises(ValidationError, match="tenant_uuid"):
|
|
GroundcoverIntegrationConfig.model_validate({"api_key": "tok", "tenant_id": "t1"})
|
|
|
|
|
|
def test_config_rejects_non_https_url() -> None:
|
|
with pytest.raises(ValidationError, match="https"):
|
|
GroundcoverIntegrationConfig.model_validate(
|
|
{"api_key": "tok", "mcp_url": "http://mcp.groundcover.com/api/mcp"}
|
|
)
|
|
|
|
|
|
def test_config_allows_loopback_http_for_tests() -> None:
|
|
config = GroundcoverIntegrationConfig.model_validate(
|
|
{"api_key": "tok", "mcp_url": "http://127.0.0.1:8080/api/mcp"}
|
|
)
|
|
assert config.mcp_url == "http://127.0.0.1:8080/api/mcp"
|
|
|
|
|
|
def test_env_single_instance_resolves(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr("integrations.catalog.load_integrations", lambda: [])
|
|
monkeypatch.setenv("GROUNDCOVER_API_KEY", "env-tok")
|
|
monkeypatch.setenv("GROUNDCOVER_TENANT_UUID", "t1")
|
|
|
|
effective = resolve_effective_integrations()
|
|
|
|
assert effective["groundcover"]["source"] == "local env"
|
|
config = effective["groundcover"]["config"]
|
|
assert config["api_key"] == "env-tok"
|
|
assert config["tenant_uuid"] == "t1"
|
|
assert config["mcp_url"] == "https://mcp.groundcover.com/api/mcp"
|
|
|
|
|
|
def test_env_token_alias(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr("integrations.catalog.load_integrations", lambda: [])
|
|
monkeypatch.delenv("GROUNDCOVER_API_KEY", raising=False)
|
|
monkeypatch.setenv("GROUNDCOVER_MCP_TOKEN", "alias-tok")
|
|
|
|
effective = resolve_effective_integrations()
|
|
|
|
assert effective["groundcover"]["config"]["api_key"] == "alias-tok"
|
|
|
|
|
|
def test_env_multi_instance(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
from integrations.catalog import load_env_integrations
|
|
|
|
monkeypatch.delenv("GROUNDCOVER_API_KEY", raising=False)
|
|
monkeypatch.delenv("GROUNDCOVER_MCP_TOKEN", raising=False)
|
|
monkeypatch.setenv(
|
|
"GROUNDCOVER_INSTANCES",
|
|
'[{"name":"prod","api_key":"k1","tenant_uuid":"t1"},'
|
|
'{"name":"staging","api_key":"k2","tenant_uuid":"t2"}]',
|
|
)
|
|
|
|
records = load_env_integrations()
|
|
groundcover_records = [r for r in records if r.get("service") == "groundcover"]
|
|
assert len(groundcover_records) == 1
|
|
instances = groundcover_records[0]["instances"]
|
|
assert [i["name"] for i in instances] == ["prod", "staging"]
|
|
assert instances[0]["credentials"]["api_key"] == "k1"
|
|
|
|
|
|
def test_verify_reports_probe_failure(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
from integrations.groundcover.client import GroundcoverClient
|
|
|
|
monkeypatch.setattr(
|
|
GroundcoverClient,
|
|
"probe_access",
|
|
lambda _self: ProbeResult.failed("Could not connect to groundcover MCP: timeout"),
|
|
)
|
|
|
|
result = _verify_groundcover("local env", {"api_key": "tok"})
|
|
|
|
assert result["status"] == "failed"
|
|
assert "connect" in result["detail"].lower()
|
|
|
|
|
|
def test_verify_missing_when_no_token() -> None:
|
|
result = _verify_groundcover("local store", {"api_key": "", "integration_id": "gc-local"})
|
|
assert result["status"] == "missing"
|
|
assert "Missing groundcover API key" in result["detail"]
|
|
|
|
|
|
def test_verify_never_leaks_token(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
from integrations.groundcover.client import GroundcoverClient
|
|
|
|
def _raise(_self: GroundcoverClient) -> ProbeResult:
|
|
raise RuntimeError("boom")
|
|
|
|
monkeypatch.setattr(GroundcoverClient, "probe_access", _raise)
|
|
|
|
result = _verify_groundcover("local env", {"api_key": "super-secret-token"})
|
|
|
|
assert result["status"] == "failed"
|
|
assert "super-secret-token" not in result["detail"]
|