feat: 实现统一错误处理系统 (003-error-handling)
- 新增统一错误码定义和管理 (pkg/errors/codes.go) - 新增全局错误处理器和中间件 (pkg/errors/handler.go, internal/middleware/error_handler.go) - 新增错误上下文管理 (pkg/errors/context.go) - 增强 Panic 恢复中间件 (internal/middleware/recover.go) - 新增完整的单元测试和集成测试 - 新增功能文档 (docs/003-error-handling/) - 新增功能规范 (specs/003-error-handling/) - 更新 CLAUDE.md 和 README.md
This commit is contained in:
190
pkg/errors/codes_test.go
Normal file
190
pkg/errors/codes_test.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// TestGetHTTPStatus 测试错误码到 HTTP 状态码的映射
|
||||
func TestGetHTTPStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code int
|
||||
expected int
|
||||
}{
|
||||
// 成功
|
||||
{"成功", CodeSuccess, fiber.StatusOK},
|
||||
|
||||
// 客户端错误 (1xxx -> 4xx)
|
||||
{"参数验证失败", CodeInvalidParam, fiber.StatusBadRequest},
|
||||
{"缺失认证令牌", CodeMissingToken, fiber.StatusUnauthorized},
|
||||
{"无效令牌", CodeInvalidToken, fiber.StatusUnauthorized},
|
||||
{"未授权访问", CodeUnauthorized, fiber.StatusUnauthorized},
|
||||
{"禁止访问", CodeForbidden, fiber.StatusForbidden},
|
||||
{"资源未找到", CodeNotFound, fiber.StatusNotFound},
|
||||
{"资源冲突", CodeConflict, fiber.StatusConflict},
|
||||
{"请求过多", CodeTooManyRequests, fiber.StatusTooManyRequests},
|
||||
{"请求体过大", CodeRequestTooLarge, fiber.StatusBadRequest},
|
||||
|
||||
// 服务端错误 (2xxx -> 5xx)
|
||||
{"内部服务器错误", CodeInternalError, fiber.StatusInternalServerError},
|
||||
{"数据库错误", CodeDatabaseError, fiber.StatusInternalServerError},
|
||||
{"缓存服务错误", CodeRedisError, fiber.StatusInternalServerError},
|
||||
{"服务不可用", CodeServiceUnavailable, fiber.StatusServiceUnavailable},
|
||||
{"请求超时", CodeTimeout, fiber.StatusGatewayTimeout},
|
||||
{"任务队列错误", CodeTaskQueueError, fiber.StatusInternalServerError},
|
||||
|
||||
// 未知错误码
|
||||
{"未知错误码", 9999, fiber.StatusInternalServerError},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := GetHTTPStatus(tt.code)
|
||||
if result != tt.expected {
|
||||
t.Errorf("GetHTTPStatus(%d) = %d, expected %d", tt.code, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetMessage 测试错误码到错误消息的映射
|
||||
func TestGetMessage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code int
|
||||
expected string
|
||||
}{
|
||||
// 成功
|
||||
{"成功", CodeSuccess, "成功"},
|
||||
|
||||
// 客户端错误
|
||||
{"参数验证失败", CodeInvalidParam, "参数验证失败"},
|
||||
{"缺失认证令牌", CodeMissingToken, "缺失认证令牌"},
|
||||
{"无效令牌", CodeInvalidToken, "无效或过期的令牌"},
|
||||
{"未授权访问", CodeUnauthorized, "未授权访问"},
|
||||
{"禁止访问", CodeForbidden, "禁止访问"},
|
||||
{"资源未找到", CodeNotFound, "资源未找到"},
|
||||
{"资源冲突", CodeConflict, "资源冲突"},
|
||||
{"请求过多", CodeTooManyRequests, "请求过多,请稍后重试"},
|
||||
{"请求体过大", CodeRequestTooLarge, "请求体过大"},
|
||||
|
||||
// 服务端错误
|
||||
{"内部服务器错误", CodeInternalError, "内部服务器错误"},
|
||||
{"数据库错误", CodeDatabaseError, "数据库错误"},
|
||||
{"缓存服务错误", CodeRedisError, "缓存服务错误"},
|
||||
{"服务不可用", CodeServiceUnavailable, "服务暂时不可用"},
|
||||
{"请求超时", CodeTimeout, "请求超时"},
|
||||
{"任务队列错误", CodeTaskQueueError, "任务队列错误"},
|
||||
|
||||
// 未知错误码
|
||||
{"未知错误码", 9999, "请求处理失败"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := GetMessage(tt.code, "zh-CN")
|
||||
if result != tt.expected {
|
||||
t.Errorf("GetMessage(%d, \"zh-CN\") = %q, expected %q", tt.code, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetLogLevel 测试错误码到日志级别的映射
|
||||
func TestGetLogLevel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code int
|
||||
expected string
|
||||
}{
|
||||
// 成功 (不记录日志)
|
||||
{"成功", CodeSuccess, "info"},
|
||||
|
||||
// 客户端错误 (Warn 级别)
|
||||
{"参数验证失败", CodeInvalidParam, "warn"},
|
||||
{"缺失认证令牌", CodeMissingToken, "warn"},
|
||||
{"无效令牌", CodeInvalidToken, "warn"},
|
||||
{"未授权访问", CodeUnauthorized, "warn"},
|
||||
{"禁止访问", CodeForbidden, "warn"},
|
||||
{"资源未找到", CodeNotFound, "warn"},
|
||||
{"资源冲突", CodeConflict, "warn"},
|
||||
{"请求过多", CodeTooManyRequests, "warn"},
|
||||
{"请求体过大", CodeRequestTooLarge, "warn"},
|
||||
|
||||
// 服务端错误 (Error 级别)
|
||||
{"内部服务器错误", CodeInternalError, "error"},
|
||||
{"数据库错误", CodeDatabaseError, "error"},
|
||||
{"缓存服务错误", CodeRedisError, "error"},
|
||||
{"服务不可用", CodeServiceUnavailable, "error"},
|
||||
{"请求超时", CodeTimeout, "error"},
|
||||
{"任务队列错误", CodeTaskQueueError, "error"},
|
||||
|
||||
// 未知错误码 (Error 级别)
|
||||
{"未知错误码", 9999, "error"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := GetLogLevel(tt.code)
|
||||
if result != tt.expected {
|
||||
t.Errorf("GetLogLevel(%d) = %q, expected %q", tt.code, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetHTTPStatus 基准测试 HTTP 状态码映射性能
|
||||
func BenchmarkGetHTTPStatus(b *testing.B) {
|
||||
codes := []int{
|
||||
CodeSuccess,
|
||||
CodeInvalidParam,
|
||||
CodeMissingToken,
|
||||
CodeInternalError,
|
||||
CodeDatabaseError,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, code := range codes {
|
||||
GetHTTPStatus(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetMessage 基准测试错误消息获取性能
|
||||
func BenchmarkGetMessage(b *testing.B) {
|
||||
codes := []int{
|
||||
CodeSuccess,
|
||||
CodeInvalidParam,
|
||||
CodeMissingToken,
|
||||
CodeInternalError,
|
||||
CodeDatabaseError,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, code := range codes {
|
||||
GetMessage(code, "zh-CN")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetLogLevel 基准测试日志级别映射性能
|
||||
func BenchmarkGetLogLevel(b *testing.B) {
|
||||
codes := []int{
|
||||
CodeSuccess,
|
||||
CodeInvalidParam,
|
||||
CodeMissingToken,
|
||||
CodeInternalError,
|
||||
CodeDatabaseError,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, code := range codes {
|
||||
GetLogLevel(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user