184 lines
5.6 KiB
Go
184 lines
5.6 KiB
Go
|
|
//go:build !integration
|
||
|
|
// +build !integration
|
||
|
|
|
||
|
|
package middleware_test
|
||
|
|
|
||
|
|
import (
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"runtime"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/gin-gonic/gin"
|
||
|
|
"github.com/stretchr/testify/assert"
|
||
|
|
|
||
|
|
"veza-backend-api/internal/middleware"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TestTimeoutMiddleware_NoGoroutineLeak vérifie qu'il n'y a pas de fuite de goroutines après des timeouts
|
||
|
|
func TestTimeoutMiddleware_NoGoroutineLeak(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
|
||
|
|
// Capturer le nombre initial de goroutines
|
||
|
|
initialGoroutines := runtime.NumGoroutine()
|
||
|
|
|
||
|
|
// Créer un router avec timeout middleware
|
||
|
|
router := gin.New()
|
||
|
|
router.Use(middleware.Timeout(10 * time.Millisecond))
|
||
|
|
|
||
|
|
// Handler qui dépasse le timeout
|
||
|
|
router.GET("/slow", func(c *gin.Context) {
|
||
|
|
// Simuler un travail qui dépasse le timeout
|
||
|
|
time.Sleep(100 * time.Millisecond)
|
||
|
|
// Vérifier que le contexte est annulé
|
||
|
|
select {
|
||
|
|
case <-c.Request.Context().Done():
|
||
|
|
// Contexte annulé, ne pas écrire de réponse
|
||
|
|
return
|
||
|
|
case <-time.After(10 * time.Millisecond):
|
||
|
|
// Si on arrive ici, le contexte n'a pas été annulé (ne devrait pas arriver)
|
||
|
|
c.JSON(http.StatusOK, gin.H{"status": "should not reach here"})
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// Exécuter plusieurs requêtes qui vont timeout
|
||
|
|
const numRequests = 10
|
||
|
|
for i := 0; i < numRequests; i++ {
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
req := httptest.NewRequest("GET", "/slow", nil)
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
// Vérifier que le timeout est respecté
|
||
|
|
assert.Equal(t, http.StatusGatewayTimeout, w.Code)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Attendre un peu pour que les goroutines se nettoient
|
||
|
|
time.Sleep(200 * time.Millisecond)
|
||
|
|
|
||
|
|
// Forcer un GC pour nettoyer les ressources
|
||
|
|
runtime.GC()
|
||
|
|
time.Sleep(50 * time.Millisecond)
|
||
|
|
|
||
|
|
// Vérifier que le nombre de goroutines n'a pas augmenté de manière significative
|
||
|
|
// On accepte une marge de 2 goroutines pour les goroutines système
|
||
|
|
finalGoroutines := runtime.NumGoroutine()
|
||
|
|
goroutineIncrease := finalGoroutines - initialGoroutines
|
||
|
|
|
||
|
|
// Le nombre de goroutines ne devrait pas augmenter de plus de 2 (marge pour les goroutines système)
|
||
|
|
assert.LessOrEqual(t, goroutineIncrease, 2,
|
||
|
|
"Goroutine leak detected: initial=%d, final=%d, increase=%d",
|
||
|
|
initialGoroutines, finalGoroutines, goroutineIncrease)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestTimeoutMiddleware_HandlerRespectsContext vérifie que le handler respecte l'annulation du contexte
|
||
|
|
func TestTimeoutMiddleware_HandlerRespectsContext(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
|
||
|
|
router := gin.New()
|
||
|
|
router.Use(middleware.Timeout(10 * time.Millisecond))
|
||
|
|
|
||
|
|
handlerExecuted := false
|
||
|
|
|
||
|
|
router.GET("/test", func(c *gin.Context) {
|
||
|
|
handlerExecuted = true
|
||
|
|
// Attendre que le contexte soit annulé
|
||
|
|
select {
|
||
|
|
case <-c.Request.Context().Done():
|
||
|
|
// Contexte annulé, ne pas écrire de réponse
|
||
|
|
return
|
||
|
|
case <-time.After(100 * time.Millisecond):
|
||
|
|
// Timeout du test lui-même
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
req := httptest.NewRequest("GET", "/test", nil)
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
// Vérifier que le handler a été exécuté
|
||
|
|
assert.True(t, handlerExecuted, "Handler should have been executed")
|
||
|
|
|
||
|
|
// Vérifier que le contexte a été annulé
|
||
|
|
// Note: Le handler peut ne pas avoir eu le temps de détecter l'annulation
|
||
|
|
// mais le middleware doit avoir retourné 504
|
||
|
|
assert.Equal(t, http.StatusGatewayTimeout, w.Code)
|
||
|
|
|
||
|
|
// Attendre un peu pour que le handler détecte l'annulation
|
||
|
|
time.Sleep(50 * time.Millisecond)
|
||
|
|
// Le contexte devrait être annulé (mais le handler peut ne pas avoir eu le temps de le détecter)
|
||
|
|
// L'important est que le middleware retourne 504 et que les ressources soient nettoyées
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestTimeoutMiddleware_MultipleConcurrentRequests vérifie qu'il n'y a pas de fuite avec plusieurs requêtes concurrentes
|
||
|
|
func TestTimeoutMiddleware_MultipleConcurrentRequests(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
|
||
|
|
initialGoroutines := runtime.NumGoroutine()
|
||
|
|
|
||
|
|
router := gin.New()
|
||
|
|
router.Use(middleware.Timeout(10 * time.Millisecond))
|
||
|
|
|
||
|
|
router.GET("/slow", func(c *gin.Context) {
|
||
|
|
time.Sleep(100 * time.Millisecond)
|
||
|
|
select {
|
||
|
|
case <-c.Request.Context().Done():
|
||
|
|
return
|
||
|
|
default:
|
||
|
|
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// Lancer plusieurs requêtes concurrentes
|
||
|
|
const numConcurrent = 5
|
||
|
|
done := make(chan bool, numConcurrent)
|
||
|
|
|
||
|
|
for i := 0; i < numConcurrent; i++ {
|
||
|
|
go func() {
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
req := httptest.NewRequest("GET", "/slow", nil)
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
assert.Equal(t, http.StatusGatewayTimeout, w.Code)
|
||
|
|
done <- true
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Attendre que toutes les requêtes soient terminées
|
||
|
|
for i := 0; i < numConcurrent; i++ {
|
||
|
|
<-done
|
||
|
|
}
|
||
|
|
|
||
|
|
// Attendre que les goroutines se nettoient
|
||
|
|
time.Sleep(200 * time.Millisecond)
|
||
|
|
runtime.GC()
|
||
|
|
time.Sleep(50 * time.Millisecond)
|
||
|
|
|
||
|
|
finalGoroutines := runtime.NumGoroutine()
|
||
|
|
goroutineIncrease := finalGoroutines - initialGoroutines
|
||
|
|
|
||
|
|
// Le nombre de goroutines ne devrait pas augmenter de plus de 2
|
||
|
|
assert.LessOrEqual(t, goroutineIncrease, 2,
|
||
|
|
"Goroutine leak detected with concurrent requests: initial=%d, final=%d, increase=%d",
|
||
|
|
initialGoroutines, finalGoroutines, goroutineIncrease)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestTimeoutMiddleware_FastHandler_NoTimeout vérifie qu'un handler rapide ne déclenche pas de timeout
|
||
|
|
func TestTimeoutMiddleware_FastHandler_NoTimeout(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
|
||
|
|
router := gin.New()
|
||
|
|
router.Use(middleware.Timeout(100 * time.Millisecond))
|
||
|
|
|
||
|
|
router.GET("/fast", func(c *gin.Context) {
|
||
|
|
time.Sleep(10 * time.Millisecond)
|
||
|
|
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||
|
|
})
|
||
|
|
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
req := httptest.NewRequest("GET", "/fast", nil)
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
// Vérifier que le handler a réussi (pas de timeout)
|
||
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
||
|
|
}
|