package middleware import ( "net/http/httptest" "testing" "time" "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestMetricsMiddleware(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Metrics()) router.GET("/test", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) // Vérifier que les métriques ont été enregistrées // On vérifie via le registry Prometheus par défaut registry := prometheus.DefaultRegisterer.(*prometheus.Registry) metricFamilies, err := registry.Gather() require.NoError(t, err) foundRequestsTotal := false foundDuration := false for _, mf := range metricFamilies { if *mf.Name == "veza_gin_http_requests_total" { foundRequestsTotal = true assert.Greater(t, len(mf.Metric), 0) } if *mf.Name == "veza_gin_http_request_duration_seconds" { foundDuration = true assert.Greater(t, len(mf.Metric), 0) } } assert.True(t, foundRequestsTotal, "veza_gin_http_requests_total metric should exist") assert.True(t, foundDuration, "veza_gin_http_request_duration_seconds metric should exist") } func TestMetricsMiddleware_DifferentStatusCodes(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Metrics()) router.GET("/ok", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) }) router.GET("/notfound", func(c *gin.Context) { c.JSON(404, gin.H{"error": "not found"}) }) router.GET("/error", func(c *gin.Context) { c.JSON(500, gin.H{"error": "internal error"}) }) // Tester différents codes de status w1 := httptest.NewRecorder() req1 := httptest.NewRequest("GET", "/ok", nil) router.ServeHTTP(w1, req1) assert.Equal(t, 200, w1.Code) w2 := httptest.NewRecorder() req2 := httptest.NewRequest("GET", "/notfound", nil) router.ServeHTTP(w2, req2) assert.Equal(t, 404, w2.Code) w3 := httptest.NewRecorder() req3 := httptest.NewRequest("GET", "/error", nil) router.ServeHTTP(w3, req3) assert.Equal(t, 500, w3.Code) } func TestMetricsMiddleware_DifferentMethods(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Metrics()) router.GET("/resource", func(c *gin.Context) { c.JSON(200, gin.H{"method": "GET"}) }) router.POST("/resource", func(c *gin.Context) { c.JSON(201, gin.H{"method": "POST"}) }) router.PUT("/resource", func(c *gin.Context) { c.JSON(200, gin.H{"method": "PUT"}) }) router.DELETE("/resource", func(c *gin.Context) { c.JSON(204, gin.H{"method": "DELETE"}) }) // Tester différentes méthodes HTTP methods := []struct { method string path string status int }{ {"GET", "/resource", 200}, {"POST", "/resource", 201}, {"PUT", "/resource", 200}, {"DELETE", "/resource", 204}, } for _, m := range methods { w := httptest.NewRecorder() req := httptest.NewRequest(m.method, m.path, nil) router.ServeHTTP(w, req) assert.Equal(t, m.status, w.Code) } } func TestMetricsMiddleware_DurationMeasurement(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Metrics()) router.GET("/slow", func(c *gin.Context) { time.Sleep(50 * time.Millisecond) c.JSON(200, gin.H{"ok": true}) }) start := time.Now() w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/slow", nil) router.ServeHTTP(w, req) duration := time.Since(start) assert.Equal(t, 200, w.Code) assert.GreaterOrEqual(t, duration, 50*time.Millisecond, "Should measure at least the sleep duration") } func TestMetricsMiddleware_EmptyPath(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Metrics()) // Route sans nom de route défini router.Any("/unknown/*path", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/unknown/test", nil) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) // Le path devrait être l'URL path si FullPath est vide } func TestMetricsMiddleware_MultipleRequests(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Metrics()) router.GET("/test", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) }) // Faire plusieurs requêtes for i := 0; i < 5; i++ { w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) } // Vérifier que les métriques sont accumulées registry := prometheus.DefaultRegisterer.(*prometheus.Registry) metricFamilies, err := registry.Gather() require.NoError(t, err) totalRequests := 0.0 for _, mf := range metricFamilies { if *mf.Name == "veza_gin_http_requests_total" { for _, metric := range mf.Metric { if metric.Counter != nil { // Somme toutes les valeurs de counter pour cette métrique totalRequests += *metric.Counter.Value } } } } // Au moins 5 requêtes devraient être comptées au total // (les métriques sont groupées par labels, donc on somme toutes les valeurs) assert.GreaterOrEqual(t, totalRequests, float64(5), "Should have recorded at least 5 requests") } func TestMetricsMiddleware_LabelsCorrectness(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Metrics()) router.GET("/api/v1/users/:id", func(c *gin.Context) { c.JSON(200, gin.H{"id": c.Param("id")}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/api/v1/users/123", nil) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) // Vérifier que les labels sont corrects registry := prometheus.DefaultRegisterer.(*prometheus.Registry) metricFamilies, err := registry.Gather() require.NoError(t, err) for _, mf := range metricFamilies { if *mf.Name == "veza_http_requests_total" { for _, metric := range mf.Metric { method := "" path := "" status := "" for _, label := range metric.Label { switch *label.Name { case "method": method = *label.Value case "path": path = *label.Value case "status": status = *label.Value } } if method == "GET" && path == "/api/v1/users/:id" { assert.Equal(t, "200", status) } } } } } func TestMetricsMiddleware_HistogramBuckets(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Metrics()) router.GET("/test", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) // Vérifier que l'histogramme est correctement configuré registry := prometheus.DefaultRegisterer.(*prometheus.Registry) metricFamilies, err := registry.Gather() require.NoError(t, err) for _, mf := range metricFamilies { if *mf.Name == "veza_http_request_duration_seconds" { assert.Equal(t, dto.MetricType_HISTOGRAM, *mf.Type) assert.Greater(t, len(mf.Metric), 0) } } }