kra-oa/app/system/internal/server/middleware/error_audit_test.go

112 lines
3.7 KiB
Go

package middleware
import (
"bytes"
"errors"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
)
func runErrorAudit(t *testing.T, path, response string, logger *slog.Logger) {
t.Helper()
gin.SetMode(gin.TestMode)
engine := gin.New()
engine.Use(ErrorAudit(logger))
engine.GET(path, func(c *gin.Context) {
c.Set(ctxRespBufferKey, bytes.NewBufferString(response))
c.Set("request_id", "request-1")
c.Set("trace_id", "trace-1")
c.Status(http.StatusOK)
})
request := httptest.NewRequest(http.MethodGet, path, nil)
responseRecorder := httptest.NewRecorder()
engine.ServeHTTP(responseRecorder, request)
}
func TestErrorAuditEmitsUnexpectedBusinessFailure(t *testing.T) {
var output bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&output, nil))
runErrorAudit(t, "/test", `{"code":7,"data":{},"msg":"数据库写入失败"}`, logger)
if text := output.String(); !strings.Contains(text, `"mod":"biz"`) || !strings.Contains(text, `"error":"数据库写入失败"`) {
t.Fatalf("unexpected error log: %s", text)
}
}
func TestErrorAuditUsesFeatureModule(t *testing.T) {
var output bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&output, nil))
runErrorAudit(t, "/timedTask/triggerTimedTask", `{"code":7,"data":{},"msg":"任务执行失败"}`, logger)
if text := output.String(); !strings.Contains(text, `"mod":"timedTask"`) {
t.Fatalf("unexpected feature log module: %s", text)
}
}
func TestErrorAuditSkipsExpectedClientFailure(t *testing.T) {
var output bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&output, nil))
runErrorAudit(t, "/test", `{"code":7,"data":{},"msg":"参数错误"}`, logger)
if output.Len() != 0 {
t.Fatalf("expected no error log, got %s", output.String())
}
}
func TestErrorAuditAllowsNilLogger(t *testing.T) {
runErrorAudit(t, "/test", `{"code":7,"data":{},"msg":"数据库写入失败"}`, nil)
}
func TestErrorAuditSkipsLogViewerFailureAlreadyLoggedByHandler(t *testing.T) {
var output bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&output, nil))
runErrorAudit(t, "/logViewer/content", `{"code":7,"data":{},"msg":"日志文件不可读取"}`, logger)
if output.Len() != 0 {
t.Fatalf("log viewer failure must not be emitted twice, got %s", output.String())
}
}
func TestErrorAuditEmitsPrivateMiddlewareErrorOnSuccessResponse(t *testing.T) {
var output bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&output, nil))
gin.SetMode(gin.TestMode)
engine := gin.New()
engine.Use(ErrorAudit(logger))
engine.GET("/test", func(c *gin.Context) {
c.Error(errors.New("operation audit persist: database unavailable"))
c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{}, "msg": "成功"})
})
response := httptest.NewRecorder()
engine.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/test", nil))
if !strings.Contains(output.String(), "operation audit persist: database unavailable") {
t.Fatalf("private middleware error was not logged: %s", output.String())
}
}
func TestErrorAuditEmitsOperationAuditFailureForSysErrorRoute(t *testing.T) {
var output bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&output, nil))
gin.SetMode(gin.TestMode)
engine := gin.New()
engine.Use(ErrorAudit(logger))
engine.PUT("/sysError/updateSysError", func(c *gin.Context) {
c.Set(ctxOperationAuditPersistFailedKey, true)
c.Error(errors.New("operation audit persist: database unavailable"))
c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{}, "msg": "成功"})
})
response := httptest.NewRecorder()
engine.ServeHTTP(response, httptest.NewRequest(http.MethodPut, "/sysError/updateSysError", nil))
if !strings.Contains(output.String(), "operation audit persist: database unavailable") {
t.Fatalf("sysError operation audit failure was not logged: %s", output.String())
}
}