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.
482 lines
15 KiB
Gherkin
482 lines
15 KiB
Gherkin
# 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
|
|
}
|
|
]
|
|
}
|
|
"""
|