Files
alishahryar1--free-claude-code/tests/api/test_execution_failure_contract.py
wehub-resource-sync 5296d0e97c
CI / Ban suppressions and legacy annotations (push) Has been cancelled
CI / pytest (push) Has been cancelled
CI / ruff-check (push) Has been cancelled
CI / ruff-format (push) Has been cancelled
CI / ty (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:44 +08:00

422 lines
14 KiB
Python

"""Public commit-boundary behavior for canonical execution failures."""
from collections.abc import AsyncIterator
from typing import Any
from unittest.mock import MagicMock, patch
import pytest
from fastapi.testclient import TestClient
from free_claude_code.core.anthropic.stream_contracts import parse_sse_text
from free_claude_code.core.anthropic.streaming import format_sse_event
from free_claude_code.core.failures import ExecutionFailure, FailureKind
from tests.api.support import create_test_app
_PARTIAL_CONTENT = "PARTIAL_ASSISTANT_CONTENT"
class CanonicalFailureProvider:
"""Provider double that raises one request-correlated canonical failure."""
def __init__(
self,
chunks: list[str],
*,
kind: FailureKind,
status_code: int,
message: str,
retryable: bool,
grouped: bool = False,
) -> None:
self._chunks = chunks
self._kind = kind
self._status_code = status_code
self._message = message
self._retryable = retryable
self._grouped = grouped
self.preflight_stream = MagicMock()
self.stream_kwargs: list[dict[str, Any]] = []
async def stream_response(
self,
_request: object,
**kwargs: Any,
) -> AsyncIterator[str]:
self.stream_kwargs.append(kwargs)
for chunk in self._chunks:
yield chunk
request_id = kwargs["request_id"]
failure = ExecutionFailure(
kind=self._kind,
status_code=self._status_code,
message=f"{self._message}\n\nRequest ID: {request_id}",
retryable=self._retryable,
)
if self._grouped:
raise ExceptionGroup(
"provider stream and cleanup failed",
[
RuntimeError("cleanup failed"),
ExceptionGroup("provider request failed", [failure]),
],
)
raise failure
def _messages_payload(*, stream: bool) -> dict[str, object]:
return {
"model": "nvidia_nim/test-model",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 32,
"stream": stream,
}
def _responses_payload() -> dict[str, object]:
return {
"model": "nvidia_nim/test-model",
"input": "Hello",
"max_output_tokens": 32,
}
def _partial_anthropic_stream(*, close_block: bool) -> list[str]:
chunks = [
format_sse_event("message_start", {"type": "message_start", "message": {}}),
format_sse_event(
"content_block_start",
{
"type": "content_block_start",
"index": 0,
"content_block": {"type": "text", "text": ""},
},
),
format_sse_event(
"content_block_delta",
{
"type": "content_block_delta",
"index": 0,
"delta": {"type": "text_delta", "text": _PARTIAL_CONTENT},
},
),
]
if close_block:
chunks.append(
format_sse_event(
"content_block_stop",
{"type": "content_block_stop", "index": 0},
)
)
return chunks
def _client_for(provider: CanonicalFailureProvider):
app = create_test_app()
return (
patch("free_claude_code.api.routes.resolve_provider", return_value=provider),
TestClient(app),
)
def _terminal_trace(trace_mock: MagicMock) -> dict[str, Any]:
return dict(
next(
call.kwargs
for call in trace_mock.call_args_list
if call.kwargs.get("event")
== "free_claude_code.api.response.terminal_execution_error"
)
)
def _grouped_rate_limit_provider(chunks: list[str]) -> CanonicalFailureProvider:
return CanonicalFailureProvider(
chunks,
kind=FailureKind.RATE_LIMIT,
status_code=429,
message="upstream is busy",
retryable=True,
grouped=True,
)
@pytest.mark.parametrize(
("path", "payload", "expected_type"),
[
("/v1/messages", _messages_payload(stream=True), "rate_limit_error"),
("/v1/responses", _responses_payload(), "rate_limit_error"),
],
)
def test_grouped_pre_start_execution_failure_keeps_canonical_wire_error(
path: str,
payload: dict[str, object],
expected_type: str,
) -> None:
provider = _grouped_rate_limit_provider([])
resolver_patch, client = _client_for(provider)
with (
resolver_patch,
patch("free_claude_code.api.response_streams.trace_event") as trace_mock,
client,
):
response = client.post(path, json=payload)
request_id = response.headers["request-id"]
assert response.status_code == 429
assert response.headers["x-should-retry"] == "false"
error = response.json()["error"]
assert error["type"] == expected_type
assert error["message"] == f"upstream is busy\n\nRequest ID: {request_id}"
trace = _terminal_trace(trace_mock)
assert trace["status_code"] == 429
assert trace["error_type"] == "rate_limit_error"
assert trace["exc_type"] == "ExecutionFailure"
assert trace["failure_kind"] == "rate_limit"
@pytest.mark.parametrize("path", ["/v1/messages", "/v1/responses"])
def test_grouped_post_start_execution_failure_keeps_canonical_terminal_event(
path: str,
) -> None:
provider = _grouped_rate_limit_provider(_partial_anthropic_stream(close_block=True))
payload = (
_messages_payload(stream=True)
if path == "/v1/messages"
else _responses_payload()
)
resolver_patch, client = _client_for(provider)
with (
resolver_patch,
patch("free_claude_code.api.response_streams.trace_event") as trace_mock,
client,
):
response = client.post(path, json=payload)
request_id = response.headers["request-id"]
events = parse_sse_text(response.text)
if path == "/v1/messages":
assert events[-1].event == "error"
error = events[-1].data["error"]
else:
assert events[-1].event == "response.failed"
error = events[-1].data["response"]["error"]
assert response.status_code == 200
assert error["type"] == "rate_limit_error"
assert error["message"] == f"upstream is busy\n\nRequest ID: {request_id}"
assert _terminal_trace(trace_mock)["failure_kind"] == "rate_limit"
def test_grouped_stream_false_execution_failure_discards_partial_content() -> None:
provider = _grouped_rate_limit_provider(
_partial_anthropic_stream(close_block=False)
)
resolver_patch, client = _client_for(provider)
with (
resolver_patch,
patch("free_claude_code.api.response_streams.trace_event") as trace_mock,
client,
):
response = client.post("/v1/messages", json=_messages_payload(stream=False))
request_id = response.headers["request-id"]
assert response.status_code == 429
assert response.headers["x-should-retry"] == "false"
assert response.json()["error"] == {
"type": "rate_limit_error",
"message": f"upstream is busy\n\nRequest ID: {request_id}",
}
assert _PARTIAL_CONTENT not in response.text
trace = _terminal_trace(trace_mock)
assert trace["status_code"] == 429
assert trace["error_type"] == "rate_limit_error"
assert trace["exc_type"] == "ExecutionFailure"
assert trace["failure_kind"] == "rate_limit"
def test_messages_pre_start_execution_failure_is_correlated_terminal_json() -> None:
provider = CanonicalFailureProvider(
[],
kind=FailureKind.RATE_LIMIT,
status_code=429,
message="upstream is busy",
retryable=True,
)
resolver_patch, client = _client_for(provider)
with resolver_patch, client:
response = client.post("/v1/messages", json=_messages_payload(stream=True))
request_id = response.headers["request-id"]
assert response.status_code == 429
assert response.headers["content-type"].startswith("application/json")
assert response.headers["x-should-retry"] == "false"
assert "x-request-id" not in response.headers
assert response.json() == {
"type": "error",
"error": {
"type": "rate_limit_error",
"message": f"upstream is busy\n\nRequest ID: {request_id}",
},
"request_id": request_id,
}
assert provider.stream_kwargs[0]["request_id"] == request_id
def test_responses_pre_start_execution_failure_is_correlated_terminal_json() -> None:
provider = CanonicalFailureProvider(
[],
kind=FailureKind.OVERLOADED,
status_code=529,
message="provider overloaded",
retryable=True,
)
resolver_patch, client = _client_for(provider)
with resolver_patch, client:
response = client.post("/v1/responses", json=_responses_payload())
request_id = response.headers["request-id"]
assert response.status_code == 529
assert response.headers["content-type"].startswith("application/json")
assert response.headers["x-should-retry"] == "false"
assert response.headers["x-request-id"] == request_id
assert response.json() == {
"error": {
"message": f"provider overloaded\n\nRequest ID: {request_id}",
"type": "overloaded_error",
"param": None,
"code": None,
}
}
assert provider.stream_kwargs[0]["request_id"] == request_id
def test_messages_post_start_execution_failure_follows_closed_block() -> None:
provider = CanonicalFailureProvider(
_partial_anthropic_stream(close_block=True),
kind=FailureKind.OVERLOADED,
status_code=529,
message="provider overloaded",
retryable=True,
)
resolver_patch, client = _client_for(provider)
with (
resolver_patch,
patch("free_claude_code.api.response_streams.trace_event") as trace_mock,
client,
):
response = client.post("/v1/messages", json=_messages_payload(stream=True))
request_id = response.headers["request-id"]
events = parse_sse_text(response.text)
assert response.status_code == 200
assert "x-should-retry" not in response.headers
assert [event.event for event in events] == [
"message_start",
"content_block_start",
"content_block_delta",
"content_block_stop",
"error",
]
assert events[-1].data["error"] == {
"type": "overloaded_error",
"message": f"provider overloaded\n\nRequest ID: {request_id}",
}
assert "message_stop" not in response.text
assert _terminal_trace(trace_mock) == {
"stage": "egress",
"event": "free_claude_code.api.response.terminal_execution_error",
"source": "api",
"wire_api": "messages",
"request_id": request_id,
"status_code": 529,
"error_type": "overloaded_error",
"client_should_retry": False,
"exc_type": "ExecutionFailure",
"failure_kind": "overloaded",
"provider_retryable": True,
}
def test_responses_post_start_execution_failure_retains_id_after_block_close() -> None:
provider = CanonicalFailureProvider(
_partial_anthropic_stream(close_block=True),
kind=FailureKind.RATE_LIMIT,
status_code=429,
message="upstream is busy",
retryable=True,
)
resolver_patch, client = _client_for(provider)
with (
resolver_patch,
patch("free_claude_code.api.response_streams.trace_event") as trace_mock,
client,
):
response = client.post("/v1/responses", json=_responses_payload())
request_id = response.headers["request-id"]
events = parse_sse_text(response.text)
event_names = [event.event for event in events]
created = events[0].data["response"]
failed = events[-1].data["response"]
assert response.status_code == 200
assert response.headers["x-request-id"] == request_id
assert "x-should-retry" not in response.headers
assert event_names[0] == "response.created"
assert "response.output_item.done" in event_names
assert event_names.index("response.output_item.done") < event_names.index(
"response.failed"
)
assert event_names[-1] == "response.failed"
assert failed["id"] == created["id"]
assert failed["status"] == "failed"
assert failed["error"] == {
"message": f"upstream is busy\n\nRequest ID: {request_id}",
"type": "rate_limit_error",
"param": None,
"code": None,
}
assert _terminal_trace(trace_mock) == {
"stage": "egress",
"event": "free_claude_code.api.response.terminal_execution_error",
"source": "api",
"wire_api": "responses",
"request_id": request_id,
"status_code": 429,
"error_type": "rate_limit_error",
"client_should_retry": False,
"exc_type": "ExecutionFailure",
"failure_kind": "rate_limit",
"provider_retryable": True,
}
def test_messages_stream_false_discards_partial_content_on_execution_failure() -> None:
provider = CanonicalFailureProvider(
_partial_anthropic_stream(close_block=False),
kind=FailureKind.RATE_LIMIT,
status_code=429,
message="upstream is busy",
retryable=True,
)
resolver_patch, client = _client_for(provider)
with resolver_patch, client:
response = client.post("/v1/messages", json=_messages_payload(stream=False))
request_id = response.headers["request-id"]
assert response.status_code == 429
assert response.headers["content-type"].startswith("application/json")
assert response.headers["x-should-retry"] == "false"
assert response.json()["request_id"] == request_id
assert response.json()["error"] == {
"type": "rate_limit_error",
"message": f"upstream is busy\n\nRequest ID: {request_id}",
}
assert _PARTIAL_CONTENT not in response.text