359 lines
9 KiB
Markdown
359 lines
9 KiB
Markdown
# ✅ P1-002 — VALIDATION INPUTS SYSTÉMATIQUE (DTO + HELPER)
|
|
|
|
**Date**: 2025-12-12
|
|
**Objectif**: Rendre la validation homogène sur les endpoints critiques avec DTOs + helper centralisé
|
|
|
|
---
|
|
|
|
## 📋 RÉSUMÉ
|
|
|
|
✅ **Helper centralisé créé** : `common.BindAndValidateJSON()` utilisable par tous les handlers
|
|
✅ **DTOs complétés** : Tags `validate` ajoutés aux DTOs existants (en plus de `binding`)
|
|
✅ **Handlers mis à jour** : `TrackHandler` utilise maintenant le helper centralisé
|
|
✅ **Tests ajoutés** : Tests unitaires pour cas invalides, limites, UUID invalides
|
|
✅ **Format d'erreur standardisé** : HTTP 400 avec détails structurés
|
|
|
|
---
|
|
|
|
## 📁 FICHIERS MODIFIÉS
|
|
|
|
### 1. `internal/common/validation.go`
|
|
- ✅ Amélioration de `BindAndValidateJSON()` pour utiliser le validator centralisé
|
|
- ✅ Gestion d'erreurs améliorée (JSON malformé, body trop gros, types incorrects)
|
|
- ✅ Validation avec `go-playground/validator` après binding Gin
|
|
- ✅ Format d'erreur standardisé via `response.ValidationError()`
|
|
|
|
### 2. `internal/core/track/handler.go`
|
|
- ✅ Ajout import `common` pour utiliser `BindAndValidateJSON()`
|
|
- ✅ Remplacement de tous les appels `ShouldBindJSON()` par `BindAndValidateJSON()`
|
|
- ✅ Ajout tags `validate` aux DTOs :
|
|
- `InitiateChunkedUploadRequest`
|
|
- `UpdateTrackRequest`
|
|
- `CompleteChunkedUploadRequest`
|
|
- `BatchDeleteRequest`
|
|
- `BatchUpdateRequest`
|
|
- `CreateShareRequest`
|
|
- `StreamCallbackRequest`
|
|
|
|
### 3. `internal/handlers/profile_handler.go`
|
|
- ✅ Ajout tags `validate` à `UpdateProfileRequest`
|
|
- ✅ Utilise déjà `BindAndValidateJSON()` de `CommonHandler` (pas de changement)
|
|
|
|
### 4. `internal/common/validation_test.go` (nouveau)
|
|
- ✅ Tests unitaires complets :
|
|
- Requête valide
|
|
- Champ requis manquant
|
|
- UUID invalide
|
|
- String trop longue
|
|
- Nombre hors limites
|
|
- String vide
|
|
- JSON malformé
|
|
- Body trop gros
|
|
|
|
---
|
|
|
|
## 🎯 ENDPOINTS COUVERTS
|
|
|
|
### TrackHandler (`/api/v1/tracks/*`)
|
|
|
|
| Endpoint | Méthode | DTO | Validation |
|
|
|----------|---------|-----|------------|
|
|
| `/tracks/initiate` | POST | `InitiateChunkedUploadRequest` | ✅ Tags `validate` + helper |
|
|
| `/tracks/{id}` | PUT | `UpdateTrackRequest` | ✅ Tags `validate` + helper |
|
|
| `/tracks/complete` | POST | `CompleteChunkedUploadRequest` | ✅ Tags `validate` + helper |
|
|
| `/tracks/batch/delete` | POST | `BatchDeleteRequest` | ✅ Tags `validate` + helper |
|
|
| `/tracks/batch/update` | POST | `BatchUpdateRequest` | ✅ Tags `validate` + helper |
|
|
| `/tracks/{id}/share` | POST | `CreateShareRequest` | ✅ Tags `validate` + helper |
|
|
| `/tracks/stream/callback` | POST | `StreamCallbackRequest` | ✅ Tags `validate` + helper |
|
|
|
|
### ProfileHandler (`/api/v1/users/*`)
|
|
|
|
| Endpoint | Méthode | DTO | Validation |
|
|
|----------|---------|-----|------------|
|
|
| `/users/{id}` | PUT | `UpdateProfileRequest` | ✅ Tags `validate` + helper (déjà utilisé) |
|
|
|
|
### PlaylistHandler (`/api/v1/playlists/*`)
|
|
|
|
| Endpoint | Méthode | DTO | Validation |
|
|
|----------|---------|-----|------------|
|
|
| `/playlists` | POST | `CreatePlaylistRequest` | ✅ Déjà utilise `BindAndValidateJSON()` |
|
|
| `/playlists/{id}` | PUT | `UpdatePlaylistRequest` | ✅ Déjà utilise `BindAndValidateJSON()` |
|
|
| `/playlists/{id}/reorder` | POST | `ReorderTracksRequest` | ✅ Déjà utilise `BindAndValidateJSON()` |
|
|
|
|
---
|
|
|
|
## 🚫 ENDPOINTS HORS SCOPE (justifiés)
|
|
|
|
### 1. Endpoints avec `ShouldBind()` (multipart/form-data)
|
|
- **`/tracks/chunk`** (POST) : `UploadChunkRequest` utilise `form` tags (multipart)
|
|
- **Justification** : Validation différente (fichiers, pas JSON)
|
|
- **Action** : Hors scope P1-002 (validation JSON uniquement)
|
|
|
|
### 2. Endpoints avec `ShouldBind()` (query params)
|
|
- Endpoints avec query params uniquement
|
|
- **Justification** : Pas de body JSON, validation différente
|
|
- **Action** : Hors scope P1-002
|
|
|
|
### 3. Handlers dans `.backup-pre-uuid-migration/`
|
|
- **Justification** : Fichiers de backup, non utilisés
|
|
- **Action** : Ignorés
|
|
|
|
---
|
|
|
|
## 📊 EXEMPLES D'ERREURS JSON RETOURNÉES
|
|
|
|
### Cas 1 : Champ requis manquant
|
|
|
|
**Requête** :
|
|
```json
|
|
{
|
|
"year": 2020
|
|
// title manquant
|
|
}
|
|
```
|
|
|
|
**Réponse** (HTTP 400) :
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": {
|
|
"code": 400,
|
|
"message": "Validation failed",
|
|
"details": {
|
|
"title": "title is required"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Cas 2 : UUID invalide
|
|
|
|
**Requête** :
|
|
```json
|
|
{
|
|
"title": "Test Track",
|
|
"track_id": "invalid-uuid"
|
|
}
|
|
```
|
|
|
|
**Réponse** (HTTP 400) :
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": {
|
|
"code": 400,
|
|
"message": "Validation failed",
|
|
"details": {
|
|
"track_id": "track_id must be a valid UUID"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Cas 3 : String trop longue
|
|
|
|
**Requête** :
|
|
```json
|
|
{
|
|
"title": "A" * 300, // 300 caractères > max 255
|
|
"track_id": "550e8400-e29b-41d4-a716-446655440000"
|
|
}
|
|
```
|
|
|
|
**Réponse** (HTTP 400) :
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": {
|
|
"code": 400,
|
|
"message": "Validation failed",
|
|
"details": {
|
|
"title": "title must be at most 255 characters"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Cas 4 : Nombre hors limites
|
|
|
|
**Requête** :
|
|
```json
|
|
{
|
|
"title": "Test Track",
|
|
"year": 1800, // < min 1900
|
|
"track_id": "550e8400-e29b-41d4-a716-446655440000"
|
|
}
|
|
```
|
|
|
|
**Réponse** (HTTP 400) :
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": {
|
|
"code": 400,
|
|
"message": "Validation failed",
|
|
"details": {
|
|
"year": "year must be greater than or equal to 1900"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Cas 5 : JSON malformé
|
|
|
|
**Requête** :
|
|
```json
|
|
{
|
|
"title": "Test",
|
|
"track_id": invalid
|
|
}
|
|
```
|
|
|
|
**Réponse** (HTTP 400) :
|
|
```json
|
|
{
|
|
"error": "Invalid JSON syntax at offset 35: invalid character 'i' looking for beginning of value"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🧪 TESTS
|
|
|
|
### Tests unitaires
|
|
|
|
```bash
|
|
go test ./internal/common -run TestBindAndValidateJSON -v -count=1
|
|
```
|
|
|
|
**Résultat** : ✅ **Tous les tests passent**
|
|
- `TestBindAndValidateJSON_ValidRequest` — PASS
|
|
- `TestBindAndValidateJSON_MissingRequiredField` — PASS
|
|
- `TestBindAndValidateJSON_InvalidUUID` — PASS
|
|
- `TestBindAndValidateJSON_StringTooLong` — PASS
|
|
- `TestBindAndValidateJSON_NumberOutOfRange` — PASS
|
|
- `TestBindAndValidateJSON_EmptyString` — PASS
|
|
- `TestBindAndValidateJSON_InvalidJSON` — PASS
|
|
- `TestBindAndValidateJSON_BodyTooLarge` — PASS
|
|
|
|
### Tests complets
|
|
|
|
```bash
|
|
go test ./... -count=1
|
|
```
|
|
|
|
**Résultat** : Tests unitaires P1-002 passent. Les tests qui échouent sont préexistants (database, handlers nécessitant DB).
|
|
|
|
---
|
|
|
|
## 🔧 HELPER CENTRALISÉ
|
|
|
|
### `common.BindAndValidateJSON(c *gin.Context, obj interface{}) bool`
|
|
|
|
**Fonctionnalités** :
|
|
1. ✅ Vérifie la taille du body (max 10MB)
|
|
2. ✅ Parse le JSON avec `ShouldBindJSON` (Gin)
|
|
3. ✅ Valide avec `go-playground/validator` (tags `validate`)
|
|
4. ✅ Retourne `false` si erreur (erreur déjà envoyée au client avec HTTP 400)
|
|
5. ✅ Format d'erreur standardisé via `response.ValidationError()`
|
|
|
|
**Usage** :
|
|
```go
|
|
var req UpdateTrackRequest
|
|
if !common.BindAndValidateJSON(c, &req) {
|
|
return // Erreur déjà envoyée au client
|
|
}
|
|
// req est validé et prêt à être utilisé
|
|
```
|
|
|
|
---
|
|
|
|
## 📝 TAGS VALIDATE AJOUTÉS
|
|
|
|
### Exemples de DTOs mis à jour
|
|
|
|
#### `UpdateTrackRequest`
|
|
```go
|
|
type UpdateTrackRequest struct {
|
|
Title *string `json:"title" binding:"omitempty,min=1,max=255" validate:"omitempty,min=1,max=255"`
|
|
Artist *string `json:"artist" binding:"omitempty,max=255" validate:"omitempty,max=255"`
|
|
Year *int `json:"year" binding:"omitempty,min=1900,max=2100" validate:"omitempty,min=1900,max=2100"`
|
|
}
|
|
```
|
|
|
|
#### `InitiateChunkedUploadRequest`
|
|
```go
|
|
type InitiateChunkedUploadRequest struct {
|
|
TotalChunks int `json:"total_chunks" binding:"required,min=1" validate:"required,min=1"`
|
|
TotalSize int64 `json:"total_size" binding:"required,min=1" validate:"required,min=1"`
|
|
Filename string `json:"filename" binding:"required" validate:"required"`
|
|
}
|
|
```
|
|
|
|
#### `BatchDeleteRequest`
|
|
```go
|
|
type BatchDeleteRequest struct {
|
|
TrackIDs []string `json:"track_ids" binding:"required" validate:"required,min=1,dive,uuid"`
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## ✅ VALIDATION
|
|
|
|
### Compilation
|
|
|
|
```bash
|
|
go build ./...
|
|
```
|
|
|
|
**Résultat** : ✅ **Compilation réussie**
|
|
|
|
### Tests unitaires
|
|
|
|
```bash
|
|
go test ./internal/common -run TestBindAndValidateJSON -v
|
|
```
|
|
|
|
**Résultat** : ✅ **Tous les tests passent (8/8)**
|
|
|
|
### Tests complets
|
|
|
|
```bash
|
|
go test ./... -count=1
|
|
```
|
|
|
|
**Résultat** : Tests unitaires P1-002 passent. Les tests qui échouent sont préexistants.
|
|
|
|
---
|
|
|
|
## 🎯 OBJECTIFS ATTEINTS
|
|
|
|
- ✅ **Helper centralisé** : `common.BindAndValidateJSON()` utilisable par tous les handlers
|
|
- ✅ **DTOs avec tags validate** : Tous les DTOs critiques ont des tags `validate`
|
|
- ✅ **Pas de validation inline** : Tous les handlers utilisent le helper
|
|
- ✅ **Format d'erreur standardisé** : HTTP 400 avec détails structurés
|
|
- ✅ **Tests complets** : Cas invalides, limites, UUID invalides, body trop gros
|
|
|
|
---
|
|
|
|
## 📋 COMMANDES DE VALIDATION
|
|
|
|
### Tests unitaires
|
|
```bash
|
|
go test ./internal/common -run TestBindAndValidateJSON -v -count=1
|
|
```
|
|
|
|
### Tests complets
|
|
```bash
|
|
go test ./... -count=1
|
|
```
|
|
|
|
### Compilation
|
|
```bash
|
|
go build ./...
|
|
```
|
|
|
|
---
|
|
|
|
**Statut final** : ✅ **P1-002 IMPLÉMENTÉ ET VALIDÉ**
|