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.
338 lines
12 KiB
Gherkin
338 lines
12 KiB
Gherkin
# language: fr
|
|
Fonctionnalité: API - Jauge initiale et cold start
|
|
En tant qu'API backend
|
|
Je veux initialiser toutes les jauges à 50% lors de l'inscription
|
|
Afin de garantir un démarrage neutre et équitable
|
|
|
|
Contexte:
|
|
Étant donné que l'API RoadWave est disponible
|
|
Et que la base de données PostgreSQL est accessible
|
|
|
|
Scénario: API initialise toutes les jauges à 50% lors inscription
|
|
Quand je POST /api/v1/auth/register
|
|
"""json
|
|
{
|
|
"email": "nouveau@example.com",
|
|
"password": "SecureP@ss123",
|
|
"birth_date": "1990-01-15",
|
|
"username": "nouveau_user"
|
|
}
|
|
"""
|
|
Alors le statut de réponse est 201
|
|
Et la réponse contient:
|
|
"""json
|
|
{
|
|
"user_id": "<uuid>",
|
|
"email": "nouveau@example.com",
|
|
"username": "nouveau_user"
|
|
}
|
|
"""
|
|
Et en base de données, la table interest_gauges contient 12 lignes pour ce user_id:
|
|
| category | level |
|
|
| Automobile | 50 |
|
|
| Voyage | 50 |
|
|
| Famille | 50 |
|
|
| Amour | 50 |
|
|
| Musique | 50 |
|
|
| Économie | 50 |
|
|
| Cryptomonnaie | 50 |
|
|
| Politique | 50 |
|
|
| Culture générale | 50 |
|
|
| Sport | 50 |
|
|
| Technologie | 50 |
|
|
| Santé | 50 |
|
|
|
|
Scénario: API retourne les 12 catégories disponibles
|
|
Quand je GET /api/v1/interest-gauges/categories
|
|
Alors le statut de réponse est 200
|
|
Et la réponse contient:
|
|
"""json
|
|
{
|
|
"categories": [
|
|
{"id": "automobile", "name": "Automobile", "icon": "car"},
|
|
{"id": "voyage", "name": "Voyage", "icon": "plane"},
|
|
{"id": "famille", "name": "Famille", "icon": "users"},
|
|
{"id": "amour", "name": "Amour", "icon": "heart"},
|
|
{"id": "musique", "name": "Musique", "icon": "music"},
|
|
{"id": "economie", "name": "Économie", "icon": "chart"},
|
|
{"id": "cryptomonnaie", "name": "Cryptomonnaie", "icon": "bitcoin"},
|
|
{"id": "politique", "name": "Politique", "icon": "landmark"},
|
|
{"id": "culture-generale", "name": "Culture générale", "icon": "book"},
|
|
{"id": "sport", "name": "Sport", "icon": "running"},
|
|
{"id": "technologie", "name": "Technologie", "icon": "cpu"},
|
|
{"id": "sante", "name": "Santé", "icon": "heart-pulse"}
|
|
]
|
|
}
|
|
"""
|
|
|
|
Scénario: API GET retourne jauges utilisateur nouvellement inscrit
|
|
Étant donné qu'un utilisateur "user_new" vient de s'inscrire
|
|
Quand je GET /api/v1/users/user_new/interest-gauges
|
|
Alors le statut de réponse est 200
|
|
Et la réponse contient 12 jauges toutes à 50%:
|
|
"""json
|
|
{
|
|
"user_id": "user_new",
|
|
"gauges": [
|
|
{"category": "Automobile", "level": 50, "evolution_since_signup": 0},
|
|
{"category": "Voyage", "level": 50, "evolution_since_signup": 0},
|
|
{"category": "Famille", "level": 50, "evolution_since_signup": 0},
|
|
{"category": "Amour", "level": 50, "evolution_since_signup": 0},
|
|
{"category": "Musique", "level": 50, "evolution_since_signup": 0},
|
|
{"category": "Économie", "level": 50, "evolution_since_signup": 0},
|
|
{"category": "Cryptomonnaie", "level": 50, "evolution_since_signup": 0},
|
|
{"category": "Politique", "level": 50, "evolution_since_signup": 0},
|
|
{"category": "Culture générale", "level": 50, "evolution_since_signup": 0},
|
|
{"category": "Sport", "level": 50, "evolution_since_signup": 0},
|
|
{"category": "Technologie", "level": 50, "evolution_since_signup": 0},
|
|
{"category": "Santé", "level": 50, "evolution_since_signup": 0}
|
|
]
|
|
}
|
|
"""
|
|
|
|
Scénario: API calcule recommandations avec jauges à 50% - pas de biais
|
|
Étant donné qu'un utilisateur "user_new" vient de s'inscrire
|
|
Et que toutes ses jauges sont à 50%
|
|
Et qu'il est à la position GPS (48.8566, 2.3522) - Paris
|
|
Quand je POST /api/v1/recommendations
|
|
"""json
|
|
{
|
|
"user_id": "user_new",
|
|
"latitude": 48.8566,
|
|
"longitude": 2.3522,
|
|
"limit": 10
|
|
}
|
|
"""
|
|
Alors le statut de réponse est 200
|
|
Et la réponse contient 10 contenus
|
|
Et le scoring est basé uniquement sur:
|
|
| critère | poids |
|
|
| Distance géographique| 100% |
|
|
| Intérêts (50% égal) | 0% |
|
|
Et aucune catégorie n'a d'avantage initial
|
|
|
|
Scénario: API permet ajout de nouvelles catégories
|
|
Étant donné qu'un admin ajoute une nouvelle catégorie "Gastronomie"
|
|
Quand je POST /api/v1/admin/interest-gauges/categories
|
|
"""json
|
|
{
|
|
"id": "gastronomie",
|
|
"name": "Gastronomie",
|
|
"icon": "utensils"
|
|
}
|
|
"""
|
|
Alors le statut de réponse est 201
|
|
Et pour tous les utilisateurs existants:
|
|
| action |
|
|
| Une ligne est créée dans interest_gauges |
|
|
| category = "Gastronomie" |
|
|
| level = 50 |
|
|
Et les nouveaux utilisateurs auront aussi cette catégorie à 50%
|
|
|
|
Scénario: API calcule évolution depuis inscription
|
|
Étant donné qu'un utilisateur "user123" s'est inscrit il y a 30 jours
|
|
Et qu'il a les jauges suivantes en base:
|
|
| catégorie | niveau | initial |
|
|
| Automobile | 67% | 50% |
|
|
| Voyage | 82% | 50% |
|
|
| Économie | 34% | 50% |
|
|
| Sport | 50% | 50% |
|
|
Quand je GET /api/v1/users/user123/interest-gauges
|
|
Alors le statut de réponse est 200
|
|
Et la réponse contient:
|
|
"""json
|
|
{
|
|
"user_id": "user123",
|
|
"signup_date": "2026-01-03T10:00:00Z",
|
|
"gauges": [
|
|
{
|
|
"category": "Automobile",
|
|
"level": 67,
|
|
"evolution_since_signup": 17
|
|
},
|
|
{
|
|
"category": "Voyage",
|
|
"level": 82,
|
|
"evolution_since_signup": 32
|
|
},
|
|
{
|
|
"category": "Économie",
|
|
"level": 34,
|
|
"evolution_since_signup": -16
|
|
},
|
|
{
|
|
"category": "Sport",
|
|
"level": 50,
|
|
"evolution_since_signup": 0
|
|
}
|
|
]
|
|
}
|
|
"""
|
|
|
|
Scénario: API transaction atomique lors inscription
|
|
Quand je POST /api/v1/auth/register
|
|
"""json
|
|
{
|
|
"email": "test@example.com",
|
|
"password": "SecureP@ss123",
|
|
"birth_date": "1995-03-20",
|
|
"username": "test_user"
|
|
}
|
|
"""
|
|
Alors l'insertion en base de données est atomique:
|
|
| action |
|
|
| INSERT INTO users |
|
|
| INSERT INTO interest_gauges (12 lignes) |
|
|
| Tout ou rien (transaction) |
|
|
Et si une erreur survient, aucune donnée partielle n'est créée
|
|
|
|
Scénario: API rollback si initialisation jauges échoue
|
|
Étant donné que la table interest_gauges a une contrainte violée
|
|
Quand je POST /api/v1/auth/register avec données valides
|
|
Alors le statut de réponse est 500
|
|
Et aucune ligne n'est créée dans la table users
|
|
Et aucune ligne n'est créée dans la table interest_gauges
|
|
Et la transaction est rollback complètement
|
|
|
|
Scénario: API POST questionnaire optionnel post-MVP
|
|
Étant donné qu'un utilisateur "user123" a écouté 3 contenus
|
|
Et qu'il décide de remplir le questionnaire optionnel
|
|
Quand je POST /api/v1/users/user123/interest-gauges/quick-setup
|
|
"""json
|
|
{
|
|
"selected_categories": ["Automobile", "Voyage", "Sport"]
|
|
}
|
|
"""
|
|
Alors le statut de réponse est 200
|
|
Et en base de données:
|
|
| catégorie | niveau |
|
|
| Automobile | 70 |
|
|
| Voyage | 70 |
|
|
| Sport | 70 |
|
|
| Musique | 30 |
|
|
| Économie | 30 |
|
|
| Cryptomonnaie | 30 |
|
|
| Politique | 30 |
|
|
| Culture générale | 30 |
|
|
| Technologie | 30 |
|
|
| Santé | 30 |
|
|
| Famille | 30 |
|
|
| Amour | 30 |
|
|
Et un flag quick_setup_completed = true est enregistré
|
|
|
|
Scénario: API rejette questionnaire optionnel si déjà rempli
|
|
Étant donné qu'un utilisateur "user123" a déjà rempli le questionnaire optionnel
|
|
Quand je POST /api/v1/users/user123/interest-gauges/quick-setup
|
|
"""json
|
|
{
|
|
"selected_categories": ["Musique", "Technologie"]
|
|
}
|
|
"""
|
|
Alors le statut de réponse est 409
|
|
Et la réponse contient:
|
|
"""json
|
|
{
|
|
"error": "QUICK_SETUP_ALREADY_COMPLETED",
|
|
"message": "Le questionnaire a déjà été rempli"
|
|
}
|
|
"""
|
|
|
|
Scénario: API valide nombre de catégories sélectionnées
|
|
Quand je POST /api/v1/users/user123/interest-gauges/quick-setup
|
|
"""json
|
|
{
|
|
"selected_categories": ["Automobile"]
|
|
}
|
|
"""
|
|
Alors le statut de réponse est 400
|
|
Et la réponse contient:
|
|
"""json
|
|
{
|
|
"error": "VALIDATION_ERROR",
|
|
"message": "Vous devez sélectionner entre 2 et 5 catégories"
|
|
}
|
|
"""
|
|
|
|
Scénario: API déterministe - deux users identiques
|
|
Étant donné que l'utilisateur "userA" s'inscrit à 10:00:00
|
|
Et que l'utilisateur "userB" s'inscrit à 10:00:01
|
|
Quand je GET /api/v1/users/userA/interest-gauges
|
|
Et je GET /api/v1/users/userB/interest-gauges
|
|
Alors les deux réponses ont des jauges identiques (toutes à 50%)
|
|
Et le comportement est déterministe
|
|
|
|
Scénario: API retourne statistiques cold start
|
|
Étant donné qu'un utilisateur "user_new" vient de s'inscrire
|
|
Quand je GET /api/v1/users/user_new/stats
|
|
Alors le statut de réponse est 200
|
|
Et la réponse contient:
|
|
"""json
|
|
{
|
|
"user_id": "user_new",
|
|
"signup_date": "2026-02-02T14:00:00Z",
|
|
"total_listened_content": 0,
|
|
"gauges_summary": {
|
|
"all_at_default": true,
|
|
"default_value": 50,
|
|
"total_categories": 12,
|
|
"personalization_level": "none"
|
|
}
|
|
}
|
|
"""
|
|
|
|
Scénario: API recommandations cold start priorité géo
|
|
Étant donné qu'un utilisateur "user_new" vient de s'inscrire
|
|
Et qu'il est à Paris avec 100 contenus disponibles dans un rayon de 5km
|
|
Et que ces contenus ont des catégories variées
|
|
Quand je POST /api/v1/recommendations
|
|
"""json
|
|
{
|
|
"user_id": "user_new",
|
|
"latitude": 48.8566,
|
|
"longitude": 2.3522,
|
|
"limit": 10
|
|
}
|
|
"""
|
|
Alors le statut de réponse est 200
|
|
Et les 10 contenus retournés sont les plus proches géographiquement
|
|
Et les catégories sont variées (pas de biais intérêts)
|
|
Et la réponse contient:
|
|
"""json
|
|
{
|
|
"recommendations": [
|
|
{
|
|
"content_id": "<uuid>",
|
|
"distance_meters": 150,
|
|
"interest_match": 50,
|
|
"final_score": 0.95
|
|
}
|
|
],
|
|
"cold_start": true,
|
|
"personalization_level": "none"
|
|
}
|
|
"""
|
|
|
|
Scénario: API index optimisé pour lecture jauges
|
|
Étant donné que la table interest_gauges a 1 million de lignes
|
|
Quand je GET /api/v1/users/user123/interest-gauges
|
|
Alors la requête SQL utilise l'index (user_id, category)
|
|
Et le temps de réponse est < 50ms
|
|
Et le plan d'exécution confirme l'utilisation de l'index
|
|
|
|
Scénario: API cache jauges utilisateur en Redis
|
|
Étant donné qu'un utilisateur "user123" a ses jauges en base
|
|
Quand je GET /api/v1/users/user123/interest-gauges
|
|
Alors le backend vérifie d'abord Redis avec clé "user:user123:gauges"
|
|
Et si absent, lit depuis PostgreSQL
|
|
Et met en cache dans Redis avec TTL = 300 secondes
|
|
Quand je GET à nouveau dans les 5 minutes
|
|
Alors la réponse vient directement de Redis
|
|
Et aucune requête PostgreSQL n'est faite
|
|
|
|
Scénario: API invalide cache Redis lors mise à jour jauge
|
|
Étant donné que les jauges de "user123" sont en cache Redis
|
|
Quand je POST /api/v1/listening-events qui modifie une jauge
|
|
Alors le cache Redis "user:user123:gauges" est supprimé
|
|
Et le prochain GET recharge depuis PostgreSQL
|
|
Et remet en cache avec nouvelles valeurs
|