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 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()) } }