package middleware import ( "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestTracing_GeneratesTraceID(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Tracing()) router.GET("/test", func(c *gin.Context) { traceID, exists := c.Get(TraceIDKey) require.True(t, exists) c.JSON(200, gin.H{"trace_id": traceID}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) // Vérifier que le header X-Trace-ID est présent dans la réponse traceIDHeader := w.Header().Get(TraceIDHeader) assert.NotEmpty(t, traceIDHeader, "X-Trace-ID header should be present") // Vérifier que c'est un UUID valide _, err := uuid.Parse(traceIDHeader) assert.NoError(t, err, "Trace ID should be a valid UUID") // Vérifier dans la réponse JSON var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, traceIDHeader, response["trace_id"]) } func TestTracing_PropagatesTraceID(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Tracing()) router.GET("/test", func(c *gin.Context) { traceID := GetTraceID(c) c.JSON(200, gin.H{"trace_id": traceID}) }) // Générer un trace ID existant existingTraceID := uuid.New().String() w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) req.Header.Set(TraceIDHeader, existingTraceID) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) // Vérifier que le trace ID propagé est réutilisé traceIDHeader := w.Header().Get(TraceIDHeader) assert.Equal(t, existingTraceID, traceIDHeader, "Trace ID should be propagated") var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, existingTraceID, response["trace_id"]) } func TestTracing_GeneratesSpanID(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Tracing()) router.GET("/test", func(c *gin.Context) { spanID := GetSpanID(c) c.JSON(200, gin.H{"span_id": spanID}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) // Vérifier que le header X-Span-ID est présent spanIDHeader := w.Header().Get(SpanIDHeader) assert.NotEmpty(t, spanIDHeader, "X-Span-ID header should be present") // Vérifier que c'est un UUID valide _, err := uuid.Parse(spanIDHeader) assert.NoError(t, err, "Span ID should be a valid UUID") var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, spanIDHeader, response["span_id"]) } func TestTracing_PropagatesSpanID(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Tracing()) router.GET("/test", func(c *gin.Context) { spanID := GetSpanID(c) c.JSON(200, gin.H{"span_id": spanID}) }) // Générer un span ID existant existingSpanID := uuid.New().String() w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) req.Header.Set(SpanIDHeader, existingSpanID) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) // Vérifier que le span ID propagé est réutilisé spanIDHeader := w.Header().Get(SpanIDHeader) assert.Equal(t, existingSpanID, spanIDHeader, "Span ID should be propagated") var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, existingSpanID, response["span_id"]) } func TestTracing_UniqueTraceIDs(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Tracing()) router.GET("/test", func(c *gin.Context) { traceID := GetTraceID(c) c.JSON(200, gin.H{"trace_id": traceID}) }) // Générer plusieurs requêtes et vérifier que chaque trace ID est unique traceIDs := make(map[string]bool) for i := 0; i < 10; i++ { w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) traceIDHeader := w.Header().Get(TraceIDHeader) assert.False(t, traceIDs[traceIDHeader], "Trace ID should be unique") traceIDs[traceIDHeader] = true } assert.Equal(t, 10, len(traceIDs), "Should have 10 unique trace IDs") } func TestTracing_ContextKeys(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Tracing()) router.GET("/test", func(c *gin.Context) { traceID, traceExists := c.Get(TraceIDKey) spanID, spanExists := c.Get(SpanIDKey) assert.True(t, traceExists, "Trace ID should be in context") assert.True(t, spanExists, "Span ID should be in context") assert.NotEmpty(t, traceID) assert.NotEmpty(t, spanID) c.JSON(200, gin.H{ "trace_id": traceID, "span_id": spanID, }) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } func TestGetTraceID(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Tracing()) router.GET("/test", func(c *gin.Context) { traceID := GetTraceID(c) assert.NotEmpty(t, traceID) // Tester avec un contexte vide (devrait retourner chaîne vide) // Ne pas créer un Context vide car Request serait nil et causerait un panic // À la place, tester avec un contexte qui n'a pas de trace ID w2 := httptest.NewRecorder() emptyCtx, _ := gin.CreateTestContext(w2) emptyCtx.Request = httptest.NewRequest("GET", "/", nil) emptyTraceID := GetTraceID(emptyCtx) assert.Empty(t, emptyTraceID) c.JSON(200, gin.H{"trace_id": traceID}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } func TestGetSpanID(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Tracing()) router.GET("/test", func(c *gin.Context) { spanID := GetSpanID(c) assert.NotEmpty(t, spanID) // Tester avec un contexte vide (devrait retourner chaîne vide) // Ne pas créer un Context vide car Request serait nil et causerait un panic // À la place, tester avec un contexte qui n'a pas de span ID w2 := httptest.NewRecorder() emptyCtx, _ := gin.CreateTestContext(w2) emptyCtx.Request = httptest.NewRequest("GET", "/", nil) emptySpanID := GetSpanID(emptyCtx) assert.Empty(t, emptySpanID) c.JSON(200, gin.H{"span_id": spanID}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } func TestTracing_W3CTraceContextCompatible(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Tracing()) router.GET("/test", func(c *gin.Context) { traceID := GetTraceID(c) c.JSON(200, gin.H{"trace_id": traceID}) }) // Tester avec un trace ID W3C Trace Context format (16 hex digits) // Le format W3C permet traceparent: 00-{trace_id}-{span_id}-01 // Ici on teste juste que notre UUID est compatible (peut être utilisé dans traceparent) w3cTraceID := "4bf92f3577b34da6a3ce929d0e0e4736" // 32 hex digits (128 bits, comme UUID) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) req.Header.Set(TraceIDHeader, w3cTraceID) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) traceIDHeader := w.Header().Get(TraceIDHeader) assert.Equal(t, w3cTraceID, traceIDHeader, "Should accept W3C-compatible trace ID") }