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