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
215 lines
8.7 KiB
Python
215 lines
8.7 KiB
Python
"""Tests for the shared configured-integration-services helper.
|
|
|
|
This helper is the single source of truth shared by the welcome banner and the
|
|
REPL session, so it must return lowercase service keys, deduplicate, and never
|
|
raise (returning an empty list on failure).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from integrations import catalog
|
|
|
|
|
|
def test_returns_lowercase_service_keys_deduplicated(monkeypatch: Any) -> None:
|
|
monkeypatch.setattr(
|
|
catalog,
|
|
"load_env_integration_services",
|
|
lambda: ["GitLab", "datadog", "gitlab", ""],
|
|
)
|
|
monkeypatch.setattr(catalog, "load_integrations", list)
|
|
assert catalog.configured_integration_services() == ["gitlab", "datadog"]
|
|
|
|
|
|
def test_includes_active_store_integrations_and_dedupes_with_env(monkeypatch: Any) -> None:
|
|
monkeypatch.setattr(
|
|
catalog,
|
|
"load_env_integration_services",
|
|
lambda: ["sentry", "gitlab"],
|
|
)
|
|
monkeypatch.setattr(
|
|
catalog,
|
|
"load_integrations",
|
|
lambda: [
|
|
{"service": "GitHub", "status": "active"}, # store-only (e.g. first-launch login)
|
|
{"service": "gitlab", "status": "active"}, # duplicate of env entry
|
|
{"service": "datadog", "status": "disabled"}, # inactive — ignored
|
|
{"service": "", "status": "active"}, # ignored
|
|
],
|
|
)
|
|
assert catalog.configured_integration_services() == ["sentry", "gitlab", "github"]
|
|
|
|
|
|
def test_returns_empty_list_when_env_loader_raises(monkeypatch: Any) -> None:
|
|
def _boom() -> list[str]:
|
|
raise RuntimeError("env unreadable")
|
|
|
|
monkeypatch.setattr(catalog, "load_env_integration_services", _boom)
|
|
monkeypatch.setattr(catalog, "load_integrations", list)
|
|
assert catalog.configured_integration_services() == []
|
|
|
|
|
|
def test_store_only_when_env_loader_raises(monkeypatch: Any) -> None:
|
|
def _boom() -> list[str]:
|
|
raise RuntimeError("env unreadable")
|
|
|
|
monkeypatch.setattr(catalog, "load_env_integration_services", _boom)
|
|
monkeypatch.setattr(
|
|
catalog,
|
|
"load_integrations",
|
|
lambda: [{"service": "github", "status": "active"}],
|
|
)
|
|
assert catalog.configured_integration_services() == ["github"]
|
|
|
|
|
|
def test_empty_when_no_integrations(monkeypatch: Any) -> None:
|
|
monkeypatch.setattr(catalog, "load_env_integration_services", list)
|
|
monkeypatch.setattr(catalog, "load_integrations", list)
|
|
assert catalog.configured_integration_services() == []
|
|
|
|
|
|
def test_configured_services_do_not_call_full_env_loader(monkeypatch: Any) -> None:
|
|
def _full_loader_should_not_run() -> list[dict[str, Any]]:
|
|
raise AssertionError("startup metadata path must not resolve env integrations")
|
|
|
|
monkeypatch.setattr(catalog, "load_env_integrations", _full_loader_should_not_run)
|
|
monkeypatch.setattr(catalog, "load_env_integration_services", lambda: ["gitlab"])
|
|
monkeypatch.setattr(catalog, "load_integrations", list)
|
|
|
|
assert catalog.configured_integration_services() == ["gitlab"]
|
|
|
|
|
|
def test_env_service_list_uses_plain_env_without_keyring(monkeypatch: Any) -> None:
|
|
monkeypatch.setenv("GITLAB_ACCESS_TOKEN", "from-env")
|
|
monkeypatch.delenv("POSTHOG_MCP_AUTH_TOKEN", raising=False)
|
|
|
|
def _keyring_should_not_run(*_args: Any, **_kwargs: Any) -> str:
|
|
raise AssertionError("startup metadata path must not read keyring")
|
|
|
|
monkeypatch.setattr("keyring.get_password", _keyring_should_not_run)
|
|
|
|
assert "gitlab" in catalog.load_env_integration_services()
|
|
|
|
|
|
class TestConfiguredIntegrationHealth:
|
|
"""Offline health for the welcome banner: present vs. minimally usable.
|
|
|
|
The banner must not imply a half-configured integration (e.g. a hosted MCP
|
|
record saved without an API token) is connected, so this helper downgrades
|
|
such records to ``"incomplete"`` without running any network verification.
|
|
"""
|
|
|
|
def test_ok_when_classified_into_usable_config(self, monkeypatch: Any) -> None:
|
|
monkeypatch.setattr(
|
|
catalog, "configured_integration_services", lambda: ["datadog", "gitlab"]
|
|
)
|
|
monkeypatch.setattr(catalog, "load_integrations", list)
|
|
assert catalog.configured_integration_health() == [
|
|
("datadog", "ok"),
|
|
("gitlab", "ok"),
|
|
]
|
|
|
|
def test_hosted_mcp_without_token_is_incomplete(self, monkeypatch: Any) -> None:
|
|
monkeypatch.setattr(catalog, "configured_integration_services", lambda: ["posthog_mcp"])
|
|
monkeypatch.setattr(
|
|
catalog,
|
|
"load_integrations",
|
|
lambda: [
|
|
{
|
|
"service": "posthog_mcp",
|
|
"status": "active",
|
|
"instances": [
|
|
{
|
|
"name": "default",
|
|
"tags": {},
|
|
"credentials": {
|
|
"mode": "streamable-http",
|
|
"url": "https://mcp.posthog.com/mcp",
|
|
"auth_token": "",
|
|
},
|
|
}
|
|
],
|
|
}
|
|
],
|
|
)
|
|
assert catalog.configured_integration_health() == [("posthog_mcp", "incomplete")]
|
|
|
|
def test_hosted_mcp_with_token_is_ok(self, monkeypatch: Any) -> None:
|
|
monkeypatch.setattr(catalog, "configured_integration_services", lambda: ["posthog_mcp"])
|
|
monkeypatch.setattr(
|
|
catalog,
|
|
"load_integrations",
|
|
lambda: [
|
|
{
|
|
"service": "posthog_mcp",
|
|
"status": "active",
|
|
"instances": [
|
|
{
|
|
"name": "default",
|
|
"tags": {},
|
|
"credentials": {
|
|
"mode": "streamable-http",
|
|
"url": "https://mcp.posthog.com/mcp",
|
|
"auth_token": "phx_secret",
|
|
},
|
|
}
|
|
],
|
|
}
|
|
],
|
|
)
|
|
assert catalog.configured_integration_health() == [("posthog_mcp", "ok")]
|
|
|
|
def test_stdio_mcp_without_token_is_ok(self, monkeypatch: Any) -> None:
|
|
# stdio MCP authenticates via the local subprocess, so no token is needed.
|
|
monkeypatch.setattr(catalog, "configured_integration_services", lambda: ["posthog_mcp"])
|
|
monkeypatch.setattr(
|
|
catalog,
|
|
"load_integrations",
|
|
lambda: [
|
|
{
|
|
"service": "posthog_mcp",
|
|
"status": "active",
|
|
"instances": [
|
|
{
|
|
"name": "default",
|
|
"tags": {},
|
|
"credentials": {"mode": "stdio", "command": "npx", "auth_token": ""},
|
|
}
|
|
],
|
|
}
|
|
],
|
|
)
|
|
assert catalog.configured_integration_health() == [("posthog_mcp", "ok")]
|
|
|
|
def test_non_mcp_empty_token_field_is_not_flagged(self, monkeypatch: Any) -> None:
|
|
# Only the hosted-MCP token rule applies; an unrelated service that
|
|
# classified successfully stays "ok" even if it lacks an auth_token key.
|
|
monkeypatch.setattr(catalog, "configured_integration_services", lambda: ["github"])
|
|
monkeypatch.setattr(catalog, "load_integrations", list)
|
|
assert catalog.configured_integration_health() == [("github", "ok")]
|
|
|
|
def test_empty_when_no_integrations(self, monkeypatch: Any) -> None:
|
|
monkeypatch.setattr(catalog, "configured_integration_services", list)
|
|
assert catalog.configured_integration_health() == []
|
|
|
|
def test_defaults_ok_when_store_load_raises(self, monkeypatch: Any) -> None:
|
|
def _boom() -> list[dict[str, Any]]:
|
|
raise RuntimeError("store unreadable")
|
|
|
|
monkeypatch.setattr(catalog, "configured_integration_services", lambda: ["datadog"])
|
|
monkeypatch.setattr(catalog, "load_integrations", _boom)
|
|
# Resolution failure must not crash the banner or alarm the user: when
|
|
# health can't be determined offline, every service falls back to "ok".
|
|
assert catalog.configured_integration_health() == [("datadog", "ok")]
|
|
|
|
def test_health_does_not_call_effective_resolution(self, monkeypatch: Any) -> None:
|
|
def _resolve_should_not_run() -> dict[str, Any]:
|
|
raise AssertionError("startup health must not resolve secrets")
|
|
|
|
monkeypatch.setattr(catalog, "configured_integration_services", lambda: ["datadog"])
|
|
monkeypatch.setattr(catalog, "load_integrations", list)
|
|
monkeypatch.setattr(catalog, "resolve_effective_integrations", _resolve_should_not_run)
|
|
|
|
assert catalog.configured_integration_health() == [("datadog", "ok")]
|