已验证的问题状态: 1. P0-07补偿处理器 - 已集成到main.go ✅ 2. P0-09外键校验器 - 已集成到main.go并调用 ✅ 3. 幂等协议Idempotency-Key - 已在idempotency.go实现 ✅ 4. 幂等唯一索引 - 已在SQL中定义 ✅ Gateway修复: - 修复cors.go语法错误(重复函数定义) - 修复middleware_test.go参数不匹配问题 - 修复go.mod降级到go 1.21解决依赖问题
325 lines
8.9 KiB
Go
325 lines
8.9 KiB
Go
package error
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestErrorCodes(t *testing.T) {
|
|
// 验证所有错误码常量
|
|
tests := []struct {
|
|
code ErrorCode
|
|
expected string
|
|
}{
|
|
{AUTH_INVALID_TOKEN, "AUTH_001"},
|
|
{AUTH_INSUFFICIENT_PERMISSION, "AUTH_002"},
|
|
{AUTH_MFA_REQUIRED, "AUTH_003"},
|
|
{BILLING_INSUFFICIENT_BALANCE, "BILLING_001"},
|
|
{BILLING_CHARGE_FAILED, "BILLING_002"},
|
|
{BILLING_REFUND_FAILED, "BILLING_003"},
|
|
{BILLING_DISCREPANCY, "BILLING_004"},
|
|
{ROUTER_NO_PROVIDER_AVAILABLE, "ROUTER_001"},
|
|
{ROUTER_ALL_PROVIDERS_FAILED, "ROUTER_002"},
|
|
{ROUTER_TIMEOUT, "ROUTER_003"},
|
|
{PROVIDER_INVALID_KEY, "PROVIDER_001"},
|
|
{PROVIDER_RATE_LIMIT, "PROVIDER_002"},
|
|
{PROVIDER_QUOTA_EXCEEDED, "PROVIDER_003"},
|
|
{PROVIDER_MODEL_NOT_FOUND, "PROVIDER_004"},
|
|
{PROVIDER_ERROR, "PROVIDER_005"},
|
|
{RATE_LIMIT_EXCEEDED, "RATE_LIMIT_001"},
|
|
{RATE_LIMIT_TOKEN_EXCEEDED, "RATE_LIMIT_002"},
|
|
{RATE_LIMIT_BURST_EXCEEDED, "RATE_LIMIT_003"},
|
|
{COMMON_INVALID_REQUEST, "COMMON_001"},
|
|
{COMMON_RESOURCE_NOT_FOUND, "COMMON_002"},
|
|
{COMMON_INTERNAL_ERROR, "COMMON_003"},
|
|
{COMMON_SERVICE_UNAVAILABLE, "COMMON_004"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if string(tt.code) != tt.expected {
|
|
t.Errorf("expected %s, got %s", tt.expected, tt.code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNewGatewayError(t *testing.T) {
|
|
err := NewGatewayError(COMMON_INVALID_REQUEST, "test message")
|
|
|
|
if err.Code != COMMON_INVALID_REQUEST {
|
|
t.Errorf("expected code COMMON_INVALID_REQUEST, got %s", err.Code)
|
|
}
|
|
if err.Message != "test message" {
|
|
t.Errorf("expected message 'test message', got %s", err.Message)
|
|
}
|
|
if err.Details == nil {
|
|
t.Error("expected Details to be initialized")
|
|
}
|
|
}
|
|
|
|
func TestGatewayError_Error(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
err *GatewayError
|
|
expected string
|
|
}{
|
|
{
|
|
name: "without internal error",
|
|
err: NewGatewayError(COMMON_INVALID_REQUEST, "test"),
|
|
expected: "COMMON_001: test",
|
|
},
|
|
{
|
|
name: "with internal error",
|
|
err: NewGatewayError(COMMON_INTERNAL_ERROR, "outer").WithInternal(errors.New("inner")),
|
|
expected: "COMMON_003: outer (caused by: inner)",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := tt.err.Error(); got != tt.expected {
|
|
t.Errorf("Error() = %v, want %v", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGatewayError_Unwrap(t *testing.T) {
|
|
internalErr := errors.New("inner error")
|
|
err := NewGatewayError(COMMON_INTERNAL_ERROR, "outer").WithInternal(internalErr)
|
|
|
|
if err.Unwrap() != internalErr {
|
|
t.Error("Unwrap() should return the internal error")
|
|
}
|
|
}
|
|
|
|
func TestGatewayError_WithRequestID(t *testing.T) {
|
|
err := NewGatewayError(COMMON_INVALID_REQUEST, "test")
|
|
result := err.WithRequestID("req-123")
|
|
|
|
if err.RequestID != "req-123" {
|
|
t.Errorf("expected RequestID req-123, got %s", err.RequestID)
|
|
}
|
|
if result != err {
|
|
t.Error("WithRequestID should return the same error")
|
|
}
|
|
}
|
|
|
|
func TestGatewayError_WithDetail(t *testing.T) {
|
|
err := NewGatewayError(COMMON_INVALID_REQUEST, "test")
|
|
result := err.WithDetail("key", "value")
|
|
|
|
if err.Details["key"] != "value" {
|
|
t.Errorf("expected Details[key] = value, got %v", err.Details["key"])
|
|
}
|
|
if result != err {
|
|
t.Error("WithDetail should return the same error")
|
|
}
|
|
}
|
|
|
|
func TestGatewayError_WithInternal(t *testing.T) {
|
|
internalErr := errors.New("internal error")
|
|
err := NewGatewayError(COMMON_INVALID_REQUEST, "test")
|
|
result := err.WithInternal(internalErr)
|
|
|
|
if err.Internal != internalErr {
|
|
t.Error("expected Internal to be set")
|
|
}
|
|
if result != err {
|
|
t.Error("WithInternal should return the same error")
|
|
}
|
|
}
|
|
|
|
func TestGetErrorInfo(t *testing.T) {
|
|
tests := []struct {
|
|
code ErrorCode
|
|
expectedStatus int
|
|
expectedRetry bool
|
|
}{
|
|
{AUTH_INVALID_TOKEN, 401, false},
|
|
{AUTH_INSUFFICIENT_PERMISSION, 403, false},
|
|
{AUTH_MFA_REQUIRED, 403, false},
|
|
{BILLING_INSUFFICIENT_BALANCE, 402, false},
|
|
{BILLING_CHARGE_FAILED, 500, true},
|
|
{BILLING_REFUND_FAILED, 500, true},
|
|
{BILLING_DISCREPANCY, 500, true},
|
|
{ROUTER_NO_PROVIDER_AVAILABLE, 503, true},
|
|
{ROUTER_ALL_PROVIDERS_FAILED, 503, true},
|
|
{ROUTER_TIMEOUT, 504, true},
|
|
{PROVIDER_INVALID_KEY, 401, false},
|
|
{PROVIDER_RATE_LIMIT, 429, true},
|
|
{PROVIDER_QUOTA_EXCEEDED, 402, false},
|
|
{PROVIDER_MODEL_NOT_FOUND, 404, false},
|
|
{PROVIDER_ERROR, 502, true},
|
|
{RATE_LIMIT_EXCEEDED, 429, false},
|
|
{RATE_LIMIT_TOKEN_EXCEEDED, 429, false},
|
|
{RATE_LIMIT_BURST_EXCEEDED, 429, false},
|
|
{COMMON_INVALID_REQUEST, 400, false},
|
|
{COMMON_RESOURCE_NOT_FOUND, 404, false},
|
|
{COMMON_INTERNAL_ERROR, 500, true},
|
|
{COMMON_SERVICE_UNAVAILABLE, 503, true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(string(tt.code), func(t *testing.T) {
|
|
err := NewGatewayError(tt.code, "test")
|
|
info := err.GetErrorInfo()
|
|
|
|
if info.HTTPStatus != tt.expectedStatus {
|
|
t.Errorf("code %s: expected status %d, got %d", tt.code, tt.expectedStatus, info.HTTPStatus)
|
|
}
|
|
if info.Retryable != tt.expectedRetry {
|
|
t.Errorf("code %s: expected retryable %v, got %v", tt.code, tt.expectedRetry, info.Retryable)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetErrorInfo_UnknownCode(t *testing.T) {
|
|
err := NewGatewayError("UNKNOWN_CODE", "test")
|
|
info := err.GetErrorInfo()
|
|
|
|
// 未知错误码应返回默认值
|
|
if info.HTTPStatus != 500 {
|
|
t.Errorf("expected status 500, got %d", info.HTTPStatus)
|
|
}
|
|
if info.Retryable != true {
|
|
t.Error("expected retryable true for unknown code")
|
|
}
|
|
if info.Code != COMMON_INTERNAL_ERROR {
|
|
t.Errorf("expected code COMMON_INTERNAL_ERROR, got %s", info.Code)
|
|
}
|
|
}
|
|
|
|
func TestErrorInfo_Struct(t *testing.T) {
|
|
info := ErrorInfo{
|
|
Code: COMMON_INVALID_REQUEST,
|
|
Message: "test message",
|
|
HTTPStatus: 400,
|
|
Retryable: false,
|
|
}
|
|
|
|
if info.Code != COMMON_INVALID_REQUEST {
|
|
t.Errorf("expected code COMMON_INVALID_REQUEST, got %s", info.Code)
|
|
}
|
|
if info.Message != "test message" {
|
|
t.Errorf("expected message 'test message', got %s", info.Message)
|
|
}
|
|
if info.HTTPStatus != 400 {
|
|
t.Errorf("expected HTTPStatus 400, got %d", info.HTTPStatus)
|
|
}
|
|
if info.Retryable != false {
|
|
t.Error("expected Retryable false")
|
|
}
|
|
}
|
|
|
|
func TestGatewayError_Chaining(t *testing.T) {
|
|
err := NewGatewayError(COMMON_INVALID_REQUEST, "test").
|
|
WithRequestID("req-123").
|
|
WithDetail("field", "email").
|
|
WithDetail("reason", "invalid format")
|
|
|
|
if err.RequestID != "req-123" {
|
|
t.Errorf("expected RequestID req-123, got %s", err.RequestID)
|
|
}
|
|
if err.Details["field"] != "email" {
|
|
t.Errorf("expected field=email, got %v", err.Details["field"])
|
|
}
|
|
if err.Details["reason"] != "invalid format" {
|
|
t.Errorf("expected reason=invalid format, got %v", err.Details["reason"])
|
|
}
|
|
}
|
|
|
|
func TestErrorDefinitions_Completeness(t *testing.T) {
|
|
// 确保所有错误码都在ErrorDefinitions中定义
|
|
codes := []ErrorCode{
|
|
AUTH_INVALID_TOKEN,
|
|
AUTH_INSUFFICIENT_PERMISSION,
|
|
AUTH_MFA_REQUIRED,
|
|
BILLING_INSUFFICIENT_BALANCE,
|
|
BILLING_CHARGE_FAILED,
|
|
BILLING_REFUND_FAILED,
|
|
BILLING_DISCREPANCY,
|
|
ROUTER_NO_PROVIDER_AVAILABLE,
|
|
ROUTER_ALL_PROVIDERS_FAILED,
|
|
ROUTER_TIMEOUT,
|
|
PROVIDER_INVALID_KEY,
|
|
PROVIDER_RATE_LIMIT,
|
|
PROVIDER_QUOTA_EXCEEDED,
|
|
PROVIDER_MODEL_NOT_FOUND,
|
|
PROVIDER_ERROR,
|
|
RATE_LIMIT_EXCEEDED,
|
|
RATE_LIMIT_TOKEN_EXCEEDED,
|
|
RATE_LIMIT_BURST_EXCEEDED,
|
|
COMMON_INVALID_REQUEST,
|
|
COMMON_RESOURCE_NOT_FOUND,
|
|
COMMON_INTERNAL_ERROR,
|
|
COMMON_SERVICE_UNAVAILABLE,
|
|
}
|
|
|
|
for _, code := range codes {
|
|
if _, ok := ErrorDefinitions[code]; !ok {
|
|
t.Errorf("code %s not found in ErrorDefinitions", code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestErrorDefinitions_Consistency(t *testing.T) {
|
|
for code, info := range ErrorDefinitions {
|
|
if info.Code != code {
|
|
t.Errorf("ErrorDefinitions[%s].Code = %s, expected %s", code, info.Code, code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGatewayError_ImplementsErrorInterface(t *testing.T) {
|
|
err := NewGatewayError(COMMON_INVALID_REQUEST, "test")
|
|
|
|
var e error = err
|
|
if e.Error() != "COMMON_001: test" {
|
|
t.Error("GatewayError should implement error interface")
|
|
}
|
|
}
|
|
|
|
func TestGatewayError_ErrorWithWrappedError(t *testing.T) {
|
|
wrapped := errors.New("wrapped error")
|
|
err := NewGatewayError(COMMON_INTERNAL_ERROR, "outer error").WithInternal(wrapped)
|
|
|
|
// Error()应该包含wrapped error的信息
|
|
expected := "COMMON_003: outer error (caused by: wrapped error)"
|
|
if err.Error() != expected {
|
|
t.Errorf("expected %s, got %s", expected, err.Error())
|
|
}
|
|
}
|
|
|
|
func TestNewGatewayError_EmptyMessage(t *testing.T) {
|
|
err := NewGatewayError(COMMON_INVALID_REQUEST, "")
|
|
|
|
if err.Message != "" {
|
|
t.Errorf("expected empty message, got %s", err.Message)
|
|
}
|
|
}
|
|
|
|
func TestGetErrorInfo_ErrorDefinitions(t *testing.T) {
|
|
info := ErrorDefinitions[AUTH_INVALID_TOKEN]
|
|
|
|
if info.Code != AUTH_INVALID_TOKEN {
|
|
t.Errorf("expected AUTH_INVALID_TOKEN, got %s", info.Code)
|
|
}
|
|
if info.Message != "Invalid or expired token" {
|
|
t.Errorf("unexpected message: %s", info.Message)
|
|
}
|
|
if info.HTTPStatus != 401 {
|
|
t.Errorf("expected 401, got %d", info.HTTPStatus)
|
|
}
|
|
if info.Retryable != false {
|
|
t.Error("expected non-retryable")
|
|
}
|
|
}
|
|
|
|
func TestErrorCode_Type(t *testing.T) {
|
|
var code ErrorCode = "TEST_001"
|
|
if string(code) != "TEST_001" {
|
|
t.Errorf("expected TEST_001, got %s", code)
|
|
}
|
|
}
|