recovery_test.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. //go:build unit
  2. package middleware
  3. import (
  4. "bytes"
  5. "encoding/json"
  6. "net/http"
  7. "net/http/httptest"
  8. "testing"
  9. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  10. "github.com/Wei-Shaw/sub2api/internal/pkg/response"
  11. "github.com/gin-gonic/gin"
  12. "github.com/stretchr/testify/require"
  13. )
  14. func TestRecovery_PanicLogContainsInfo(t *testing.T) {
  15. gin.SetMode(gin.TestMode)
  16. // 临时替换 DefaultErrorWriter 以捕获日志输出
  17. var buf bytes.Buffer
  18. originalWriter := gin.DefaultErrorWriter
  19. gin.DefaultErrorWriter = &buf
  20. t.Cleanup(func() {
  21. gin.DefaultErrorWriter = originalWriter
  22. })
  23. r := gin.New()
  24. r.Use(Recovery())
  25. r.GET("/panic", func(c *gin.Context) {
  26. panic("custom panic message for test")
  27. })
  28. w := httptest.NewRecorder()
  29. req := httptest.NewRequest(http.MethodGet, "/panic", nil)
  30. r.ServeHTTP(w, req)
  31. require.Equal(t, http.StatusInternalServerError, w.Code)
  32. logOutput := buf.String()
  33. require.Contains(t, logOutput, "custom panic message for test", "日志应包含 panic 信息")
  34. require.Contains(t, logOutput, "recovery_test.go", "日志应包含堆栈跟踪文件名")
  35. }
  36. func TestRecovery(t *testing.T) {
  37. gin.SetMode(gin.TestMode)
  38. tests := []struct {
  39. name string
  40. handler gin.HandlerFunc
  41. wantHTTPCode int
  42. wantBody response.Response
  43. }{
  44. {
  45. name: "panic_returns_standard_json_500",
  46. handler: func(c *gin.Context) {
  47. panic("boom")
  48. },
  49. wantHTTPCode: http.StatusInternalServerError,
  50. wantBody: response.Response{
  51. Code: http.StatusInternalServerError,
  52. Message: infraerrors.UnknownMessage,
  53. },
  54. },
  55. {
  56. name: "no_panic_passthrough",
  57. handler: func(c *gin.Context) {
  58. response.Success(c, gin.H{"ok": true})
  59. },
  60. wantHTTPCode: http.StatusOK,
  61. wantBody: response.Response{
  62. Code: 0,
  63. Message: "success",
  64. Data: map[string]any{"ok": true},
  65. },
  66. },
  67. {
  68. name: "panic_after_write_does_not_override_body",
  69. handler: func(c *gin.Context) {
  70. response.Success(c, gin.H{"ok": true})
  71. panic("boom")
  72. },
  73. wantHTTPCode: http.StatusOK,
  74. wantBody: response.Response{
  75. Code: 0,
  76. Message: "success",
  77. Data: map[string]any{"ok": true},
  78. },
  79. },
  80. }
  81. for _, tt := range tests {
  82. t.Run(tt.name, func(t *testing.T) {
  83. r := gin.New()
  84. r.Use(Recovery())
  85. r.GET("/t", tt.handler)
  86. w := httptest.NewRecorder()
  87. req := httptest.NewRequest(http.MethodGet, "/t", nil)
  88. r.ServeHTTP(w, req)
  89. require.Equal(t, tt.wantHTTPCode, w.Code)
  90. var got response.Response
  91. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &got))
  92. require.Equal(t, tt.wantBody, got)
  93. })
  94. }
  95. }