Files
tracer-cloud--opensre/tests/tools/test_opensearch_analytics_tool.py
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

357 lines
12 KiB
Python

"""Dedicated unit tests for OpenSearchAnalyticsTool.
Covers contract metadata, source-availability gating, parameter extraction,
client config normalization, bounded result limits, log filtering/normalization
on the tool side (non-dict items dropped), and propagation of client errors.
"""
from __future__ import annotations
from collections.abc import Callable
from typing import Any
import pytest
from integrations.elasticsearch.client import ElasticsearchConfig
from integrations.opensearch.tools.opensearch_analytics_tool import query_opensearch_analytics
from tests.tools.conftest import BaseToolContract
# ---------------------------------------------------------------------------
# Test helpers — keep ElasticsearchClient stubbing consistent
# ---------------------------------------------------------------------------
def _install_es_stubs(
monkeypatch: pytest.MonkeyPatch,
search_impl: Callable[..., dict[str, Any]],
) -> dict[str, Any]:
"""Patch ``ElasticsearchClient.__init__`` AND ``search_logs`` together.
Returns a dict that captures the ``ElasticsearchConfig`` the tool builds,
accessible as ``captured["config"]`` after the tool is invoked. Patching
both methods keeps every test isolated from the real client even if
``__init__`` ever stops being lazy (e.g. starts validating URL reachability).
"""
captured: dict[str, Any] = {}
def _fake_init(self: Any, config: ElasticsearchConfig) -> None:
captured["config"] = config
self.config = config
monkeypatch.setattr(
"integrations.opensearch.tools.opensearch_analytics_tool.ElasticsearchClient.__init__",
_fake_init,
)
monkeypatch.setattr(
"integrations.opensearch.tools.opensearch_analytics_tool.ElasticsearchClient.search_logs",
search_impl,
)
return captured
def _ok_search(_self: Any, **_kwargs: Any) -> dict[str, Any]:
"""Default ``search_logs`` stub — succeeds with an empty result set."""
return {"success": True, "logs": []}
# ---------------------------------------------------------------------------
# Contract — metadata, is_available, extract_params surface
# ---------------------------------------------------------------------------
class TestOpenSearchAnalyticsToolContract(BaseToolContract):
def get_tool_under_test(self) -> Any:
return query_opensearch_analytics.__opensre_registered_tool__
def _rt() -> Any:
return query_opensearch_analytics.__opensre_registered_tool__
def test_is_available_true_when_url_and_verified() -> None:
sources = {
"opensearch": {
"connection_verified": True,
"url": "https://os.example.invalid",
}
}
assert _rt().is_available(sources) is True
def test_is_available_false_when_connection_not_verified() -> None:
sources = {
"opensearch": {
"connection_verified": False,
"url": "https://os.example.invalid",
}
}
assert _rt().is_available(sources) is False
def test_is_available_false_when_url_missing() -> None:
sources = {"opensearch": {"connection_verified": True}}
assert _rt().is_available(sources) is False
def test_is_available_false_when_no_opensearch_source() -> None:
assert _rt().is_available({}) is False
def test_extract_params_strips_and_uses_defaults() -> None:
sources = {
"opensearch": {
"url": " https://os.example.invalid ",
"api_key": " os-key ",
"index_pattern": " logs-* ",
"default_query": " service:foo ",
"integration_id": " os-1 ",
}
}
params = _rt().extract_params(sources)
assert params["url"] == "https://os.example.invalid"
assert params["api_key"] == "os-key"
assert params["index_pattern"] == "logs-*"
assert params["query"] == "service:foo"
assert params["time_range_minutes"] == 60 # default
assert params["limit"] == 50
assert params["max_results"] == 100 # _DEFAULT_MAX_RESULTS
assert params["integration_id"] == "os-1"
def test_extract_params_falls_back_to_star_for_blank_index_and_query() -> None:
sources = {
"opensearch": {
"url": "https://os.example.invalid",
"index_pattern": " ",
"default_query": "",
}
}
params = _rt().extract_params(sources)
assert params["index_pattern"] == "*"
assert params["query"] == "*"
def test_extract_params_uses_default_max_results_when_zero() -> None:
sources = {"opensearch": {"url": "https://os.example.invalid", "max_results": 0}}
assert _rt().extract_params(sources)["max_results"] == 100
# ---------------------------------------------------------------------------
# Validation guards — missing config
# ---------------------------------------------------------------------------
def test_returns_missing_url_when_url_blank() -> None:
result = query_opensearch_analytics(url=" ")
assert result["available"] is False
assert result["error"] == "Missing OpenSearch URL."
assert result["logs"] == []
# ---------------------------------------------------------------------------
# Client config normalization — what gets passed to ElasticsearchClient
# ---------------------------------------------------------------------------
def test_client_config_normalizes_url_api_key_and_index(
monkeypatch: pytest.MonkeyPatch,
) -> None:
captured = _install_es_stubs(monkeypatch, _ok_search)
query_opensearch_analytics(
url=" https://os.example.invalid/ ", # trailing slash + spaces stripped
api_key=" os-key ",
index_pattern="logs-prod-*",
)
cfg = captured["config"]
assert isinstance(cfg, ElasticsearchConfig)
assert cfg.url == "https://os.example.invalid"
assert cfg.api_key == "os-key"
assert cfg.index_pattern == "logs-prod-*"
def test_client_config_treats_blank_api_key_as_none(monkeypatch: pytest.MonkeyPatch) -> None:
captured = _install_es_stubs(monkeypatch, _ok_search)
query_opensearch_analytics(
url="https://os.example.invalid",
api_key=" ",
index_pattern="*",
)
assert captured["config"].api_key is None
def test_blank_index_pattern_becomes_star(monkeypatch: pytest.MonkeyPatch) -> None:
search_kwargs: dict[str, Any] = {}
def _impl(_self: Any, **kwargs: Any) -> dict[str, Any]:
search_kwargs.update(kwargs)
return {"success": True, "logs": []}
captured = _install_es_stubs(monkeypatch, _impl)
result = query_opensearch_analytics(url="https://os.example.invalid", index_pattern="")
assert captured["config"].index_pattern == "*"
assert search_kwargs["index_pattern"] == "*"
assert result["index_pattern"] == "*"
# ---------------------------------------------------------------------------
# Search call shape — query, time range, bounded limit
# ---------------------------------------------------------------------------
def test_search_query_defaults_to_star_when_blank(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
def _impl(_self: Any, **kwargs: Any) -> dict[str, Any]:
captured.update(kwargs)
return {"success": True, "logs": []}
_install_es_stubs(monkeypatch, _impl)
result = query_opensearch_analytics(
url="https://os.example.invalid",
query="",
)
assert captured["query"] == "*"
assert result["query"] == "*"
def test_time_range_minutes_floor_one(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
def _impl(_self: Any, **kwargs: Any) -> dict[str, Any]:
captured.update(kwargs)
return {"success": True, "logs": []}
_install_es_stubs(monkeypatch, _impl)
query_opensearch_analytics(
url="https://os.example.invalid",
time_range_minutes=0,
)
# max(1, time_range_minutes) — never zero or negative
assert captured["time_range_minutes"] == 1
def test_bounded_limit_caps_caller_request(monkeypatch: pytest.MonkeyPatch) -> None:
captured: dict[str, Any] = {}
def _impl(_self: Any, **kwargs: Any) -> dict[str, Any]:
captured["limit"] = kwargs["limit"]
# Server returns more than the cap — tool must trim to effective_limit
return {
"success": True,
"logs": [{"message": f"log-{idx}"} for idx in range(20)],
}
_install_es_stubs(monkeypatch, _impl)
result = query_opensearch_analytics(
url="https://os.example.invalid",
limit=999,
max_results=5,
)
assert captured["limit"] == 5
assert len(result["logs"]) == 5
assert result["total_returned"] == 5
def test_bounded_limit_capped_by_hard_max(monkeypatch: pytest.MonkeyPatch) -> None:
"""Caller may not request more than _MAX_HARD_LIMIT (200), even via max_results."""
captured: dict[str, Any] = {}
def _impl(_self: Any, **kwargs: Any) -> dict[str, Any]:
captured["limit"] = kwargs["limit"]
return {"success": True, "logs": []}
_install_es_stubs(monkeypatch, _impl)
query_opensearch_analytics(
url="https://os.example.invalid",
limit=10_000,
max_results=10_000,
)
assert captured["limit"] == 200
# ---------------------------------------------------------------------------
# Response normalization — non-dict log entries are dropped
# ---------------------------------------------------------------------------
def test_filters_non_dict_log_entries(monkeypatch: pytest.MonkeyPatch) -> None:
def _impl(_self: Any, **_kwargs: Any) -> dict[str, Any]:
return {
"success": True,
"logs": [
{"message": "ok"},
"garbage-string",
{"message": "ok2"},
None,
42,
],
}
_install_es_stubs(monkeypatch, _impl)
result = query_opensearch_analytics(
url="https://os.example.invalid",
max_results=10,
)
assert result["available"] is True
assert result["logs"] == [{"message": "ok"}, {"message": "ok2"}]
assert result["total_returned"] == 2
def test_handles_missing_logs_field(monkeypatch: pytest.MonkeyPatch) -> None:
def _impl(_self: Any, **_kwargs: Any) -> dict[str, Any]:
return {"success": True} # no 'logs' key at all
_install_es_stubs(monkeypatch, _impl)
result = query_opensearch_analytics(url="https://os.example.invalid")
assert result["available"] is True
assert result["logs"] == []
def test_handles_non_list_logs_field(monkeypatch: pytest.MonkeyPatch) -> None:
def _impl(_self: Any, **_kwargs: Any) -> dict[str, Any]:
return {"success": True, "logs": "should-be-a-list-but-isnt"}
_install_es_stubs(monkeypatch, _impl)
result = query_opensearch_analytics(url="https://os.example.invalid")
assert result["available"] is True
assert result["logs"] == []
# ---------------------------------------------------------------------------
# Client error propagation
# ---------------------------------------------------------------------------
def test_propagates_client_failure(monkeypatch: pytest.MonkeyPatch) -> None:
def _impl(_self: Any, **_kwargs: Any) -> dict[str, Any]:
return {"success": False, "error": "auth failed"}
_install_es_stubs(monkeypatch, _impl)
result = query_opensearch_analytics(url="https://os.example.invalid")
assert result["available"] is False
assert "auth failed" in result["error"]
assert result["logs"] == []
def test_propagates_unknown_client_error(monkeypatch: pytest.MonkeyPatch) -> None:
def _impl(_self: Any, **_kwargs: Any) -> dict[str, Any]:
return {"success": False} # no 'error' field
_install_es_stubs(monkeypatch, _impl)
result = query_opensearch_analytics(url="https://os.example.invalid")
assert result["available"] is False
# Tool falls back to a generic message rather than raising
assert "Unknown OpenSearch error" in result["error"]