feat: add capability and admin introspection endpoints
Expose capability discovery plus admin-only config and request inspection endpoints so clients and operators can understand gateway behavior without reading code. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,37 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from fastapi import HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
from starlette.requests import Request
|
||||
|
||||
from app.auth import AnthropicAuthError, require_anthropic_key, require_bearer, require_metrics_access
|
||||
from app.concurrency import BackpressureRejected, InFlightGuard
|
||||
|
||||
_playwright = types.ModuleType("playwright")
|
||||
_playwright_async = types.ModuleType("playwright.async_api")
|
||||
|
||||
|
||||
class _StubPlaywrightTimeoutError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
async def _stub_async_playwright():
|
||||
raise RuntimeError("playwright is stubbed in unit tests")
|
||||
|
||||
|
||||
_playwright_async.TimeoutError = _StubPlaywrightTimeoutError
|
||||
_playwright_async.async_playwright = _stub_async_playwright
|
||||
sys.modules.setdefault("playwright", _playwright)
|
||||
sys.modules.setdefault("playwright.async_api", _playwright_async)
|
||||
|
||||
import app.main as main
|
||||
|
||||
|
||||
def _req(headers: dict[str, str] | None = None) -> Request:
|
||||
pairs = []
|
||||
@@ -82,5 +105,48 @@ class AuthAndConcurrencyTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(guard.in_flight, 0)
|
||||
|
||||
|
||||
class DebugRequestRecordingTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
main._DEBUG_REQUEST_LOG.clear()
|
||||
|
||||
def test_redacts_sensitive_fields_and_data_urls(self) -> None:
|
||||
body = {
|
||||
"authorization": "Bearer abc",
|
||||
"x-api-key": "secret",
|
||||
"session_bundle": "very-secret",
|
||||
"images": ["data:image/png;base64,ABC"],
|
||||
"tool": {"args": "x" * 3000},
|
||||
}
|
||||
redacted = main._redact_debug_value((), body)
|
||||
|
||||
self.assertEqual(redacted["authorization"], "***")
|
||||
self.assertEqual(redacted["x-api-key"], "***")
|
||||
self.assertEqual(redacted["session_bundle"], "***")
|
||||
self.assertEqual(redacted["images"][0], "[redacted-data-url]")
|
||||
self.assertIn("[truncated]", redacted["tool"]["args"])
|
||||
|
||||
def test_internal_debug_requests_requires_admin_and_returns_items(self) -> None:
|
||||
with patch.object(main.settings, "api_keys", ["k1"]), patch.object(main.settings, "admin_token", "admin-1"):
|
||||
client = TestClient(main.app)
|
||||
req_payload = {
|
||||
"model": "org_auto",
|
||||
"messages": [{"role": "user", "content": "hello"}],
|
||||
}
|
||||
main._record_debug_request("openai", "/v1/chat/completions", req_payload, _req({"x-request-id": "req-1"}))
|
||||
|
||||
denied = client.get("/internal/debug/requests")
|
||||
self.assertEqual(denied.status_code, 401)
|
||||
|
||||
ok = client.get(
|
||||
"/internal/debug/requests?limit=1",
|
||||
headers={"Authorization": "Bearer admin-1"},
|
||||
)
|
||||
self.assertEqual(ok.status_code, 200)
|
||||
data = ok.json()
|
||||
self.assertTrue(data["ok"])
|
||||
self.assertEqual(data["count"], 1)
|
||||
self.assertEqual(data["items"][0]["protocol"], "openai")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user