Files
goplt/internal/server/middleware_test.go
0x1d 5fdbb729bd
Some checks failed
CI / Test (pull_request) Failing after 17s
CI / Lint (pull_request) Failing after 18s
CI / Build (pull_request) Successful in 12s
CI / Format Check (pull_request) Successful in 2s
test: add comprehensive tests for all Epic 1 stories
Story 1.2: Database Layer
- Test database client creation, connection, ping, and close
- Test connection pooling configuration
- Tests skip if database is not available (short mode)

Story 1.3: Health Monitoring and Metrics
- Test health registry registration and checking
- Test database health checker
- Test liveness and readiness checks
- Test metrics creation, middleware, and handler
- Test Prometheus metrics endpoint

Story 1.4: Error Handling and Error Bus
- Test channel-based error bus creation
- Test error publishing with context
- Test nil error handling
- Test channel full scenario
- Test graceful shutdown
- Fix Close() method to handle multiple calls safely

Story 1.5: HTTP Server and Middleware
- Test server creation with all middleware
- Test request ID middleware
- Test logging middleware
- Test panic recovery middleware
- Test CORS middleware
- Test timeout middleware
- Test health and metrics endpoints
- Test server shutdown

Story 1.6: OpenTelemetry Tracing
- Test tracer initialization (enabled/disabled)
- Test development and production modes
- Test OTLP exporter configuration
- Test graceful shutdown
- Test no-op tracer provider

All tests follow Go testing best practices:
- Table-driven tests where appropriate
- Parallel execution
- Proper mocking of interfaces
- Skip tests requiring external dependencies in short mode
2025-11-05 21:05:36 +01:00

260 lines
5.8 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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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)
}