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

512 lines
15 KiB
Python

"""Tests for AlertmanagerClient."""
from __future__ import annotations
from typing import Any
from unittest.mock import MagicMock
import pytest
from integrations.alertmanager.client import (
AlertmanagerClient,
make_alertmanager_client,
)
from integrations.config_models import AlertmanagerIntegrationConfig
AlertmanagerConfig = AlertmanagerIntegrationConfig
class _FakeResponse:
def __init__(self, payload: Any, status_code: int = 200) -> None:
self._payload = payload
self.status_code = status_code
self.text = str(payload)[:200]
def raise_for_status(self) -> None:
if self.status_code >= 400:
import httpx
raise httpx.HTTPStatusError(
f"HTTP {self.status_code}",
request=None, # type: ignore[arg-type]
response=self, # type: ignore[arg-type]
)
def json(self) -> Any:
return self._payload
def _client(**kwargs: Any) -> AlertmanagerClient:
default_config = {
"base_url": "https://alertmanager.example.com",
"bearer_token": "test-token",
"username": "",
"password": "",
}
default_config.update(kwargs)
return AlertmanagerClient(AlertmanagerConfig(**default_config))
# --- Config ---
def test_bearer_token_auth_includes_header() -> None:
c = _client(bearer_token="my-secret-token")
assert c.config.headers["Authorization"] == "Bearer my-secret-token"
def test_basic_auth_config_set() -> None:
c = _client(bearer_token="", username="admin", password="secret123")
assert c.config.username == "admin"
assert c.config.password == "secret123"
assert c.config.bearer_token == ""
assert c.config.basic_auth == ("admin", "secret123")
def test_basic_auth_headers_do_not_include_bearer() -> None:
c = _client(bearer_token="", username="admin", password="secret123")
assert "Authorization" not in c.config.headers
def test_is_configured_with_bearer_token() -> None:
c = _client(bearer_token="test-token")
assert c.is_configured is True
def test_is_configured_with_basic_auth() -> None:
c = _client(bearer_token="", username="user", password="pass")
assert c.is_configured is True
def test_is_configured_without_credentials() -> None:
c = _client(bearer_token="", username="", password="")
# Has base_url so is configured
assert c.is_configured is True
def test_is_configured_false_without_base_url() -> None:
client = AlertmanagerClient(AlertmanagerConfig(base_url="", bearer_token="token"))
assert client.is_configured is False
def test_dual_auth_rejected() -> None:
with pytest.raises(ValueError, match="both bearer_token and username"):
AlertmanagerConfig(
base_url="https://example.com",
bearer_token="token",
username="user",
password="pass",
)
def test_get_client_forwards_auth_config_to_httpx(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
class _FakeClient:
def __init__(self, **kwargs: Any) -> None:
captured.update(kwargs)
def close(self) -> None:
return None
monkeypatch.setattr("integrations.alertmanager.client.httpx.Client", _FakeClient)
c = _client(bearer_token="", username="admin", password="secret123")
_ = c._get_client()
assert captured["base_url"] == "https://alertmanager.example.com"
assert captured["headers"] == {"Content-Type": "application/json"}
assert captured["auth"] == ("admin", "secret123")
assert captured["timeout"] == 30
# --- get_status ---
def test_get_status_success(monkeypatch: pytest.MonkeyPatch) -> None:
payload = {
"cluster": {
"status": "ok",
"peers": ["peer1", "peer2"],
}
}
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse(payload),
)
result = _client().get_status()
assert result["success"] is True
assert result["status"]["cluster"]["status"] == "ok"
def test_get_status_http_error(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse({"message": "forbidden"}, 403),
)
result = _client().get_status()
assert result["success"] is False
assert "403" in result["error"]
def test_get_status_network_error(monkeypatch: pytest.MonkeyPatch) -> None:
import httpx
def _raise(_self: Any, _path: str, **_kw: Any) -> _FakeResponse:
raise httpx.ConnectError("connection refused")
monkeypatch.setattr("integrations.alertmanager.client.httpx.Client.get", _raise)
result = _client().get_status()
assert result["success"] is False
assert "connection refused" in result["error"]
def test_get_status_calls_correct_endpoint(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
def _fake_get(_self: Any, path: str, **_kwargs: Any) -> _FakeResponse:
captured["path"] = path
return _FakeResponse({"cluster": {"status": "ok"}})
monkeypatch.setattr("integrations.alertmanager.client.httpx.Client.get", _fake_get)
_client().get_status()
assert captured["path"] == "/api/v2/status"
# --- list_alerts ---
def test_list_alerts_success(monkeypatch: pytest.MonkeyPatch) -> None:
payload = [
{
"fingerprint": "abc123",
"status": {"state": "firing", "inhibitedBy": [], "silencedBy": []},
"labels": {"alertname": "HighErrorRate", "severity": "critical"},
"annotations": {"description": "Error rate > 50%"},
"startsAt": "2026-05-02T10:00:00Z",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://prometheus.example.com",
}
]
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse(payload),
)
result = _client().list_alerts()
assert result["success"] is True
assert len(result["alerts"]) == 1
assert result["alerts"][0]["fingerprint"] == "abc123"
assert result["alerts"][0]["status"] == "firing"
def test_list_alerts_with_filter_params(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
def _fake_get(_self: Any, _path: str, **kwargs: Any) -> _FakeResponse:
captured["params"] = kwargs.get("params", {})
return _FakeResponse([])
monkeypatch.setattr("integrations.alertmanager.client.httpx.Client.get", _fake_get)
_client().list_alerts(active=True, silenced=False, filter_labels=['alertname="Test"'])
assert captured["params"]["active"] == "true"
assert captured["params"]["silenced"] == "false"
assert captured["params"]["filter"] == ['alertname="Test"']
def test_list_alerts_http_error(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse({"error": "unauthorized"}, 401),
)
result = _client().list_alerts()
assert result["success"] is False
assert "401" in result["error"]
def test_list_alerts_empty_response(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse([]),
)
result = _client().list_alerts()
assert result["success"] is True
assert result["alerts"] == []
assert result["total"] == 0
def test_list_alerts_unexpected_format(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse({"not": "a list"}),
)
result = _client().list_alerts()
assert result["success"] is False
assert "Unexpected response format" in result["error"]
def test_list_alerts_limit_respected(monkeypatch: pytest.MonkeyPatch) -> None:
payload = [
{
"fingerprint": f"fp{i}",
"status": {"state": "firing", "inhibitedBy": [], "silencedBy": []},
}
for i in range(10)
]
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse(payload),
)
result = _client().list_alerts(limit=5)
assert result["success"] is True
assert result["total"] == 5
# --- list_silences ---
def test_list_silences_success(monkeypatch: pytest.MonkeyPatch) -> None:
payload = [
{
"id": "silence-123",
"status": {"state": "active"},
"matchers": [{"name": "service", "value": "api", "isRegex": False}],
"comment": "Planned maintenance",
"createdBy": "admin@example.com",
"startsAt": "2026-05-02T10:00:00Z",
"endsAt": "2026-05-02T12:00:00Z",
},
{
"id": "silence-456",
"status": {"state": "expired"},
"matchers": [],
"comment": "Old maintenance",
"createdBy": "admin@example.com",
"startsAt": "2026-05-01T10:00:00Z",
"endsAt": "2026-05-01T12:00:00Z",
},
]
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse(payload),
)
result = _client().list_silences()
assert result["success"] is True
assert result["total"] == 2
assert result["silences"][0]["id"] == "silence-123"
assert result["silences"][0]["comment"] == "Planned maintenance"
assert result["active_silences"] == [result["silences"][0]]
def test_list_silences_http_error(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse({"error": "forbidden"}, 403),
)
result = _client().list_silences()
assert result["success"] is False
assert "HTTP 403" in result["error"]
def test_list_silences_empty_response(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse([]),
)
result = _client().list_silences()
assert result["success"] is True
assert result["silences"] == []
assert result["active_silences"] == []
assert result["total"] == 0
def test_list_silences_unexpected_format(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse({"silences": []}),
)
result = _client().list_silences()
assert result["success"] is False
assert "Unexpected response format" in result["error"]
def test_list_silences_limit_respected(monkeypatch: pytest.MonkeyPatch) -> None:
payload = [
{
"id": f"silence-{i}",
"status": {"state": "active"},
}
for i in range(10)
]
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse(payload),
)
result = _client().list_silences(limit=5)
assert result["success"] is True
assert result["total"] == 5
assert len(result["active_silences"]) == 5
# --- Context Manager ---
def test_close_releases_http_client() -> None:
c = _client()
mock_client = MagicMock()
c._client = mock_client
assert c._client is not None
c.close()
assert c._client is None
mock_client.close.assert_called_once()
def test_close_is_idempotent() -> None:
c = _client()
mock_client = MagicMock()
c._client = mock_client
c.close()
c.close() # Should not raise
mock_client.close.assert_called_once()
def test_context_manager_closes_on_exit() -> None:
c = _client()
mock_client = MagicMock()
c._client = mock_client
with c:
assert c._client is not None
assert c._client is None
mock_client.close.assert_called_once()
def _raise_value_error() -> None:
raise ValueError("test error")
def test_context_manager_closes_on_exception() -> None:
c = _client()
mock_client = MagicMock()
c._client = mock_client
with pytest.raises(ValueError), c:
_raise_value_error()
assert c._client is None
mock_client.close.assert_called_once()
def test_context_manager_returns_self() -> None:
c = _client()
with c as entered:
assert entered is c
# --- Factory Function ---
def test_make_client_with_bearer_token() -> None:
client = make_alertmanager_client(
base_url="https://alertmanager.example.com",
bearer_token="test-token",
)
assert client is not None
assert client.is_configured is True
assert client.config.bearer_token == "test-token"
def test_make_client_with_basic_auth() -> None:
client = make_alertmanager_client(
base_url="https://alertmanager.example.com",
username="admin",
password="secret",
)
assert client is not None
assert client.config.username == "admin"
assert client.config.password == "secret"
def test_make_client_returns_none_when_no_url() -> None:
assert make_alertmanager_client("") is None
assert make_alertmanager_client(None) is None
assert make_alertmanager_client(" ") is None
def test_make_client_normalizes_url() -> None:
client = make_alertmanager_client(
base_url="https://alertmanager.example.com/",
bearer_token="token",
)
assert client is not None
assert client.config.base_url == "https://alertmanager.example.com"
def test_make_client_strips_whitespace() -> None:
client = make_alertmanager_client(
base_url=" https://alertmanager.example.com ",
bearer_token="token",
)
assert client is not None
assert client.config.base_url == "https://alertmanager.example.com"
# --- Probe Access ---
def test_probe_access_success(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse({"cluster": {"status": "ok"}}),
)
result = _client().probe_access()
assert result.status == "passed"
assert "Connected to Alertmanager" in result.detail
def test_probe_access_failure(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(
"integrations.alertmanager.client.httpx.Client.get",
lambda _self, _path, **_kw: _FakeResponse({"error": "unauthorized"}, 401),
)
result = _client().probe_access()
assert result.status == "failed"
assert "Status check failed" in result.detail
def test_probe_access_not_configured() -> None:
client = AlertmanagerClient(AlertmanagerConfig(base_url="", bearer_token="token"))
result = client.probe_access()
assert result.status == "missing"
assert "Missing base_url" in result.detail