Files
nexus-claude-api/tests/test_routes.py
Guangfei.Zhao 0e98ce57d4 Refactor models and logging for nexus-claude-api
- Update default models in config to use `claude-opus-4.6`.
- Introduce logging configuration to write logs to a file.
- Add correlation ID to error responses for better traceability.
- Implement diagnostics for summarizing message requests.
- Normalize legacy system messages in the API.
- Enhance tests to cover new logging and error handling features.
- Update README and documentation to reflect changes in model defaults and logging behavior.
2026-06-26 22:36:09 +08:00

197 lines
5.9 KiB
Python

from __future__ import annotations
import logging
from botocore.exceptions import ClientError
from fastapi.testclient import TestClient
from nexus_claude_api.config import Settings
from nexus_claude_api.nexus_client import _map_client_error
from nexus_claude_api.server import create_app
class FakeNexusClient:
def converse(self, request: dict, *, correlation_id: str | None = None) -> dict:
assert correlation_id
assert request["messages"][0]["content"][0]["text"] == "Hello"
return {
"ResponseMetadata": {"RequestId": "req-route"},
"output": {"message": {"content": [{"text": "Hi"}]}},
"stopReason": "end_turn",
"usage": {"inputTokens": 3, "outputTokens": 1},
}
def converse_stream(self, request: dict, *, correlation_id: str | None = None):
assert correlation_id
assert request["messages"][0]["content"][0]["text"] == "Hello"
return [
{"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": "Hi"}}},
{"contentBlockStop": {"contentBlockIndex": 0}},
{"messageStop": {"stopReason": "end_turn"}},
]
class AccessDeniedNexusClient:
def converse(self, request: dict, *, correlation_id: str | None = None) -> dict:
raise _map_client_error(
ClientError(
{
"Error": {
"Code": "AccessDeniedException",
"Message": "not allowed",
},
"ResponseMetadata": {
"HTTPStatusCode": 403,
"RequestId": "req-denied",
},
},
"Converse",
),
operation="converse",
correlation_id=correlation_id,
)
def client(nexus_client: object | None = None) -> TestClient:
settings = Settings.from_values(api_key="test", require_api_key=False)
return TestClient(
create_app(settings=settings, nexus_client=nexus_client or FakeNexusClient())
)
def test_health() -> None:
response = client().get("/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
def test_root_head() -> None:
response = client().head("/")
assert response.status_code == 200
assert response.content == b""
def test_models() -> None:
response = client().get("/v1/models")
assert response.status_code == 200
data = response.json()["data"]
assert [model["id"] for model in data] == ["claude-opus-4.6"]
def test_messages_non_stream() -> None:
response = client().post(
"/v1/messages",
json={
"model": "claude-opus-4.6",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 32,
},
)
assert response.status_code == 200
body = response.json()
assert body["id"] == "req-route"
assert body["content"][0]["text"] == "Hi"
def test_messages_normalizes_legacy_system_role() -> None:
response = client().post(
"/v1/messages",
json={
"model": "claude-opus-4.6",
"messages": [
{"role": "system", "content": "You are helpful."},
{"role": "user", "content": "Hello"},
],
"max_tokens": 32,
},
)
assert response.status_code == 200
assert response.json()["content"][0]["text"] == "Hi"
def test_messages_upstream_error_returns_correlation_id() -> None:
response = client(AccessDeniedNexusClient()).post(
"/v1/messages",
json={
"model": "claude-opus-4.6",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 32,
},
)
assert response.status_code == 403
error = response.json()["error"]
assert error["type"] == "api_error"
assert error["correlation_id"]
assert f"correlation_id={error['correlation_id']}" in error["message"]
def test_client_error_mapping_logs_sanitized_details(caplog) -> None:
caplog.set_level(logging.WARNING, logger="nexus_claude_api.nexus_client")
mapped = _map_client_error(
ClientError(
{
"Error": {
"Code": "AccessDeniedException",
"Message": "model access denied",
},
"ResponseMetadata": {
"HTTPStatusCode": 400,
"RequestId": "req-log",
},
},
"Converse",
),
operation="converse",
correlation_id="corr-log",
)
assert mapped.status_code == 403
assert mapped.error_type == "api_error"
record = caplog.records[-1]
assert record.levelno == logging.WARNING
assert record.correlation_id == "corr-log"
assert record.operation == "converse"
assert record.nexus_error_code == "AccessDeniedException"
assert record.http_status == 403
assert record.nexus_request_id == "req-log"
log_text = caplog.text
assert "api_key" not in log_text
assert "Authorization" not in log_text
assert "aW1hZ2UtYnl0ZXM=" not in log_text
def test_messages_stream() -> None:
with client().stream(
"POST",
"/v1/messages",
json={
"model": "claude-opus-4.6",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 32,
"stream": True,
},
) as response:
body = response.read().decode("utf-8")
assert response.status_code == 200
assert "event: message_start" in body
assert "event: content_block_delta" in body
assert "event: message_stop" in body
def test_count_tokens() -> None:
response = client().post(
"/v1/messages/count_tokens",
json={
"model": "claude-opus-4.6",
"messages": [{"role": "user", "content": "Hello"}],
},
)
assert response.status_code == 200
assert response.json()["input_tokens"] > 0