package common import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "os" "testing" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" ) // TestGetMaxJSONBodySize vérifie la limite par défaut et l'override via env (TASK-SEC-005) func TestGetMaxJSONBodySize(t *testing.T) { defer os.Unsetenv("MAX_JSON_BODY_SIZE") // Défaut 1MB os.Unsetenv("MAX_JSON_BODY_SIZE") assert.Equal(t, int64(1024*1024), GetMaxJSONBodySize()) // Override via env os.Setenv("MAX_JSON_BODY_SIZE", "2097152") // 2MB assert.Equal(t, int64(2097152), GetMaxJSONBodySize()) } // TestRequest est un DTO de test avec validation type TestRequest struct { Title string `json:"title" binding:"required,min=1,max=255" validate:"required,min=1,max=255"` Year int `json:"year" binding:"omitempty,min=1900,max=2100" validate:"omitempty,min=1900,max=2100"` TrackID uuid.UUID `json:"track_id" binding:"required" validate:"required,uuid"` IsPublic *bool `json:"is_public"` } // TestBindAndValidateJSON_ValidRequest vérifie qu'une requête valide passe la validation func TestBindAndValidateJSON_ValidRequest(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.POST("/test", func(c *gin.Context) { var req TestRequest if !BindAndValidateJSON(c, &req) { return } c.JSON(http.StatusOK, gin.H{"success": true, "title": req.Title}) }) trackID := uuid.New() reqBody := map[string]interface{}{ "title": "Test Track", "year": 2020, "track_id": trackID.String(), "is_public": true, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} json.Unmarshal(w.Body.Bytes(), &response) assert.Equal(t, true, response["success"]) } // TestBindAndValidateJSON_MissingRequiredField vérifie qu'un champ requis manquant est rejeté func TestBindAndValidateJSON_MissingRequiredField(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.POST("/test", func(c *gin.Context) { var req TestRequest if !BindAndValidateJSON(c, &req) { return } c.JSON(http.StatusOK, gin.H{"success": true}) }) reqBody := map[string]interface{}{ "year": 2020, // title manquant } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestBindAndValidateJSON_InvalidUUID vérifie qu'un UUID invalide est rejeté func TestBindAndValidateJSON_InvalidUUID(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.POST("/test", func(c *gin.Context) { var req TestRequest if !BindAndValidateJSON(c, &req) { return } c.JSON(http.StatusOK, gin.H{"success": true}) }) reqBody := map[string]interface{}{ "title": "Test Track", "track_id": "invalid-uuid", } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestBindAndValidateJSON_StringTooLong vérifie qu'une string trop longue est rejetée func TestBindAndValidateJSON_StringTooLong(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.POST("/test", func(c *gin.Context) { var req TestRequest if !BindAndValidateJSON(c, &req) { return } c.JSON(http.StatusOK, gin.H{"success": true}) }) trackID := uuid.New() reqBody := map[string]interface{}{ "title": string(make([]byte, 300)), // 300 caractères > max 255 "track_id": trackID.String(), } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestBindAndValidateJSON_NumberOutOfRange vérifie qu'un nombre hors limites est rejeté func TestBindAndValidateJSON_NumberOutOfRange(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.POST("/test", func(c *gin.Context) { var req TestRequest if !BindAndValidateJSON(c, &req) { return } c.JSON(http.StatusOK, gin.H{"success": true}) }) trackID := uuid.New() reqBody := map[string]interface{}{ "title": "Test Track", "year": 1800, // < min 1900 "track_id": trackID.String(), } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestBindAndValidateJSON_EmptyString vérifie qu'une string vide est rejetée pour un champ required func TestBindAndValidateJSON_EmptyString(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.POST("/test", func(c *gin.Context) { var req TestRequest if !BindAndValidateJSON(c, &req) { return } c.JSON(http.StatusOK, gin.H{"success": true}) }) trackID := uuid.New() reqBody := map[string]interface{}{ "title": "", // String vide "track_id": trackID.String(), } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestBindAndValidateJSON_InvalidJSON vérifie qu'un JSON malformé est rejeté func TestBindAndValidateJSON_InvalidJSON(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.POST("/test", func(c *gin.Context) { var req TestRequest if !BindAndValidateJSON(c, &req) { return } c.JSON(http.StatusOK, gin.H{"success": true}) }) invalidJSON := []byte(`{"title": "Test", "track_id": invalid}`) req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(invalidJSON)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestBindAndValidateJSON_BodyTooLarge vérifie qu'un body trop gros est rejeté (TASK-SEC-005: 1MB défaut) func TestBindAndValidateJSON_BodyTooLarge(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.POST("/test", func(c *gin.Context) { var req TestRequest if !BindAndValidateJSON(c, &req) { return } c.JSON(http.StatusOK, gin.H{"success": true}) }) // Créer un body > limite (1MB par défaut) largeBody := make([]byte, GetMaxJSONBodySize()+1) req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(largeBody)) req.Header.Set("Content-Type", "application/json") req.ContentLength = int64(len(largeBody)) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) }