Création de 3 features Gherkin pour les tests backend des jauges d'intérêt: - evolution-jauges.feature: Tests API pour calculs de jauges (likes auto/manuels, abonnements créateurs, skips), persistence PostgreSQL, bornes 0-100%, cache Redis - jauge-initiale.feature: Tests API pour initialisation à 50% lors inscription, questionnaire optionnel post-MVP, recommandations cold start - degradation-temporelle.feature: Tests API confirmant absence de dégradation automatique, réinitialisation manuelle avec snapshot et audit log Complète les features UI existantes avec les aspects techniques backend.
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
|