Réorganise la documentation du projet selon les principes du Domain-Driven Design (DDD) pour améliorer la cohésion, la maintenabilité et l'alignement avec l'architecture modulaire du backend. **Structure cible:** ``` docs/domains/ ├── README.md (Context Map) ├── _shared/ (Core Domain) ├── recommendation/ (Supporting Subdomain) ├── content/ (Supporting Subdomain) ├── moderation/ (Supporting Subdomain) ├── advertising/ (Generic Subdomain) ├── premium/ (Generic Subdomain) └── monetization/ (Generic Subdomain) ``` **Changements effectués:** Phase 1: Création de l'arborescence des 7 bounded contexts Phase 2: Déplacement des règles métier (01-19) vers domains/*/rules/ Phase 3: Déplacement des diagrammes d'entités vers domains/*/entities/ Phase 4: Déplacement des diagrammes flux/états/séquences vers domains/*/ Phase 5: Création des README.md pour chaque domaine Phase 6: Déplacement des features Gherkin vers domains/*/features/ Phase 7: Création du Context Map (domains/README.md) Phase 8: Mise à jour de mkdocs.yml pour la nouvelle navigation Phase 9: Correction automatique des liens internes (script fix-markdown-links.sh) Phase 10: Nettoyage de l'ancienne structure (regles-metier/, diagrammes/, features/) **Configuration des tests:** - Makefile: godog run docs/domains/*/features/ - scripts/generate-bdd-docs.py: features_dir → docs/domains **Avantages:** ✅ Cohésion forte: toute la doc d'un domaine au même endroit ✅ Couplage faible: domaines indépendants, dépendances explicites ✅ Navigabilité améliorée: README par domaine = entrée claire ✅ Alignement code/docs: miroir de backend/internal/ ✅ Onboarding facilité: exploration domaine par domaine ✅ Tests BDD intégrés: features au plus près des règles métier Voir docs/REFACTOR-DDD.md pour le plan complet.
403 lines
16 KiB
Gherkin
403 lines
16 KiB
Gherkin
# language: fr
|
|
|
|
Fonctionnalité: API - Création et gestion d'audio-guides multi-séquences
|
|
En tant que système backend
|
|
Je veux exposer des endpoints pour créer et gérer les audio-guides
|
|
Afin de permettre aux créateurs de publier des expériences guidées
|
|
|
|
Contexte:
|
|
Étant donné que l'API RoadWave est démarrée
|
|
Et que le créateur "guide@example.com" est authentifié avec un token JWT valide
|
|
Et que son compte est vérifié (email_verified: true)
|
|
|
|
# 16.1.2 - Endpoints de création
|
|
|
|
Scénario: POST /api/v1/audio-guides - Création d'un audio-guide
|
|
Étant donné la requête suivante:
|
|
"""json
|
|
{
|
|
"title": "Safari du Paugre",
|
|
"description": "Découvrez les animaux du parc en voiture sur un circuit de 5km",
|
|
"mode": "voiture",
|
|
"vitesse_recommandee": "30-50 km/h",
|
|
"tags": ["nature", "animaux", "famille"],
|
|
"classification_age": "tout_public",
|
|
"zone_diffusion": {
|
|
"type": "polygon",
|
|
"coordinates": [[2.5678, 43.1234], [2.5690, 43.1245], [2.5700, 43.1250]]
|
|
}
|
|
}
|
|
"""
|
|
Quand je fais un POST sur "/api/v1/audio-guides"
|
|
Alors le code HTTP de réponse est 201
|
|
Et le corps de réponse contient:
|
|
| champ | valeur |
|
|
| id | UUID généré |
|
|
| status | draft |
|
|
| creator_id | ID du créateur |
|
|
| sequences_count | 0 |
|
|
| created_at | Timestamp actuel |
|
|
|
|
Scénario: Validation du titre (longueur 5-100 caractères)
|
|
Étant donné la requête avec titre "ABC"
|
|
Quand je fais un POST sur "/api/v1/audio-guides"
|
|
Alors le code HTTP de réponse est 400
|
|
Et le message d'erreur est "title: doit contenir entre 5 et 100 caractères"
|
|
|
|
Scénario: Validation de la description (longueur 10-500 caractères)
|
|
Étant donné la requête avec description de 8 caractères
|
|
Quand je fais un POST sur "/api/v1/audio-guides"
|
|
Alors le code HTTP de réponse est 400
|
|
Et le message d'erreur est "description: doit contenir entre 10 et 500 caractères"
|
|
|
|
Plan du Scénario: Validation du mode de déplacement
|
|
Étant donné la requête avec mode "<mode>"
|
|
Quand je fais un POST sur "/api/v1/audio-guides"
|
|
Alors le code HTTP de réponse est <code>
|
|
|
|
Exemples:
|
|
| mode | code |
|
|
| pieton | 201 |
|
|
| voiture | 201 |
|
|
| velo | 201 |
|
|
| transport | 201 |
|
|
| avion | 400 |
|
|
| invalid | 400 |
|
|
|
|
Scénario: Validation tags (1-3 tags obligatoires)
|
|
Étant donné la requête avec 0 tags
|
|
Quand je fais un POST sur "/api/v1/audio-guides"
|
|
Alors le code HTTP de réponse est 400
|
|
Et le message d'erreur est "tags: minimum 1 tag requis, maximum 3"
|
|
|
|
Scénario: Validation classification âge
|
|
Étant donné la requête avec classification_age "invalide"
|
|
Quand je fais un POST sur "/api/v1/audio-guides"
|
|
Alors le code HTTP de réponse est 400
|
|
Et le message d'erreur contient "classification_age: valeurs autorisées [tout_public, 13+, 16+, 18+]"
|
|
|
|
# Ajout de séquences
|
|
|
|
Scénario: POST /api/v1/audio-guides/{id}/sequences - Ajout première séquence
|
|
Étant donné un audio-guide draft avec ID "ag_123"
|
|
Et la requête suivante:
|
|
"""json
|
|
{
|
|
"order": 1,
|
|
"title": "Introduction - Point d'accueil",
|
|
"audio_file": "base64_encoded_mp3_data",
|
|
"gps_point": {
|
|
"latitude": 43.1234,
|
|
"longitude": 2.5678
|
|
},
|
|
"rayon_declenchement": 30
|
|
}
|
|
"""
|
|
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/sequences"
|
|
Alors le code HTTP de réponse est 201
|
|
Et la séquence est créée avec:
|
|
| champ | valeur |
|
|
| sequence_id | UUID généré |
|
|
| order | 1 |
|
|
| duration | Calculée depuis audio |
|
|
| status | uploaded |
|
|
|
|
Scénario: Validation format audio (MP3, AAC, M4A uniquement)
|
|
Étant donné un audio-guide draft
|
|
Et un fichier audio au format WAV
|
|
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
|
|
Alors le code HTTP de réponse est 400
|
|
Et le message d'erreur est "audio_file: format non supporté. Formats acceptés: MP3, AAC, M4A"
|
|
|
|
Scénario: Validation taille audio (max 200 MB)
|
|
Étant donné un audio-guide draft
|
|
Et un fichier audio de 250 MB
|
|
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
|
|
Alors le code HTTP de réponse 413
|
|
Et le message d'erreur est "audio_file: taille maximale 200 MB dépassée"
|
|
|
|
Scénario: Validation durée audio (max 15 minutes)
|
|
Étant donné un audio-guide draft
|
|
Et un fichier audio de 18 minutes
|
|
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
|
|
Alors le code HTTP de réponse est 400
|
|
Et le message d'erreur est "audio_file: durée maximale 15 minutes dépassée"
|
|
|
|
Scénario: Point GPS obligatoire sauf mode piéton
|
|
Étant donné un audio-guide en mode "voiture"
|
|
Et une séquence sans gps_point
|
|
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
|
|
Alors le code HTTP de réponse est 400
|
|
Et le message d'erreur est "gps_point: obligatoire pour mode voiture"
|
|
|
|
Scénario: Point GPS optionnel en mode piéton
|
|
Étant donné un audio-guide en mode "pieton"
|
|
Et une séquence sans gps_point
|
|
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
|
|
Alors le code HTTP de réponse est 201
|
|
Et la séquence est créée sans point GPS
|
|
|
|
Plan du Scénario: Rayon de déclenchement par défaut selon mode
|
|
Étant donné un audio-guide en mode "<mode>"
|
|
Et une séquence sans rayon_declenchement spécifié
|
|
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
|
|
Alors le rayon par défaut appliqué est <rayon>
|
|
|
|
Exemples:
|
|
| mode | rayon |
|
|
| voiture | 30 |
|
|
| velo | 50 |
|
|
| transport | 100 |
|
|
|
|
Scénario: Validation rayon configurable (10-200m)
|
|
Étant donné un audio-guide
|
|
Et une séquence avec rayon_declenchement 250
|
|
Quand je fais un POST sur "/api/v1/audio-guides/{id}/sequences"
|
|
Alors le code HTTP de réponse est 400
|
|
Et le message d'erreur est "rayon_declenchement: doit être entre 10 et 200 mètres"
|
|
|
|
Scénario: Nombre maximum de séquences (50)
|
|
Étant donné un audio-guide avec 50 séquences
|
|
Quand je tente d'ajouter une 51ème séquence
|
|
Alors le code HTTP de réponse est 400
|
|
Et le message d'erreur est "Maximum 50 séquences par audio-guide atteint"
|
|
|
|
# Modification et réordonnancement
|
|
|
|
Scénario: PATCH /api/v1/audio-guides/{id}/sequences/{seq_id} - Modification séquence
|
|
Étant donné une séquence existante "seq_456"
|
|
Et la requête suivante:
|
|
"""json
|
|
{
|
|
"title": "Introduction - Point d'accueil (édité)",
|
|
"rayon_declenchement": 40
|
|
}
|
|
"""
|
|
Quand je fais un PATCH sur "/api/v1/audio-guides/ag_123/sequences/seq_456"
|
|
Alors le code HTTP de réponse est 200
|
|
Et les champs modifiés sont mis à jour
|
|
Et updated_at est mis à jour
|
|
|
|
Scénario: PUT /api/v1/audio-guides/{id}/sequences/reorder - Réordonnancement
|
|
Étant donné un audio-guide avec 5 séquences
|
|
Et la requête suivante:
|
|
"""json
|
|
{
|
|
"sequence_orders": [
|
|
{"sequence_id": "seq_1", "order": 1},
|
|
{"sequence_id": "seq_4", "order": 2},
|
|
{"sequence_id": "seq_2", "order": 3},
|
|
{"sequence_id": "seq_3", "order": 4},
|
|
{"sequence_id": "seq_5", "order": 5}
|
|
]
|
|
}
|
|
"""
|
|
Quand je fais un PUT sur "/api/v1/audio-guides/ag_123/sequences/reorder"
|
|
Alors le code HTTP de réponse est 200
|
|
Et l'ordre des séquences est mis à jour en base
|
|
|
|
Scénario: DELETE /api/v1/audio-guides/{id}/sequences/{seq_id} - Suppression séquence
|
|
Étant donné un audio-guide avec 3 séquences
|
|
Quand je fais un DELETE sur "/api/v1/audio-guides/ag_123/sequences/seq_2"
|
|
Alors le code HTTP de réponse est 204
|
|
Et la séquence est supprimée
|
|
Et l'ordre des séquences restantes est réajusté (1, 2)
|
|
|
|
# Publication et validation
|
|
|
|
Scénario: POST /api/v1/audio-guides/{id}/publish - Publication (nouveau créateur)
|
|
Étant donné un audio-guide draft avec 3 séquences complètes
|
|
Et que c'est le 2ème audio-guide du créateur
|
|
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/publish"
|
|
Alors le code HTTP de réponse est 200
|
|
Et le status passe à "pending_moderation"
|
|
Et moderation_required est true
|
|
Et le corps de réponse contient:
|
|
"""json
|
|
{
|
|
"status": "pending_moderation",
|
|
"message": "Votre audio-guide est en cours de validation. Notre équipe le vérifiera sous 24-48h.",
|
|
"estimated_validation": "2026-01-24T14:00:00Z"
|
|
}
|
|
"""
|
|
|
|
Scénario: Publication directe pour créateur expérimenté (>5 audio-guides validés)
|
|
Étant donné un audio-guide draft avec 5 séquences
|
|
Et que le créateur a publié 8 audio-guides validés
|
|
Et qu'il n'a aucun strike actif
|
|
Quand je fais un POST sur "/api/v1/audio-guides/ag_456/publish"
|
|
Alors le code HTTP de réponse est 200
|
|
Et le status passe à "published"
|
|
Et moderation_required est false
|
|
Et l'audio-guide est immédiatement visible
|
|
|
|
Scénario: Validation nombre minimum de séquences (2)
|
|
Étant donné un audio-guide draft avec 1 seule séquence
|
|
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/publish"
|
|
Alors le code HTTP de réponse est 400
|
|
Et le message d'erreur est "Minimum 2 séquences requis pour publication"
|
|
|
|
Scénario: Alerte cohérence - Points GPS trop éloignés
|
|
Étant donné un audio-guide en mode "pieton"
|
|
Et une séquence au Louvre (Paris)
|
|
Et une séquence à Lyon (465 km de distance)
|
|
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/publish"
|
|
Alors le code HTTP de réponse est 200
|
|
Et un warning est retourné:
|
|
"""json
|
|
{
|
|
"status": "pending_moderation",
|
|
"warnings": [
|
|
{
|
|
"type": "distance_incohérence",
|
|
"message": "Distance importante entre points (465 km). Vérifiez que le mode 'pieton' est approprié.",
|
|
"severity": "warning"
|
|
}
|
|
]
|
|
}
|
|
"""
|
|
|
|
# Gestion des brouillons
|
|
|
|
Scénario: Sauvegarde automatique brouillon
|
|
Étant donné un audio-guide draft non sauvegardé depuis 5 minutes
|
|
Quand une modification est apportée via PATCH
|
|
Alors le champ updated_at est mis à jour automatiquement
|
|
Et le brouillon est sauvegardé en base
|
|
|
|
Scénario: GET /api/v1/audio-guides/drafts - Liste des brouillons
|
|
Étant donné que le créateur a 2 brouillons:
|
|
| id | title | sequences_count | updated_at |
|
|
| ag_111 | Safari du Paugre | 3 | 2026-01-20 10:00:00 |
|
|
| ag_222 | Visite du Louvre | 1 | 2026-01-15 14:30:00 |
|
|
Quand je fais un GET sur "/api/v1/audio-guides/drafts"
|
|
Alors le code HTTP de réponse est 200
|
|
Et le corps de réponse contient les 2 brouillons
|
|
Et ils sont triés par updated_at décroissant
|
|
|
|
Scénario: DELETE /api/v1/audio-guides/{id} - Suppression brouillon
|
|
Étant donné un audio-guide draft "ag_123"
|
|
Quand je fais un DELETE sur "/api/v1/audio-guides/ag_123"
|
|
Alors le code HTTP de réponse est 204
|
|
Et l'audio-guide et toutes ses séquences sont supprimés
|
|
Et les fichiers audio associés sont marqués pour suppression S3
|
|
|
|
# Modification audio-guide publié
|
|
|
|
Scénario: PATCH /api/v1/audio-guides/{id} - Modification métadonnées (publié)
|
|
Étant donné un audio-guide publié "ag_789"
|
|
Et la requête suivante:
|
|
"""json
|
|
{
|
|
"title": "Safari du Paugre - Version 2",
|
|
"description": "Nouvelle description améliorée",
|
|
"tags": ["nature", "animaux", "enfants"]
|
|
}
|
|
"""
|
|
Quand je fais un PATCH sur "/api/v1/audio-guides/ag_789"
|
|
Alors le code HTTP de réponse est 200
|
|
Et les métadonnées sont mises à jour
|
|
Et le status reste "published" (pas de revalidation)
|
|
|
|
Scénario: Interdiction modification mode après publication
|
|
Étant donné un audio-guide publié en mode "voiture"
|
|
Et la requête avec mode "pieton"
|
|
Quand je fais un PATCH sur "/api/v1/audio-guides/{id}"
|
|
Alors le code HTTP de réponse est 400
|
|
Et le message d'erreur est "mode: impossible de modifier le mode après publication"
|
|
|
|
Scénario: Interdiction modification GPS après publication
|
|
Étant donné un audio-guide publié avec points GPS
|
|
Et une tentative de modification des coordonnées GPS
|
|
Quand je fais un PATCH sur "/api/v1/audio-guides/{id}/sequences/{seq_id}"
|
|
Alors le code HTTP de réponse est 400
|
|
Et le message d'erreur est "gps_point: impossible de modifier après publication. Créez une nouvelle version."
|
|
|
|
# Duplication
|
|
|
|
Scénario: POST /api/v1/audio-guides/{id}/duplicate - Duplication audio-guide
|
|
Étant donné un audio-guide publié "ag_999" avec 12 séquences
|
|
Quand je fais un POST sur "/api/v1/audio-guides/ag_999/duplicate"
|
|
Alors le code HTTP de réponse est 201
|
|
Et un nouvel audio-guide draft est créé
|
|
Et le titre est "Safari du Paugre (copie)"
|
|
Et toutes les séquences sont copiées avec les mêmes métadonnées
|
|
Et les fichiers audio sont référencés (pas de duplication S3)
|
|
|
|
# Statistiques
|
|
|
|
Scénario: GET /api/v1/audio-guides/{id}/stats - Statistiques parcours
|
|
Étant donné un audio-guide avec les séquences suivantes:
|
|
| sequence | duration | gps_point | distance_to_next |
|
|
| 1 | 2:15 | (43.1234, 2.5678) | 150m |
|
|
| 2 | 3:42 | (43.1245, 2.5690) | 200m |
|
|
| 3 | 4:10 | (43.1250, 2.5700) | null |
|
|
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/stats"
|
|
Alors le code HTTP de réponse est 200
|
|
Et le corps de réponse contient:
|
|
"""json
|
|
{
|
|
"sequences_count": 3,
|
|
"total_duration": "10:07",
|
|
"total_distance": "350m",
|
|
"avg_sequence_duration": "3:22"
|
|
}
|
|
"""
|
|
|
|
# Gestion zone diffusion
|
|
|
|
Scénario: Validation zone diffusion (polygon géographique)
|
|
Étant donné une zone diffusion de type "polygon"
|
|
Et les coordonnées forment un polygon valide (min 3 points)
|
|
Quand je fais un POST sur "/api/v1/audio-guides"
|
|
Alors la zone est validée avec PostGIS ST_IsValid
|
|
Et stockée en type geography
|
|
|
|
Scénario: Zone diffusion via API Nominatim (ville)
|
|
Étant donné une zone diffusion de type "city"
|
|
Et la valeur "Paris"
|
|
Quand je fais un POST sur "/api/v1/audio-guides"
|
|
Alors l'API interroge Nominatim pour récupérer le polygon de Paris
|
|
Et le polygon est stocké en base
|
|
|
|
# Cas d'erreur
|
|
|
|
Scénario: Authentification requise
|
|
Étant donné une requête sans token JWT
|
|
Quand je fais un POST sur "/api/v1/audio-guides"
|
|
Alors le code HTTP de réponse est 401
|
|
Et le message d'erreur est "Authentification requise"
|
|
|
|
Scénario: Compte non vérifié
|
|
Étant donné un créateur avec email_verified: false
|
|
Quand je fais un POST sur "/api/v1/audio-guides"
|
|
Alors le code HTTP de réponse est 403
|
|
Et le message d'erreur est "Vérification email requise pour créer des audio-guides"
|
|
|
|
Scénario: Modification audio-guide d'un autre créateur (interdite)
|
|
Étant donné un audio-guide appartenant au créateur "creator_A"
|
|
Et une requête authentifiée par "creator_B"
|
|
Quand je fais un PATCH sur "/api/v1/audio-guides/{id}"
|
|
Alors le code HTTP de réponse est 403
|
|
Et le message d'erreur est "Vous n'êtes pas autorisé à modifier cet audio-guide"
|
|
|
|
Scénario: Audio-guide inexistant
|
|
Quand je fais un GET sur "/api/v1/audio-guides/ag_nonexistant"
|
|
Alors le code HTTP de réponse est 404
|
|
Et le message d'erreur est "Audio-guide non trouvé"
|
|
|
|
Scénario: Séquence inexistante
|
|
Étant donné un audio-guide "ag_123"
|
|
Quand je fais un DELETE sur "/api/v1/audio-guides/ag_123/sequences/seq_nonexistant"
|
|
Alors le code HTTP de réponse est 404
|
|
Et le message d'erreur est "Séquence non trouvée"
|
|
|
|
Scénario: Suppression audio-guide avec utilisateurs actifs
|
|
Étant donné un audio-guide publié "ag_456"
|
|
Et 20 utilisateurs ont une progression active
|
|
Quand je fais un DELETE sur "/api/v1/audio-guides/ag_456"
|
|
Alors le code HTTP de réponse est 200
|
|
Et l'audio-guide est marqué "deleted" (soft delete)
|
|
Et les progressions utilisateurs sont archivées pendant 30 jours
|
|
Et un warning est retourné: "20 utilisateurs actifs. Progressions archivées 30 jours."
|