The Gin framework uses a global mode setting (gin.SetMode()) which is not thread-safe when tests run in parallel. Removing t.Parallel() from all server tests that use gin.SetMode() prevents data races when running tests with the race detector enabled. All tests now pass with 'make test' which includes -race flag.
244 lines
5.7 KiB
Go
244 lines
5.7 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func TestRequestIDMiddleware(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
router := gin.New()
|
|
router.Use(RequestIDMiddleware())
|
|
|
|
router.GET("/test", func(c *gin.Context) {
|
|
requestID, exists := c.Get(string(requestIDKey))
|
|
if !exists {
|
|
t.Error("Expected request ID in context")
|
|
}
|
|
if requestID == nil || requestID == "" {
|
|
t.Error("Expected non-empty request ID")
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"request_id": requestID})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("Expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
// Verify X-Request-ID header is set
|
|
if w.Header().Get("X-Request-ID") == "" {
|
|
t.Error("Expected X-Request-ID header")
|
|
}
|
|
}
|
|
|
|
func TestRequestIDMiddleware_ExistingHeader(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
router := gin.New()
|
|
router.Use(RequestIDMiddleware())
|
|
|
|
router.GET("/test", func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{"ok": true})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
req.Header.Set("X-Request-ID", "existing-id")
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Header().Get("X-Request-ID") != "existing-id" {
|
|
t.Errorf("Expected existing request ID, got %s", w.Header().Get("X-Request-ID"))
|
|
}
|
|
}
|
|
|
|
func TestLoggingMiddleware(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockLogger := &mockLogger{}
|
|
router := gin.New()
|
|
router.Use(RequestIDMiddleware())
|
|
router.Use(LoggingMiddleware(mockLogger))
|
|
|
|
router.GET("/test", func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{"message": "test"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("Expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
// Verify logging was called
|
|
if len(mockLogger.infoLogs) == 0 {
|
|
t.Error("Expected info log to be called")
|
|
}
|
|
}
|
|
|
|
func TestPanicRecoveryMiddleware(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockErrorBus := &mockErrorBusMiddleware{}
|
|
|
|
router := gin.New()
|
|
router.Use(PanicRecoveryMiddleware(mockErrorBus))
|
|
|
|
router.GET("/panic", func(c *gin.Context) {
|
|
panic("test panic")
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/panic", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Errorf("Expected status 500, got %d", w.Code)
|
|
}
|
|
|
|
// Verify error was published to error bus
|
|
if len(mockErrorBus.errors) == 0 {
|
|
t.Error("Expected error to be published to error bus")
|
|
}
|
|
}
|
|
|
|
func TestPanicRecoveryMiddleware_ErrorPanic(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockErrorBus := &mockErrorBusMiddleware{}
|
|
|
|
router := gin.New()
|
|
router.Use(PanicRecoveryMiddleware(mockErrorBus))
|
|
|
|
router.GET("/panic-error", func(c *gin.Context) {
|
|
panic(errors.New("test error"))
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/panic-error", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Errorf("Expected status 500, got %d", w.Code)
|
|
}
|
|
|
|
if len(mockErrorBus.errors) == 0 {
|
|
t.Error("Expected error to be published to error bus")
|
|
}
|
|
}
|
|
|
|
func TestCORSMiddleware(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
router := gin.New()
|
|
router.Use(CORSMiddleware())
|
|
|
|
router.GET("/test", func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{"message": "test"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Verify CORS headers
|
|
if w.Header().Get("Access-Control-Allow-Origin") != "*" {
|
|
t.Error("Expected CORS header Access-Control-Allow-Origin")
|
|
}
|
|
|
|
if w.Header().Get("Access-Control-Allow-Credentials") != "true" {
|
|
t.Error("Expected CORS header Access-Control-Allow-Credentials")
|
|
}
|
|
}
|
|
|
|
func TestCORSMiddleware_OPTIONS(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
router := gin.New()
|
|
router.Use(CORSMiddleware())
|
|
|
|
router.GET("/test", func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{"message": "test"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodOptions, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusNoContent {
|
|
t.Errorf("Expected status 204 for OPTIONS, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestTimeoutMiddleware(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
router := gin.New()
|
|
router.Use(TimeoutMiddleware(100 * time.Millisecond))
|
|
|
|
router.GET("/test", func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{"message": "test"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("Expected status 200, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// mockLogger implements logger.Logger for testing.
|
|
type mockLogger struct {
|
|
infoLogs []string
|
|
errors []string
|
|
}
|
|
|
|
func (m *mockLogger) Debug(msg string, fields ...logger.Field) {}
|
|
func (m *mockLogger) Info(msg string, fields ...logger.Field) {
|
|
m.infoLogs = append(m.infoLogs, msg)
|
|
}
|
|
func (m *mockLogger) Warn(msg string, fields ...logger.Field) {}
|
|
func (m *mockLogger) Error(msg string, fields ...logger.Field) {
|
|
m.errors = append(m.errors, msg)
|
|
}
|
|
func (m *mockLogger) With(fields ...logger.Field) logger.Logger {
|
|
return m
|
|
}
|
|
func (m *mockLogger) WithContext(ctx context.Context) logger.Logger {
|
|
return m
|
|
}
|
|
|
|
// mockErrorBusMiddleware implements errorbus.ErrorPublisher for testing middleware.
|
|
type mockErrorBusMiddleware struct {
|
|
errors []error
|
|
ctxs []context.Context
|
|
}
|
|
|
|
func (m *mockErrorBusMiddleware) Publish(ctx context.Context, err error) {
|
|
m.errors = append(m.errors, err)
|
|
m.ctxs = append(m.ctxs, ctx)
|
|
}
|