feat(gherkin): ajouter features API pour jauges d'intérêt

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.
This commit is contained in:
jpgiannetti
2026-02-02 22:32:29 +01:00
parent 2cc9da29ff
commit 718581b954
3 changed files with 1114 additions and 0 deletions

View File

@@ -0,0 +1,296 @@
# language: fr
Fonctionnalité: API - Pas de dégradation temporelle
En tant qu'API backend
Je veux que les jauges n'évoluent que par les actions utilisateur
Afin d'avoir un comportement prévisible sans automatisme caché
Contexte:
Étant donné que l'API RoadWave est disponible
Et que la base de données PostgreSQL est accessible
Scénario: API ne dégrade jamais les jauges automatiquement
Étant donné qu'un utilisateur "user123" a une jauge "Économie" à 80% en base
Et que la colonne updated_at = "2026-01-01T10:00:00Z"
Quand 30 jours s'écoulent sans activité
Et je GET /api/v1/users/user123/interest-gauges le "2026-02-01T10:00:00Z"
Alors le statut de réponse est 200
Et la jauge "Économie" est toujours à 80%
Et aucune dégradation temporelle n'a été appliquée
Et la colonne updated_at n'a pas changé
Scénario: Aucun cron job de dégradation n'existe
Étant donné que le système vérifie les tâches cron planifiées
Quand je liste tous les cron jobs du backend
Alors aucun job nommé "degrade_interest_gauges" n'existe
Et aucun job périodique ne modifie la table interest_gauges
Et aucune ressource CPU n'est consommée pour la dégradation
Scénario: API GET jauges après 6 mois d'inactivité
Étant donné qu'un utilisateur "user123" a les jauges suivantes:
| catégorie | niveau | updated_at |
| Automobile | 75% | 2025-08-01T10:00:00 |
| Voyage | 60% | 2025-08-01T10:00:00 |
| Musique | 45% | 2025-08-01T10:00:00 |
Et qu'il ne se connecte pas pendant 6 mois
Quand je GET /api/v1/users/user123/interest-gauges le "2026-02-01T10:00:00Z"
Alors le statut de réponse est 200
Et les jauges sont exactement les mêmes:
| catégorie | niveau |
| Automobile | 75% |
| Voyage | 60% |
| Musique | 45% |
Et aucune modification n'a été appliquée
Scénario: Évolution par actions utilisateur uniquement
Étant donné qu'un utilisateur "user123" a une jauge "Économie" à 80%
Et qu'il skip 50 contenus "Économie" en 1 an
Quand je calcule l'évolution via les events
Alors la jauge "Économie" descend via les skips:
| action | impact | nouveau_niveau |
| 50 skips × -0.5%| -25% | 55% |
Et la dégradation vient des actions, pas du temps
Et la colonne updated_at reflète la date du dernier skip
Scénario: API POST réinitialiser centres d'intérêt
Étant donné qu'un utilisateur "user123" a des jauges personnalisées:
| catégorie | niveau |
| Automobile | 75% |
| Voyage | 60% |
| Économie | 34% |
| Sport | 88% |
Quand je POST /api/v1/users/user123/interest-gauges/reset
"""json
{
"confirmation": true
}
"""
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"message": "Vos centres d'intérêt ont été réinitialisés",
"previous_gauges_saved": true,
"new_gauges": {
"all_categories": 50
}
}
"""
Et en base de données, toutes les jauges de "user123" sont à 50%:
| catégorie | niveau |
| Automobile | 50 |
| Voyage | 50 |
| Économie | 50 |
| Sport | 50 |
| Musique | 50 |
| Technologie | 50 |
| Santé | 50 |
| Politique | 50 |
| Cryptomonnaie | 50 |
| Culture générale | 50 |
| Famille | 50 |
| Amour | 50 |
Scénario: API sauvegarde jauges précédentes avant réinitialisation
Étant donné qu'un utilisateur "user123" a des jauges personnalisées
Quand je POST /api/v1/users/user123/interest-gauges/reset
"""json
{
"confirmation": true
}
"""
Alors le statut de réponse est 200
Et une ligne est insérée dans interest_gauges_snapshots:
| user_id | snapshot_type | snapshot_date | gauges_json |
| user123 | manual_reset | 2026-02-02T14:00:00 | {"Automobile": 75, "Voyage": 60, ...} |
Et l'historique permet de restaurer si besoin
Scénario: API rejette réinitialisation sans confirmation
Quand je POST /api/v1/users/user123/interest-gauges/reset
"""json
{
"confirmation": false
}
"""
Alors le statut de réponse est 400
Et la réponse contient:
"""json
{
"error": "CONFIRMATION_REQUIRED",
"message": "Vous devez confirmer la réinitialisation"
}
"""
Et les jauges ne sont pas modifiées
Scénario: API recommandations après réinitialisation
Étant donné qu'un utilisateur "user123" avait "Économie" à 85%
Et qu'il réinitialise ses jauges (toutes à 50%)
Quand je POST /api/v1/recommendations
"""json
{
"user_id": "user123",
"latitude": 48.8566,
"longitude": 2.3522,
"limit": 10
}
"""
Alors le statut de réponse est 200
Et les recommandations utilisent 50% pour toutes les catégories
Et plus aucun biais "Économie" n'est appliqué
Et la géolocalisation redevient le critère principal
Scénario: Historique d'écoute conservé après réinitialisation
Étant donné qu'un utilisateur "user123" a écouté 500 contenus
Et que la table listening_history contient 500 lignes pour "user123"
Quand je POST /api/v1/users/user123/interest-gauges/reset
Alors le statut de réponse est 200
Et la table listening_history conserve toujours les 500 lignes
Et aucune donnée d'historique n'est supprimée
Et l'utilisateur peut toujours consulter ses anciens contenus écoutés
Scénario: API GET historique après réinitialisation
Étant donné qu'un utilisateur "user123" a réinitialisé ses jauges
Quand je GET /api/v1/users/user123/listening-history
Alors le statut de réponse est 200
Et la réponse contient tous les anciens contenus écoutés
Et l'historique est intact
Scénario: API enregistre timestamp réinitialisation
Étant donné qu'un utilisateur "user123" réinitialise ses jauges
Quand je POST /api/v1/users/user123/interest-gauges/reset
Alors en base de données, la table users est mise à jour:
| user_id | interest_gauges_reset_at | reset_count |
| user123 | 2026-02-02T14:00:00Z | 1 |
Et un compteur permet de tracker les réinitialisations multiples
Scénario: API permet réinitialisations multiples
Étant donné qu'un utilisateur "user123" a déjà réinitialisé une fois
Quand je POST /api/v1/users/user123/interest-gauges/reset une 2ème fois
Alors le statut de réponse est 200
Et le reset_count passe à 2
Et toutes les jauges reviennent à 50%
Scénario: API n'envoie jamais de suggestion de réinitialisation
Étant donné qu'un utilisateur "user123" n'a pas utilisé l'app depuis 1 an
Quand je GET /api/v1/users/user123/notifications
Alors le statut de réponse est 200
Et aucune notification "Réinitialiser vos centres d'intérêt" n'est présente
Et le système ne suggère jamais de réinitialisation automatique
Scénario: API GET statistiques respect historique utilisateur
Étant donné qu'un utilisateur "user123" aime "Cryptomonnaie" depuis 2 ans
Et que sa jauge est à 90%
Et qu'il n'a pas écouté de contenu "Cryptomonnaie" depuis 6 mois
Quand je GET /api/v1/users/user123/interest-gauges
Alors le statut de réponse est 200
Et la jauge "Cryptomonnaie" est toujours à 90%
Et la réponse contient:
"""json
{
"gauges": [
{
"category": "Cryptomonnaie",
"level": 90,
"last_updated": "2025-08-02T10:00:00Z",
"days_since_update": 183,
"preserved": true
}
]
}
"""
Et le système respecte l'historique des goûts
Scénario: API métrique temps écoulé sans modifier la jauge
Étant donné qu'un utilisateur "user123" a une jauge "Sport" à 65%
Et que la dernière modification date de 90 jours
Quand je GET /api/v1/users/user123/interest-gauges/sport
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"category": "Sport",
"level": 65,
"last_updated": "2025-11-03T12:00:00Z",
"days_since_update": 90
}
"""
Et la métrique days_since_update est informative uniquement
Et elle ne modifie jamais la jauge
Scénario: Requête SQL n'utilise jamais de calcul temporel
Étant donné que je trace les requêtes SQL du backend
Quand je GET /api/v1/users/user123/interest-gauges
Alors la requête SQL exécutée est:
"""sql
SELECT category, level, updated_at
FROM interest_gauges
WHERE user_id = $1
"""
Et aucune clause WHERE avec date/timestamp n'est présente
Et aucune fonction NOW(), CURRENT_TIMESTAMP, ou DATEDIFF n'est utilisée
Et le calcul est minimal (simple SELECT)
Scénario: API coût CPU minimal - pas de calcul de dates
Étant donné que 10000 utilisateurs consultent leurs jauges simultanément
Quand les requêtes /api/v1/users/{id}/interest-gauges sont exécutées
Alors aucun calcul de date n'est nécessaire
Et aucun appel à time.Now() ou time.Since() n'est fait
Et le coût CPU par requête est < 1ms
Et aucune dégradation de performance liée aux dates
Scénario: API pas de risque de bug fuseau horaire
Étant donné qu'aucune logique temporelle n'existe
Quand un utilisateur change de fuseau horaire (Paris Tokyo)
Alors ses jauges ne sont pas affectées
Et aucun bug de conversion UTC/local ne peut survenir
Et le comportement reste déterministe
Scénario: Audit log réinitialisation manuelle
Étant donné qu'un utilisateur "user123" réinitialise ses jauges
Quand je POST /api/v1/users/user123/interest-gauges/reset
Alors une ligne est insérée dans audit_log:
| user_id | action | timestamp | details |
| user123 | interest_gauges_reset | 2026-02-02T14:00:00 | {"previous_snapshot_id": 42} |
Et l'audit permet de tracer toutes les réinitialisations
Scénario: API empêche réinitialisation trop fréquente
Étant donné qu'un utilisateur "user123" a réinitialisé il y a 10 minutes
Quand je POST /api/v1/users/user123/interest-gauges/reset
Alors le statut de réponse est 429
Et la réponse contient:
"""json
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Vous ne pouvez réinitialiser qu'une fois par heure",
"retry_after_seconds": 3000
}
"""
Scénario: API documentation endpoints réinitialisation
Quand je GET /api/v1/openapi.json
Alors le endpoint POST /api/v1/users/{id}/interest-gauges/reset est documenté:
"""yaml
/users/{id}/interest-gauges/reset:
post:
summary: Réinitialise toutes les jauges à 50%
description: |
Remet toutes les jauges d'intérêt à leur valeur par défaut (50%).
Cette action est manuelle et requiert une confirmation.
Les jauges précédentes sont sauvegardées.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
confirmation:
type: boolean
description: Doit être true
responses:
200:
description: Réinitialisation réussie
400:
description: Confirmation manquante
429:
description: Trop de réinitialisations
"""

View File

@@ -0,0 +1,481 @@
# language: fr
Fonctionnalité: API - Évolution des jauges d'intérêt
En tant qu'API backend
Je veux calculer et persister les évolutions de jauges d'intérêt
Afin d'alimenter l'algorithme de recommandation
Contexte:
Étant donné que l'API RoadWave est disponible
Et que la base de données PostgreSQL est accessible
Et qu'un utilisateur "user123" existe avec token JWT valide
Scénario: API calcule like automatique renforcé (≥80% écoute)
Étant donné que l'utilisateur "user123" a une jauge "Automobile" à 45% en base
Et qu'un contenu "content456" de 5 minutes est tagué "Automobile"
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content456",
"listened_duration_seconds": 270,
"total_duration_seconds": 300,
"completion_percentage": 90
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"like_type": "automatic_reinforced",
"gauge_updates": [
{
"category": "Automobile",
"previous_value": 45,
"delta": 2,
"new_value": 47
}
]
}
"""
Et en base de données, la jauge "Automobile" de "user123" est à 47%
Scénario: API calcule like automatique standard (30-79% écoute)
Étant donné que l'utilisateur "user123" a une jauge "Voyage" à 60% en base
Et qu'un contenu "content789" de 10 minutes est tagué "Voyage"
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content789",
"listened_duration_seconds": 300,
"total_duration_seconds": 600,
"completion_percentage": 50
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"like_type": "automatic_standard",
"gauge_updates": [
{
"category": "Voyage",
"previous_value": 60,
"delta": 1,
"new_value": 61
}
]
}
"""
Et en base de données, la jauge "Voyage" de "user123" est à 61%
Scénario: API applique like manuel explicite (+2%)
Étant donné que l'utilisateur "user123" a une jauge "Musique" à 55% en base
Et qu'un contenu "content999" est tagué "Musique"
Quand je POST /api/v1/likes
"""json
{
"user_id": "user123",
"content_id": "content999",
"like_type": "manual"
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"like_id": "<uuid>",
"gauge_updates": [
{
"category": "Musique",
"previous_value": 55,
"delta": 2,
"new_value": 57
}
]
}
"""
Et en base de données, la jauge "Musique" de "user123" est à 57%
Scénario: API applique unlike (retire like manuel)
Étant donné que l'utilisateur "user123" a une jauge "Sport" à 57% en base
Et qu'il a liké manuellement le contenu "content888" tagué "Sport"
Et que le like a l'ID "like_abc123"
Quand je DELETE /api/v1/likes/like_abc123
Alors le statut de réponse est 204
Et en base de données, la jauge "Sport" de "user123" est à 55%
Et le like "like_abc123" est supprimé de la table likes
Scénario: API refuse unlike d'un like automatique
Étant donné que l'utilisateur "user123" a écouté un contenu à 90%
Et qu'il a reçu un like automatique "like_auto456"
Quand je DELETE /api/v1/likes/like_auto456
Alors le statut de réponse est 403
Et la réponse contient:
"""json
{
"error": "CANNOT_UNLIKE_AUTOMATIC",
"message": "Les likes automatiques ne peuvent pas être retirés"
}
"""
Et en base de données, le like "like_auto456" existe toujours
Scénario: API cumule like automatique + like manuel
Étant donné que l'utilisateur "user123" a une jauge "Technologie" à 50% en base
Et qu'un contenu "content777" de 10 minutes est tagué "Technologie"
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content777",
"completion_percentage": 50
}
"""
Alors la jauge "Technologie" passe à 51% (+1% auto)
Quand je POST ensuite /api/v1/likes
"""json
{
"user_id": "user123",
"content_id": "content777",
"like_type": "manual"
}
"""
Alors la jauge "Technologie" passe à 53% (+2% manuel)
Et le delta total est de 3%
Scénario: API applique bonus abonnement créateur (+5% tous tags)
Étant donné que l'utilisateur "user123" a les jauges suivantes:
| catégorie | niveau |
| Automobile | 50% |
| Technologie | 45% |
Et qu'un créateur "creator456" publie des contenus tagués "Automobile" et "Technologie"
Quand je POST /api/v1/subscriptions
"""json
{
"user_id": "user123",
"creator_id": "creator456"
}
"""
Alors le statut de réponse est 201
Et en base de données:
| catégorie | niveau |
| Automobile | 55% |
| Technologie | 50% |
Et l'abonnement est créé avec bonus appliqué
Scénario: API retire bonus désabonnement créateur (-5% tous tags)
Étant donné que l'utilisateur "user123" a les jauges suivantes:
| catégorie | niveau |
| Voyage | 65% |
| Culture | 58% |
Et qu'il est abonné au créateur "creator789" qui publie "Voyage" et "Culture"
Quand je DELETE /api/v1/subscriptions/creator789
Alors le statut de réponse est 204
Et en base de données:
| catégorie | niveau |
| Voyage | 60% |
| Culture | 53% |
Scénario: API applique pénalité skip rapide (<10s)
Étant donné que l'utilisateur "user123" a une jauge "Politique" à 50% en base
Et qu'un contenu "content555" de 300 secondes est tagué "Politique"
Quand je POST /api/v1/skip-events
"""json
{
"user_id": "user123",
"content_id": "content555",
"listened_duration_seconds": 5,
"total_duration_seconds": 300
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"skip_type": "early",
"gauge_updates": [
{
"category": "Politique",
"previous_value": 50,
"delta": -0.5,
"new_value": 49.5
}
]
}
"""
Et en base de données, la jauge "Politique" de "user123" est à 49.5%
Scénario: API n'applique pas de pénalité pour skip ≥10s et <30%
Étant donné que l'utilisateur "user123" a une jauge "Économie" à 60% en base
Et qu'un contenu "content333" de 600 secondes est tagué "Économie"
Quand je POST /api/v1/skip-events
"""json
{
"user_id": "user123",
"content_id": "content333",
"listened_duration_seconds": 120,
"total_duration_seconds": 600,
"completion_percentage": 20
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"skip_type": "neutral",
"gauge_updates": []
}
"""
Et en base de données, la jauge "Économie" de "user123" reste à 60%
Scénario: API n'applique pas de pénalité pour skip ≥30%
Étant donné que l'utilisateur "user123" a une jauge "Sport" à 55% en base
Et qu'un contenu "content222" de 600 secondes est tagué "Sport"
Quand je POST /api/v1/skip-events
"""json
{
"user_id": "user123",
"content_id": "content222",
"listened_duration_seconds": 300,
"total_duration_seconds": 600,
"completion_percentage": 50
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"skip_type": "late",
"gauge_updates": []
}
"""
Et en base de données, la jauge "Sport" de "user123" reste à 55%
Scénario: API applique évolution sur plusieurs tags simultanément
Étant donné que l'utilisateur "user123" a les jauges suivantes:
| catégorie | niveau |
| Automobile | 45% |
| Voyage | 60% |
Et qu'un contenu "content111" est tagué "Automobile" et "Voyage"
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content111",
"completion_percentage": 90
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"like_type": "automatic_reinforced",
"gauge_updates": [
{
"category": "Automobile",
"previous_value": 45,
"delta": 2,
"new_value": 47
},
{
"category": "Voyage",
"previous_value": 60,
"delta": 2,
"new_value": 62
}
]
}
"""
Et en base de données:
| catégorie | niveau |
| Automobile | 47% |
| Voyage | 62% |
Scénario: API respecte la borne maximum 100%
Étant donné que l'utilisateur "user123" a une jauge "Cryptomonnaie" à 99% en base
Et qu'un contenu "content_crypto" est tagué "Cryptomonnaie"
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content_crypto",
"completion_percentage": 95
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"like_type": "automatic_reinforced",
"gauge_updates": [
{
"category": "Cryptomonnaie",
"previous_value": 99,
"delta": 2,
"new_value": 100,
"capped": true
}
]
}
"""
Et en base de données, la jauge "Cryptomonnaie" de "user123" est à 100%
Et la jauge n'a pas dépassé 100%
Scénario: API respecte la borne minimum 0%
Étant donné que l'utilisateur "user123" a une jauge "Politique" à 0.3% en base
Et qu'un contenu "content_pol" est tagué "Politique"
Quand je POST /api/v1/skip-events
"""json
{
"user_id": "user123",
"content_id": "content_pol",
"listened_duration_seconds": 3,
"total_duration_seconds": 300
}
"""
Alors le statut de réponse est 201
Et la réponse contient:
"""json
{
"skip_type": "early",
"gauge_updates": [
{
"category": "Politique",
"previous_value": 0.3,
"delta": -0.5,
"new_value": 0,
"capped": true
}
]
}
"""
Et en base de données, la jauge "Politique" de "user123" est à 0%
Et la jauge n'est pas devenue négative
Scénario: API respecte borne minimum lors désabonnement
Étant donné que l'utilisateur "user123" a une jauge "Économie" à 3% en base
Et qu'il est abonné au créateur "creator_eco" qui publie "Économie"
Quand je DELETE /api/v1/subscriptions/creator_eco
Alors le statut de réponse est 204
Et en base de données, la jauge "Économie" de "user123" est à 0% (et non -2%)
Scénario: API GET retourne toutes les jauges utilisateur
Étant donné que l'utilisateur "user123" a les jauges suivantes en base:
| catégorie | niveau |
| Automobile | 67% |
| Voyage | 82% |
| Économie | 34% |
| Sport | 50% |
| Musique | 45% |
| Technologie | 71% |
Quand je GET /api/v1/users/user123/interest-gauges
Alors le statut de réponse est 200
Et la réponse contient les 12 catégories avec leurs niveaux:
"""json
{
"user_id": "user123",
"gauges": [
{"category": "Automobile", "level": 67},
{"category": "Voyage", "level": 82},
{"category": "Économie", "level": 34},
{"category": "Sport", "level": 50},
{"category": "Musique", "level": 45},
{"category": "Technologie", "level": 71}
]
}
"""
Scénario: API calcule évolution immédiate (pas de batch différé)
Étant donné que l'utilisateur "user123" a une jauge "Voyage" à 50% en base
Quand je POST /api/v1/listening-events à 12:00:00
"""json
{
"user_id": "user123",
"content_id": "content_travel",
"completion_percentage": 85
}
"""
Alors le statut de réponse est 201
Quand je GET /api/v1/users/user123/interest-gauges à 12:00:01 (1 seconde après)
Alors la jauge "Voyage" est à 52%
Et la mise à jour est visible immédiatement
Scénario: API rejette token JWT invalide
Quand je POST /api/v1/listening-events sans token JWT
Alors le statut de réponse est 401
Et la réponse contient:
"""json
{
"error": "UNAUTHORIZED",
"message": "Token JWT manquant ou invalide"
}
"""
Scénario: API valide format des données d'entrée
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content456",
"completion_percentage": 150
}
"""
Alors le statut de réponse est 400
Et la réponse contient:
"""json
{
"error": "VALIDATION_ERROR",
"message": "completion_percentage doit être entre 0 et 100"
}
"""
Scénario: API gère contenu avec tags inexistants en base
Étant donné qu'un contenu "content_new" est tagué "NouvelleCategorie" (non encore en base)
Quand je POST /api/v1/listening-events
"""json
{
"user_id": "user123",
"content_id": "content_new",
"completion_percentage": 90
}
"""
Alors le statut de réponse est 201
Et une nouvelle ligne est créée dans la table interest_gauges:
| user_id | category | level |
| user123 | NouvelleCategorie | 52 |
Et l'initialisation démarre à 50% + 2% de like auto = 52%
Scénario: API persiste historique des modifications de jauges
Étant donné que l'utilisateur "user123" a une jauge "Sport" à 50%
Quand je POST /api/v1/listening-events qui applique +2%
Alors une ligne est insérée dans interest_gauge_history:
| user_id | category | previous_value | delta | new_value | event_type | event_id | timestamp |
| user123 | Sport | 50 | 2 | 52 | listening_event| <event_uuid> | 2026-02-02T12:00:00 |
Et cet historique permet d'auditer les évolutions
Scénario: API retourne métriques d'évolution utilisateur
Étant donné que l'utilisateur "user123" a un historique d'évolution en base
Quand je GET /api/v1/users/user123/interest-gauges/evolution?since=7d
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"period": "7d",
"evolution": [
{
"category": "Automobile",
"start_value": 60,
"end_value": 67,
"delta": 7,
"events_count": 15
},
{
"category": "Voyage",
"start_value": 80,
"end_value": 82,
"delta": 2,
"events_count": 3
}
]
}
"""

View File

@@ -0,0 +1,337 @@
# 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