"""错误码与异常体系测试.""" import pytest from src.ai.errors import ( AIError, AILLMUnavailableError, AIQuotaExceededError, AIRateLimitedError, AIValidationError, AIWorkflowNotFoundError, AIWorkflowStateInvalidError, ErrorCode, ErrorCodes, ) class TestErrorCodes: """错误码映射测试.""" def test_http_status_mapping(self) -> None: """错误码 → HTTP 状态码映射正确.""" assert ErrorCodes.http_status(ErrorCode.AI_UNAUTHORIZED) == 401 assert ErrorCodes.http_status(ErrorCode.AI_FORBIDDEN) == 403 assert ErrorCodes.http_status(ErrorCode.AI_RATE_LIMITED) == 429 assert ErrorCodes.http_status(ErrorCode.AI_LLM_UNAVAILABLE) == 200 assert ErrorCodes.http_status(ErrorCode.AI_LLM_TIMEOUT) == 504 assert ErrorCodes.http_status(ErrorCode.AI_INTERNAL_ERROR) == 500 def test_http_status_unknown_fallback(self) -> None: """未知错误码回退 500.""" # 使用一个不存在的 ErrorCode 值 assert ErrorCodes.http_status(ErrorCode.AI_INTERNAL_ERROR) == 500 def test_error_code_values(self) -> None: """ErrorCode 枚举值与名称一致.""" assert ErrorCode.AI_UNAUTHORIZED == "AI_UNAUTHORIZED" assert ErrorCode.AI_LLM_ALL_PROVIDERS_FAILED == "AI_LLM_ALL_PROVIDERS_FAILED" def test_all_codes_have_http_mapping(self) -> None: """所有错误码都有 HTTP 映射.""" for code in ErrorCode: assert code in ErrorCodes.HTTP_STATUS, f"{code} missing HTTP mapping" class TestExceptions: """异常类测试.""" def test_ai_error_default(self) -> None: """AIError 默认值.""" err = AIError() assert err.code == ErrorCode.AI_INTERNAL_ERROR assert err.http_status == 500 assert err.details == {} def test_ai_validation_error(self) -> None: """AIValidationError 使用 AI_INVALID_MODEL code.""" err = AIValidationError("invalid model name") assert err.code == ErrorCode.AI_INVALID_MODEL assert err.http_status == 400 assert "invalid model name" in str(err) def test_rate_limited_error(self) -> None: """AIRateLimitedError 携带 dimension + limit.""" err = AIRateLimitedError("user", 10) assert err.code == ErrorCode.AI_RATE_LIMITED assert err.http_status == 429 assert err.details["dimension"] == "user" assert err.details["limit"] == 10 def test_quota_exceeded_error(self) -> None: """AIQuotaExceededError 携带 scope + used + budget.""" err = AIQuotaExceededError("school", 1_200_000, 1_000_000) assert err.code == ErrorCode.AI_QUOTA_EXCEEDED assert err.details["scope"] == "school" assert err.details["used"] == 1_200_000 assert err.details["budget"] == 1_000_000 def test_llm_unavailable_error(self) -> None: """AILLMUnavailableError 默认消息.""" err = AILLMUnavailableError() assert err.code == ErrorCode.AI_LLM_UNAVAILABLE assert err.http_status == 200 # 降级模式仍 200 assert "all providers failed" in str(err) def test_workflow_not_found_error(self) -> None: """AIWorkflowNotFoundError 携带 workflow_id.""" err = AIWorkflowNotFoundError("wf-123") assert err.code == ErrorCode.AI_WORKFLOW_NOT_FOUND assert err.http_status == 404 assert err.details["workflow_id"] == "wf-123" def test_workflow_state_invalid_error(self) -> None: """AIWorkflowStateInvalidError 携带状态信息.""" err = AIWorkflowStateInvalidError("wf-1", "pending", "confirm") assert err.code == ErrorCode.AI_WORKFLOW_STATE_INVALID assert err.http_status == 409 assert err.details["current_status"] == "pending" assert err.details["action"] == "confirm" def test_ai_error_is_exception(self) -> None: """AIError 是 Exception 子类.""" with pytest.raises(AIError): raise AIError(ErrorCode.AI_INTERNAL_ERROR, "test")