from __future__ import annotations import pytest from pydantic import ValidationError from yuxi.gateway.protocol.errors import ErrorCode, ErrorDetail class TestErrorCode: def test_invalid_request_value(self): assert ErrorCode.INVALID_REQUEST == -32600 def test_method_not_found_value(self): assert ErrorCode.METHOD_NOT_FOUND == -32601 def test_invalid_params_value(self): assert ErrorCode.INVALID_PARAMS == -32602 def test_internal_error_value(self): assert ErrorCode.INTERNAL_ERROR == -32603 def test_rate_limited_value(self): assert ErrorCode.RATE_LIMITED == -32000 def test_auth_required_value(self): assert ErrorCode.AUTH_REQUIRED == -32001 def test_is_int_enum(self): assert isinstance(ErrorCode.INVALID_REQUEST, int) def test_all_values_negative(self): for code in ErrorCode: assert code.value < 0 def test_length_is_eleven(self): assert len(ErrorCode) == 11 def test_not_linked_value(self): assert ErrorCode.NOT_LINKED == -32002 def test_not_paired_value(self): assert ErrorCode.NOT_PAIRED == -32003 def test_agent_timeout_value(self): assert ErrorCode.AGENT_TIMEOUT == -32004 def test_approval_not_found_value(self): assert ErrorCode.APPROVAL_NOT_FOUND == -32005 def test_unavailable_value(self): assert ErrorCode.UNAVAILABLE == -32006 class TestErrorDetail: def test_minimal_construction(self): detail = ErrorDetail(code=ErrorCode.INTERNAL_ERROR, message="server error") assert detail.code == ErrorCode.INTERNAL_ERROR assert detail.message == "server error" assert detail.details is None assert detail.retryable is False assert detail.retry_after_ms is None def test_full_construction(self): detail = ErrorDetail( code=ErrorCode.RATE_LIMITED, message="rate limited", details="max 100 req/min", retryable=True, retry_after_ms=60000, ) assert detail.details == "max 100 req/min" assert detail.retryable is True assert detail.retry_after_ms == 60000 def test_code_is_required(self): with pytest.raises(ValidationError): ErrorDetail(message="error") def test_message_is_required(self): with pytest.raises(ValidationError): ErrorDetail(code=ErrorCode.INTERNAL_ERROR) def test_defaults(self): detail = ErrorDetail(code=ErrorCode.METHOD_NOT_FOUND, message="not found") assert detail.details is None assert detail.retryable is False assert detail.retry_after_ms is None def test_json_serialization(self): detail = ErrorDetail(code=ErrorCode.AUTH_REQUIRED, message="auth needed", retryable=False) data = detail.model_dump() assert data == { "code": -32001, "message": "auth needed", "details": None, "retryable": False, "retry_after_ms": None, } def test_json_deserialization(self): data = { "code": -32600, "message": "invalid json", "details": "bad format", "retryable": False, "retry_after_ms": None, } detail = ErrorDetail.model_validate(data) assert detail.code == ErrorCode.INVALID_REQUEST assert detail.message == "invalid json" assert detail.details == "bad format" def test_retry_info_for_rate_limit(self): detail = ErrorDetail(code=ErrorCode.RATE_LIMITED, message="too many", retryable=True, retry_after_ms=30000) assert detail.retryable is True assert detail.retry_after_ms == 30000 def test_code_accepts_int_enum(self): detail = ErrorDetail(code=ErrorCode.INVALID_PARAMS, message="bad params") assert detail.code == -32602 def test_message_preserved_as_string(self): detail = ErrorDetail(code=ErrorCode.INTERNAL_ERROR, message="unexpected null") assert isinstance(detail.message, str)