veza/veza-backend-api/internal/common/validation_test.go
senke b6c004319c
Some checks failed
Backend API CI / test-unit (push) Failing after 0s
Backend API CI / test-integration (push) Failing after 0s
v0.9.2
2026-03-05 19:27:34 +01:00

261 lines
7 KiB
Go

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)
}