133 lines
4.5 KiB
Go
133 lines
4.5 KiB
Go
package middleware
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func TestOperationQueryBodyPreservesEncodedSeparators(t *testing.T) {
|
|
var values map[string]string
|
|
if err := json.Unmarshal(operationQueryBody("filter=one%3Dtwo%26three&repeat=first&repeat=last"), &values); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if values["filter"] != "one=two&three" || values["repeat"] != "last" {
|
|
t.Fatalf("unexpected query body: %#v", values)
|
|
}
|
|
}
|
|
|
|
func TestOperationQueryBodyRedactsSensitiveValues(t *testing.T) {
|
|
var values map[string]string
|
|
if err := json.Unmarshal(operationQueryBody("token=secret-token&name=alice"), &values); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if values["token"] != redactedValue || values["name"] != "alice" {
|
|
t.Fatalf("unexpected redacted query body: %#v", values)
|
|
}
|
|
}
|
|
|
|
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())
|
|
}
|
|
}
|