mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
* test(e2e): harden stage flakes for batches, UI, and MCP Unique batch model names avoid load-balancing onto stale azure-batch deployments that still pointed at the retired gpt-4.1-mini-batch, which only the managed/unified path was hitting. Retry batch retrieve on 500 and /ui/api-keys navigation on ERR_ABORTED. Skip the MCP key-access suite when the compose-only mcp-upstream is unreachable on stage k8s * test(e2e): cover Datadog remote MCP via search_datadog_logs Register the regional Datadog MCP endpoint with DD-API-KEY / DD-APPLICATION-KEY static headers (CI-safe header auth; browser OAuth is not headless-automatable). Seed a chat completion marked e2e-datadog-mcp-*, assert the proxy shipped it, list tools, call search_datadog_logs for the marker, and delete the server on teardown. Math-upstream key-access tests only skip when that compose service is unreachable * test(e2e): drop compose math MCP upstream; use Datadog only Key-access denial and happy-path MCP e2e both register the real regional Datadog remote MCP server with DD-API-KEY / DD-APPLICATION-KEY headers. Remove the mcp-upstream compose service and FastMCP add/multiply fixture * docs(e2e): require real Datadog MCP for all mcp suite tests Document that tests/e2e/mcp must register via datadog_mcp helpers against mcp.<site>/v1/mcp and must not introduce compose or fake MCP upstreams * chore: restore mcp_e2e_upstream_server.py Keep the FastMCP fixture file; e2e no longer wires it in compose, but the module itself is not part of the Datadog-only cleanup * fix(e2e): load tests/e2e/.env and fix datadog_reader importlib load pytest on the host never inherited compose env_file keys, so DD_API_KEY stayed empty. load_dotenv tests/e2e/.env in e2e_config. Register the dynamically loaded datadog_reader module in sys.modules so dataclasses do not crash under Python 3.12 * test(e2e/batches): harden azure/vertex unified lifecycle flakes Put the provider deployment name in every JSONL body so Azure does not depend on a perfect model rewrite. Retry create/retrieve/cancel on transient statuses with backoff. Drop cancel assertions for azure and vertex (registry only has a shared basic cell; create+retrieve prove routing, cancel stays best-effort cleanup) * test(e2e/ui): treat api-keys shell as success after SPA ERR_ABORTED Post-login client redirects abort the first /ui/api-keys/ goto on stage. Wait off /ui/login after cookie set, then accept the page once Create New Key is visible even if goto raised ERR_ABORTED * test(e2e): drop flaky key models dropdown Playwright suite API management e2e already covers key generate/update persistence. The UI Models-dropdown sentinel cases only added SPA ERR_ABORTED noise and no unique product signal. Remove the suite and unused browser fixtures
108 lines
4 KiB
Python
108 lines
4 KiB
Python
"""Live e2e: the proxy brokers the real Datadog remote MCP server.
|
|
|
|
Seeds a chat completion whose prompt carries a unique `e2e-datadog-mcp-*`
|
|
marker so the proxy's DataDogLogger ships a StandardLoggingPayload the org can
|
|
search. Registers the regional Datadog MCP endpoint with DD_API_KEY /
|
|
DD_APP_KEY as static headers (Datadog's documented CI/header auth). A key
|
|
granted that server lists tools, calls search_datadog_logs for the marker, and
|
|
the response must contain it. The dual read via datadog_reader proves the log
|
|
is also in the Logs Search API. The MCP server row is deleted on teardown.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from conftest import DdLogsReader
|
|
from datadog_mcp import SEARCH_LOGS_TOOL, assert_dd_mcp_creds, register_datadog_mcp
|
|
from e2e_config import CHEAP_ANTHROPIC_MODEL, DD_SEARCH_FROM, unique_marker
|
|
from e2e_http import NoBody, unwrap
|
|
from lifecycle import ResourceManager
|
|
from mcp_client import McpClient
|
|
from models import ChatBody, ChatMessage
|
|
from proxy_client import ProxyClient
|
|
|
|
pytestmark = pytest.mark.e2e
|
|
|
|
DD_LOGGER_NAME = "DataDogLogger"
|
|
MARKER_PREFIX = "e2e-datadog-mcp-"
|
|
|
|
|
|
def _assert_datadog_logger_active(proxy: ProxyClient) -> None:
|
|
result = proxy.probe("/health/readiness/details", params=NoBody())
|
|
assert result.status_code == 200, (
|
|
f"/health/readiness/details must answer 200, got {result.status_code}: {result.body[:300]}"
|
|
)
|
|
assert DD_LOGGER_NAME in result.body, (
|
|
f"the proxy must report the {DD_LOGGER_NAME} callback active "
|
|
f"(callbacks + DD_* env); got: {result.body[:400]}"
|
|
)
|
|
|
|
|
|
def _seed_completion(proxy: ProxyClient, *, key: str, marker: str) -> None:
|
|
body = ChatBody(
|
|
model=CHEAP_ANTHROPIC_MODEL,
|
|
messages=[ChatMessage(role="user", content=f"reply with one word {marker}")],
|
|
max_tokens=16,
|
|
)
|
|
unwrap(proxy.chat(key, body))
|
|
|
|
|
|
class TestDatadogMcpRoundTrip:
|
|
@pytest.mark.covers("mcp.list_tools.api_key.succeeds", "mcp.call_tool.api_key.succeeds")
|
|
def test_search_logs_finds_seeded_completion(
|
|
self,
|
|
client: McpClient,
|
|
dd_logs: DdLogsReader,
|
|
resources: ResourceManager,
|
|
) -> None:
|
|
assert_dd_mcp_creds()
|
|
_assert_datadog_logger_active(client.proxy)
|
|
|
|
server_id = register_datadog_mcp(client, resources)
|
|
marker = f"{MARKER_PREFIX}{unique_marker()}"
|
|
|
|
key = client.generate_key(
|
|
user_id=f"e2e-dd-mcp-{unique_marker()}",
|
|
mcp_servers=[server_id],
|
|
models=[CHEAP_ANTHROPIC_MODEL],
|
|
)
|
|
resources.defer(lambda: client.proxy.delete_key(key))
|
|
|
|
_seed_completion(client.proxy, key=key, marker=marker)
|
|
|
|
shipped = dd_logs.poll_events_for_marker(marker)
|
|
assert shipped, (
|
|
f"proxy DataDogLogger never shipped a log containing {marker!r} "
|
|
"within the poll deadline; MCP search would have nothing to find"
|
|
)
|
|
|
|
tools = unwrap(client.list_tools(key))
|
|
tool_name = tools.tool_name_containing(server_id, SEARCH_LOGS_TOOL)
|
|
assert tool_name is not None, (
|
|
f"granted key never saw {SEARCH_LOGS_TOOL} on server {server_id}; "
|
|
f"tools={tools.tool_names_for_server(server_id)}"
|
|
)
|
|
|
|
call = unwrap(
|
|
client.call_tool(
|
|
key,
|
|
server_id=server_id,
|
|
name=tool_name,
|
|
arguments={
|
|
"query": marker,
|
|
"from": DD_SEARCH_FROM,
|
|
"to": "now",
|
|
"max_tokens": 5000,
|
|
"telemetry": {
|
|
"intent": "e2e assert seeded litellm completion log is searchable via MCP"
|
|
},
|
|
},
|
|
)
|
|
)
|
|
assert call.is_error is not True, f"search_datadog_logs errored: {call}"
|
|
body = call.all_text
|
|
assert marker in body, (
|
|
f"search_datadog_logs response must include the seeded marker {marker!r}; "
|
|
f"got: {body[:800]!r}"
|
|
)
|