Files
tracer-cloud--opensre/tests/integrations/honeycomb/test_client.py
T
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

379 lines
13 KiB
Python

"""Tests for the Honeycomb service client."""
from unittest.mock import MagicMock, patch
import httpx
import pytest
from integrations.config_models import HoneycombIntegrationConfig
from integrations.honeycomb.client import HoneycombClient
@pytest.fixture
def config() -> HoneycombIntegrationConfig:
return HoneycombIntegrationConfig(
api_key="test-api-key",
dataset="test-dataset",
base_url="https://api.honeycomb.io",
)
@pytest.fixture
def client(config: HoneycombIntegrationConfig) -> HoneycombClient:
return HoneycombClient(config)
def test_is_configured(config: HoneycombIntegrationConfig) -> None:
# Both present
client = HoneycombClient(config)
assert client.is_configured is True
# Missing API key
config_no_key = HoneycombIntegrationConfig(api_key="", dataset="ds")
client_no_key = HoneycombClient(config_no_key)
assert client_no_key.is_configured is False
# Missing dataset
# Note: HoneycombIntegrationConfig has a default for dataset, so we test with empty string
config_no_ds = HoneycombIntegrationConfig(api_key="key", dataset="")
client_no_ds = HoneycombClient(config_no_ds)
# The normalize_dataset validator might prevent empty string, let's check the code
# Actually it returns DEFAULT_HONEYCOMB_DATASET if stripped value is empty
# So we should check if is_configured handles that or if we need to force it.
# But based on the code: bool(self.config.api_key and self.config.dataset)
# If dataset is "__all__", it's truthy.
assert client_no_ds.is_configured is True # because it defaults to "__all__"
def test_validate_access_success(client: HoneycombClient) -> None:
mock_response = MagicMock()
mock_response.json.return_value = {
"environment": {"name": "prod"},
"team": {"name": "my-team"},
"type": "ingest",
}
mock_response.raise_for_status = MagicMock()
mock_http = MagicMock()
mock_http.get.return_value = mock_response
client._client = mock_http
result = client.validate_access()
mock_http.get.assert_called_once_with("/1/auth")
assert result["success"] is True
assert result["environment"] == {"name": "prod"}
assert result["team"] == {"name": "my-team"}
assert result["key_type"] == "ingest"
def test_validate_access_http_error(client: HoneycombClient) -> None:
mock_response = MagicMock()
mock_response.status_code = 401
mock_response.text = "Unauthorized"
# We need to simulate httpx.HTTPStatusError
error = httpx.HTTPStatusError("Auth failed", request=MagicMock(), response=mock_response)
mock_response.raise_for_status.side_effect = error
mock_http = MagicMock()
mock_http.get.return_value = mock_response
client._client = mock_http
result = client.validate_access()
assert result["success"] is False
assert "HTTP 401: Unauthorized" in result["error"]
def test_validate_access_generic_exception(client: HoneycombClient) -> None:
mock_http = MagicMock()
mock_http.get.side_effect = Exception("Connection refused")
client._client = mock_http
result = client.validate_access()
assert result["success"] is False
assert "Connection refused" in result["error"]
def test_probe_access_success(client: HoneycombClient) -> None:
client.validate_access = MagicMock(
return_value={"success": True, "environment": {"slug": "prod"}}
)
client.run_query = MagicMock(return_value={"success": True, "results": [{}]})
result = client.probe_access()
assert result.status == "passed"
assert "prod" in result.detail
assert "test-dataset" in result.detail
def test_create_query_success(client: HoneycombClient) -> None:
query_spec = {"calculations": [{"op": "COUNT"}]}
mock_response = MagicMock()
mock_response.json.return_value = {"id": "query-123", "calculations": []}
mock_response.raise_for_status = MagicMock()
mock_http = MagicMock()
mock_http.post.return_value = mock_response
client._client = mock_http
result = client.create_query(query_spec)
mock_http.post.assert_any_call("/1/queries/test-dataset", json=query_spec)
assert result["success"] is True
assert result["query_id"] == "query-123"
assert result["query"] == {"id": "query-123", "calculations": []}
def test_create_query_no_id(client: HoneycombClient) -> None:
mock_response = MagicMock()
mock_response.json.return_value = {"something": "else"}
mock_response.raise_for_status = MagicMock()
mock_http = MagicMock()
mock_http.post.return_value = mock_response
client._client = mock_http
result = client.create_query({})
assert result["success"] is False
assert "returned no query ID" in result["error"]
def test_create_query_http_error(client: HoneycombClient) -> None:
mock_response = MagicMock()
mock_response.status_code = 400
mock_response.text = "Bad Request"
error = httpx.HTTPStatusError("Err", request=MagicMock(), response=mock_response)
mock_response.raise_for_status.side_effect = error
mock_http = MagicMock()
mock_http.post.return_value = mock_response
client._client = mock_http
result = client.create_query({})
assert result["success"] is False
assert "HTTP 400: Bad Request" in result["error"]
def test_create_query_result_success(client: HoneycombClient) -> None:
mock_response = MagicMock()
mock_response.json.return_value = {"id": "result-456", "complete": False}
mock_response.raise_for_status = MagicMock()
mock_http = MagicMock()
mock_http.post.return_value = mock_response
client._client = mock_http
result = client.create_query_result("query-123", limit=10)
# Check payload
args, kwargs = mock_http.post.call_args
assert args[0] == "/1/query_results/test-dataset"
assert kwargs["json"]["query_id"] == "query-123"
assert kwargs["json"]["limit"] == 10
assert result["success"] is True
assert result["result"]["id"] == "result-456"
def test_create_query_result_http_error(client: HoneycombClient) -> None:
mock_response = MagicMock()
mock_response.status_code = 500
mock_response.text = "Internal Server Error"
error = httpx.HTTPStatusError("Err", request=MagicMock(), response=mock_response)
mock_response.raise_for_status.side_effect = error
mock_http = MagicMock()
mock_http.post.return_value = mock_response
client._client = mock_http
result = client.create_query_result("q", limit=1)
assert result["success"] is False
assert "HTTP 500: Internal Server Error" in result["error"]
def test_get_query_result_success(client: HoneycombClient) -> None:
mock_response = MagicMock()
mock_response.json.return_value = {
"id": "result-456",
"complete": True,
"data": {"results": []},
}
mock_response.raise_for_status = MagicMock()
mock_http = MagicMock()
mock_http.get.return_value = mock_response
client._client = mock_http
result = client.get_query_result("result-456")
mock_http.get.assert_called_once_with("/1/query_results/test-dataset/result-456")
assert result["success"] is True
assert result["result"]["complete"] is True
def test_get_query_result_http_error(client: HoneycombClient) -> None:
mock_response = MagicMock()
mock_response.status_code = 404
mock_response.text = "Not Found"
error = httpx.HTTPStatusError("Err", request=MagicMock(), response=mock_response)
mock_response.raise_for_status.side_effect = error
mock_http = MagicMock()
mock_http.get.return_value = mock_response
client._client = mock_http
result = client.get_query_result("missing")
assert result["success"] is False
assert "HTTP 404: Not Found" in result["error"]
def test_get_query_result_generic_exception(client: HoneycombClient) -> None:
mock_http = MagicMock()
mock_http.get.side_effect = Exception("Timeout")
client._client = mock_http
result = client.get_query_result("result-id")
assert result["success"] is False
assert "Timeout" in result["error"]
def test_run_query_full_flow(client: HoneycombClient) -> None:
# We'll mock the internal methods to avoid deep httpx patching
client.create_query = MagicMock(return_value={"success": True, "query_id": "q1"})
client.create_query_result = MagicMock(
return_value={
"success": True,
"result": {"id": "r1", "complete": True, "data": {"results": [{"data": {"val": 10}}]}},
}
)
# We also need to mock time.sleep to speed up tests if it ever hits it
with patch("time.sleep"):
result = client.run_query({"calculations": []})
assert result["success"] is True
assert result["query_id"] == "q1"
assert result["query_result_id"] == "r1"
assert result["results"] == [{"val": 10}]
def test_query_traces_success(client: HoneycombClient) -> None:
client.run_query = MagicMock(return_value={"success": True, "results": []})
result = client.query_traces(service_name="auth-service", trace_id="trace-abc")
assert result["success"] is True
# Verify run_query was called with correct filter shape
args, kwargs = client.run_query.call_args
query = args[0]
filters = query["filters"]
assert any(f["column"] == "service.name" and f["value"] == "auth-service" for f in filters)
assert any(f["column"] == "trace.trace_id" and f["value"] == "trace-abc" for f in filters)
def test_run_query_with_polling(client: HoneycombClient) -> None:
client.create_query = MagicMock(return_value={"success": True, "query_id": "q1"})
client.create_query_result = MagicMock(
return_value={"success": True, "result": {"id": "r1", "complete": False}}
)
# Mock two calls to get_query_result: first False, then True
client.get_query_result = MagicMock(
side_effect=[
{"success": True, "result": {"id": "r1", "complete": False}},
{"success": True, "result": {"id": "r1", "complete": True, "data": {"results": []}}},
]
)
with patch("time.sleep"):
result = client.run_query({"calculations": []}, poll_attempts=3)
assert result["success"] is True
assert client.get_query_result.call_count == 2
def test_run_query_timeout(client: HoneycombClient) -> None:
client.create_query = MagicMock(return_value={"success": True, "query_id": "q1"})
client.create_query_result = MagicMock(
return_value={"success": True, "result": {"id": "r1", "complete": False}}
)
# Always return incomplete
client.get_query_result = MagicMock(
return_value={"success": True, "result": {"id": "r1", "complete": False}}
)
with patch("time.sleep"):
result = client.run_query({"calculations": []}, poll_attempts=2)
assert result["success"] is False
assert "did not complete before the timeout" in result["error"]
# 2 poll attempts + initial check from create_query_result
# wait, run_query does:
# for _ in range(max(poll_attempts, 1)):
# if current_result.get("complete") is True: break
# time.sleep(...)
# fetched = self.get_query_result(...)
# So if poll_attempts=2:
# iteration 1: complete is False, sleep, get_query_result (fetched 1)
# iteration 2: complete is False, sleep, get_query_result (fetched 2)
# loop ends, return timeout error.
assert client.get_query_result.call_count == 2
def test_run_query_create_result_failure(client: HoneycombClient) -> None:
client.create_query = MagicMock(return_value={"success": True, "query_id": "q1"})
client.create_query_result = MagicMock(
return_value={"success": False, "error": "Internal failure"}
)
result = client.run_query({"calculations": []})
assert result["success"] is False
assert result["error"] == "Internal failure"
def test_query_traces_no_filters(client: HoneycombClient) -> None:
result = client.query_traces()
assert result["success"] is False
assert "require a service_name or trace_id" in result["error"]
def test_run_query_create_query_failure(client: HoneycombClient) -> None:
client.create_query = MagicMock(return_value={"success": False, "error": "Query error"})
result = client.run_query({"calculations": []})
assert result["success"] is False
assert result["error"] == "Query error"
def test_run_query_missing_result_id(client: HoneycombClient) -> None:
client.create_query = MagicMock(return_value={"success": True, "query_id": "q1"})
client.create_query_result = MagicMock(
return_value={"success": True, "result": {"complete": False}} # No 'id'
)
result = client.run_query({"calculations": []})
assert result["success"] is False
assert "no result ID" in result["error"]
def test_run_query_polling_fetch_failure(client: HoneycombClient) -> None:
client.create_query = MagicMock(return_value={"success": True, "query_id": "q1"})
client.create_query_result = MagicMock(
return_value={"success": True, "result": {"id": "r1", "complete": False}}
)
# Fail on the first poll
client.get_query_result = MagicMock(return_value={"success": False, "error": "Fetch failed"})
with patch("time.sleep"):
result = client.run_query({"calculations": []}, poll_attempts=1)
assert result["success"] is False
assert result["error"] == "Fetch failed"