| import asyncio |
| from unittest.mock import patch |
|
|
| from fastapi.responses import JSONResponse |
| from starlette.routing import Route |
|
|
| from app.core import logs |
|
|
|
|
| def test_non_streaming_request_is_logged_immediately( |
| api_client, |
| configured_api_key, |
| reset_logs, |
| ): |
| response = api_client.get( |
| "/v1/models", |
| headers={"Authorization": f"Bearer {configured_api_key.key}"}, |
| ) |
|
|
| assert response.status_code == 200 |
| assert response.headers["x-request-id"] |
| recent_logs = logs.get_recent_logs() |
| assert len(recent_logs) == 1 |
| assert recent_logs[0].api_key_name == "Test key" |
| assert recent_logs[0].status == "success" |
| assert recent_logs[0].is_stream is False |
| assert recent_logs[0].request_id == response.headers["x-request-id"] |
| assert recent_logs[0].method == "GET" |
| assert recent_logs[0].path == "/v1/models" |
| assert recent_logs[0].request_headers["authorization"] == "[redacted]" |
| assert recent_logs[0].response_body == "" |
|
|
|
|
| def test_streaming_request_duration_includes_body_iteration( |
| api_client, |
| configured_api_key, |
| reset_logs, |
| ): |
| async def slow_stream(**_kwargs): |
| await asyncio.sleep(0.05) |
| yield 'data: {"choices":[{"delta":{"reasoning_content":"想一下"}}]}\n\n' |
| yield 'data: {"choices":[{"delta":{"content":"好了"}}]}\n\n' |
| yield "data: [DONE]\n\n" |
|
|
| with patch("app.api.routes._create_streaming_chat_response", slow_stream): |
| with api_client.stream( |
| "POST", |
| "/v1/chat/completions", |
| headers={"Authorization": f"Bearer {configured_api_key.key}"}, |
| json={ |
| "model": "kimi-k2.6-thinking", |
| "stream": True, |
| "messages": [{"role": "user", "content": "hi"}], |
| }, |
| ) as response: |
| body = "".join(response.iter_text()) |
|
|
| assert response.status_code == 200 |
| assert "data: [DONE]" in body |
|
|
| recent_logs = logs.get_recent_logs() |
| assert len(recent_logs) == 1 |
| assert recent_logs[0].is_stream is True |
| assert recent_logs[0].duration_ms >= 50 |
| detail = logs.get_log(recent_logs[0].request_id) |
| assert detail.raw_stream_body == "" |
| assert detail.response_body == "" |
| assert detail.parsed_response_text == "好了" |
| assert detail.parsed_reasoning_content == "想一下" |
|
|
|
|
| def test_request_logging_captures_upstream_error_metadata( |
| api_client, |
| configured_api_key, |
| reset_logs, |
| ): |
| async def upstream_error(_request): |
| return JSONResponse( |
| {"error": {"message": "upstream rate limited"}}, |
| status_code=502, |
| headers={ |
| "X-Kimi-Upstream-Status": "429", |
| "X-Kimi-Upstream-Error-Type": "rate_limited", |
| "X-Kimi-Upstream-Retry-After": "1.5", |
| }, |
| ) |
|
|
| api_client.app.router.routes.insert( |
| 0, |
| Route("/v1/upstream-error-test", upstream_error, methods=["GET"]), |
| ) |
|
|
| response = api_client.get( |
| "/v1/upstream-error-test", |
| headers={"Authorization": f"Bearer {configured_api_key.key}"}, |
| ) |
|
|
| assert response.status_code == 502 |
| detail = logs.get_recent_logs()[0] |
| assert detail.upstream_status_code == 429 |
| assert detail.upstream_error_type == "rate_limited" |
| assert detail.upstream_retry_after == 1.5 |
|
|
|
|
| def test_request_logging_captures_selected_kimi_account( |
| api_client, |
| configured_api_key, |
| reset_logs, |
| ): |
| async def account_marker(request): |
| request.state.kimi_account_id = "acc-1" |
| request.state.kimi_account_name = "Work" |
| return JSONResponse({"ok": True}) |
|
|
| api_client.app.router.routes.insert( |
| 0, |
| Route("/v1/account-marker-test", account_marker, methods=["GET"]), |
| ) |
|
|
| response = api_client.get( |
| "/v1/account-marker-test", |
| headers={"Authorization": f"Bearer {configured_api_key.key}"}, |
| ) |
|
|
| assert response.status_code == 200 |
| detail = logs.get_recent_logs()[0] |
| assert detail.kimi_account_id == "acc-1" |
| assert detail.kimi_account_name == "Work" |
|
|