feat(bdd): réorganiser features en catégories api/ui/e2e et créer ADR-024

Résolution des incohérences #10, #11, et #12 de l'analyse d'architecture.

## Phase 1 : Réorganisation Features BDD (Point #10 - RÉSOLU)

- Créer structure features/{api,ui,e2e}
- Déplacer 83 features en 3 catégories via git mv (historique préservé)
  - features/api/ : 53 features (tests API backend)
  - features/ui/ : 22 features (tests UI mobile)
  - features/e2e/ : 8 features (tests end-to-end)

Domaines déplacés :
- API : authentication, recommendation, rgpd-compliance, content-creation,
  moderation, monetisation, premium, radio-live, publicites
- UI : audio-guides, navigation, interest-gauges, mode-offline,
  partage, profil, recherche
- E2E : abonnements, error-handling

## Phase 2 : Mise à jour Documentation

### ADR-007 - Tests BDD
- Ajouter section "Convention de Catégorisation des Features"
- Documenter règles api/ui/e2e avec exemples concrets
- Spécifier step definitions (backend Go, mobile Dart)

### ADR-024 - Stratégie CI/CD Monorepo (NOUVEAU)
- Créer ADR dédié pour stratégie CI/CD avec path filters
- Architecture workflows séparés (backend.yml, mobile.yml, shared.yml)
- Configuration path filters détaillée avec exemples YAML
- Matrice de déclenchement et optimisations (~70% gain temps CI)
- Plan d'implémentation (~2h, reporté jusqu'au développement)

### ADR-016 - Organisation Monorepo
- Simplifier en retirant section CI/CD détaillée
- Ajouter référence vers ADR-024 pour stratégie CI/CD

### INCONSISTENCIES-ANALYSIS.md
- Point #10 (Tests BDD synchronisés) :  RÉSOLU
  - Catégorisation features implémentée
  - ADR-007 mis à jour avec convention complète
- Point #11 (70/30 Split paiements) :  ANNULÉ (faux problème)
  - ADR-009 et Règle 18 parfaitement cohérents
  - Documentation exhaustive existante (formule, SQL, comparaisons)
- Point #12 (Monorepo path filters) : ⏸️ DOCUMENTÉ
  - Architecture CI/CD complète dans ADR-024
  - Implémentation reportée (projet en phase documentation)
- Métriques mises à jour :
  - MODERATE : 6/9 traités (4 résolus + 1 annulé + 1 documenté)
  - ADR à jour : 100% (19/19 avec ADR-024)

## Phase 3 : Validation

- Structure features validée (api/ui/e2e, aucun répertoire restant)
- Historique Git préservé (git mv, renommages détectés)
- 83 features total (API: 53, UI: 22, E2E: 8)

Closes: Point #10 (résolu), Point #11 (annulé), Point #12 (documenté)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
jpgiannetti
2026-02-01 11:31:41 +01:00
parent 841028d1b2
commit 37c62206ad
88 changed files with 625 additions and 67 deletions

View File

@@ -0,0 +1,333 @@
# language: fr
Fonctionnalité: Création d'audio-guide multi-séquences
En tant que créateur de contenu
Je veux créer des audio-guides avec plusieurs séquences géolocalisées
Afin d'offrir des expériences guidées adaptées aux différents modes de déplacement
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que le créateur "guide@example.com" est connecté
Et que son compte est vérifié
# 16.1 - Types d'audio-guides et modes de déplacement
Plan du Scénario: Détection automatique du mode selon la vitesse
Étant donné que l'utilisateur se déplace à <vitesse> km/h
Quand la vitesse est calculée sur 30 secondes
Alors le mode <mode> est suggéré automatiquement
Exemples:
| vitesse | mode |
| 3 | Piéton |
| 15 | Vélo |
| 35 | Voiture |
| 50 | Voiture |
Scénario: Suggestion de mode au démarrage avec confirmation
Étant donné qu'un audio-guide "Safari du Paugre" est disponible
Et que l'utilisateur se déplace à 35 km/h
Quand l'audio-guide démarre
Alors une popup s'affiche:
"""
Détection : 🚗 Voiture. Est-ce correct ?
[Oui] [Changer]
"""
Scénario: Changement manuel du mode détecté
Étant donné que le mode "Voiture" est suggéré automatiquement
Quand l'utilisateur clique sur "Changer"
Alors les 4 modes sont proposés:
| mode | emoji |
| Piéton | 🚶 |
| Voiture | 🚗 |
| Vélo | 🚴 |
| Transport | 🚌 |
Plan du Scénario: Caractéristiques par mode de déplacement
Étant donné un audio-guide configuré en mode <mode>
Alors les paramètres suivants sont appliqués:
| paramètre | valeur |
| Vitesse détection | <vitesse_detection> |
| Déclenchement | <declenchement> |
Exemples:
| mode | vitesse_detection | declenchement |
| Piéton | <5 km/h | Manuel (bouton Suivant) |
| Voiture | >10 km/h | Auto GPS + Manuel |
| Vélo | 5-25 km/h | Auto GPS + Manuel |
| Transport | Variable | Auto GPS + Manuel |
# 16.1.2 - Création d'un audio-guide (côté créateur)
Scénario: Accès au formulaire de création d'audio-guide
Étant donné que le créateur est sur son dashboard
Quand il clique sur "Créer un audio-guide"
Alors le formulaire de création s'affiche
Et le wizard guidé en 4 étapes est visible:
| étape | description |
| 1 | Infos générales |
| 2 | Ajout séquences |
| 3 | Preview carte |
| 4 | Validation modération |
Scénario: Étape 1 - Informations générales obligatoires
Étant donné que le créateur est sur l'étape 1 du wizard
Quand il complète le formulaire
Alors les champs suivants sont obligatoires:
| champ | contrainte |
| Titre | 5-100 caractères |
| Description | 10-500 caractères |
| Mode de déplacement | Choix parmi 4 |
| Tags | 1-3 tags |
| Classification âge | Tout public/13+/16+/18+ |
Scénario: Sélection du mode de déplacement
Étant donné que le créateur crée un audio-guide
Quand il sélectionne le mode "🚗 Voiture (GPS auto + manuel)"
Alors le champ "Vitesse recommandée" s'affiche
Et la plage suggérée est "30-50 km/h"
Scénario: Validation du titre
Étant donné que le créateur entre un titre
Quand le titre contient moins de 5 caractères
Alors un message d'erreur "Minimum 5 caractères" s'affiche
Et le bouton "Suivant" est désactivé
Scénario: Validation de la description
Étant donné que le créateur entre une description
Quand la description contient 520 caractères
Alors un message d'erreur "Maximum 500 caractères" s'affiche
Et les 20 caractères en trop sont surlignés en rouge
Scénario: Étape 2 - Ajout de la première séquence
Étant donné que le créateur est sur l'étape 2 "Ajout séquences"
Quand il clique sur "Ajouter séquence"
Alors le formulaire de séquence s'affiche avec:
| champ | requis | note |
| Titre séquence | | 5-80 caractères |
| Audio | | Upload MP3/AAC, max 200 MB |
| Point GPS | * | *Sauf mode piéton |
| Rayon déclenchement | * | *Sauf mode piéton, 10-200m |
Scénario: Ajout du point GPS pour une séquence
Étant donné que le créateur ajoute une séquence en mode "Voiture"
Quand il clique sur "📍 Ajouter point GPS"
Alors une carte s'affiche
Et il peut:
| action |
| Cliquer sur la carte |
| Entrer coordonnées manuelles |
| Utiliser sa position actuelle |
Scénario: Configuration du rayon de déclenchement avec preview
Étant donné qu'un point GPS est défini à (43.1234, 2.5678)
Quand le créateur ajuste le curseur de rayon
Alors le rayon varie de 10m à 200m
Et un cercle visuel est affiché sur la carte
Et la valeur actuelle s'affiche "30m"
Plan du Scénario: Rayon par défaut selon le mode
Étant donné un audio-guide en mode <mode>
Quand le créateur ajoute un point GPS
Alors le rayon par défaut est <rayon_defaut>
Exemples:
| mode | rayon_defaut |
| Voiture | 30m |
| Vélo | 50m |
| Transport | 100m |
Scénario: Suggestion intelligente du rayon
Étant donné un audio-guide en mode "Voiture" avec vitesse recommandée 30 km/h
Quand le créateur ajoute un point GPS
Alors une suggestion s'affiche: "Recommandé : 30m pour voiture à 30 km/h"
Scénario: Upload audio pour une séquence
Étant donné que le créateur crée une séquence "Introduction"
Quand il upload un fichier audio de 5 MB
Alors le fichier est vérifié:
| vérification | règle |
| Format | MP3, AAC, M4A |
| Taille max | 200 MB |
| Durée max | 15 minutes |
Scénario: Ordre des séquences modifiable
Étant donné un audio-guide avec 5 séquences:
| ordre | titre |
| 1 | Introduction |
| 2 | Les lions |
| 3 | Les girafes |
| 4 | Les éléphants |
| 5 | Conclusion |
Quand le créateur glisse "Les éléphants" en position 2
Alors l'ordre devient:
| ordre | titre |
| 1 | Introduction |
| 2 | Les éléphants |
| 3 | Les lions |
| 4 | Les girafes |
| 5 | Conclusion |
Scénario: Nombre minimum de séquences requis
Étant donné un audio-guide avec seulement 1 séquence
Quand le créateur tente de passer à l'étape suivante
Alors un message d'erreur s'affiche: "Minimum 2 séquences requis"
Et le bouton "Suivant" est désactivé
Scénario: Nombre maximum de séquences
Étant donné un audio-guide avec 50 séquences
Quand le créateur tente d'ajouter une 51ème séquence
Alors un message d'erreur s'affiche: "Maximum 50 séquences par audio-guide"
Et le bouton "+ Ajouter séquence" est désactivé
Scénario: Étape 3 - Preview carte avec tracé et points
Étant donné un audio-guide avec 5 séquences géolocalisées
Quand le créateur accède à l'étape 3 "Preview carte"
Alors une carte Leaflet s'affiche
Et les éléments suivants sont visibles:
| élément | description |
| Markers numérotés | 1, 2, 3, 4, 5 sur chaque point |
| Tracé entre points | Ligne pointillée connectant les points |
| Cercles de déclenchement | Rayon visuel autour de chaque point |
Scénario: Statistiques du parcours
Étant donné un audio-guide avec les séquences suivantes:
| séquence | durée | distance_au_suivant |
| 1 | 2:15 | 150m |
| 2 | 3:42 | 200m |
| 3 | 4:10 | 320m |
Quand les statistiques sont calculées
Alors le résumé suivant est affiché:
| métrique | valeur |
| Séquences | 3 complètes |
| Durée totale | 10:07 |
| Distance totale | 670m |
Scénario: Modification d'une séquence depuis la carte
Étant donné que la preview carte est affichée
Quand le créateur clique sur le marker "2"
Alors une popup s'affiche avec:
| information |
| Titre: "Les lions" |
| Durée: 3:42 |
| Rayon: 30m |
| [ Modifier] |
| [🗑 Supprimer] |
Scénario: Zone de diffusion géographique
Étant donné un audio-guide avec des points dans Paris
Quand le créateur définit la zone de diffusion
Alors il peut choisir:
| type | exemple |
| Polygon | Tracé manuel sur carte |
| Ville | Paris (API Nominatim) |
| Département | 75 - Paris |
| Région | Île-de-France |
Scénario: Étape 4 - Publication et validation modération
Étant donné un créateur qui publie ses 3 premiers audio-guides
Quand il clique sur " Publier audio-guide"
Alors un message s'affiche:
"""
Votre audio-guide est en cours de validation.
Notre équipe le vérifiera sous 24-48h.
Vous recevrez une notification dès validation.
"""
Scénario: Publication directe pour créateurs expérimentés
Étant donné un créateur ayant publié 5 audio-guides validés
Et aucun strike actif
Quand il publie un nouvel audio-guide
Alors l'audio-guide est publié immédiatement
Et il devient visible pour les utilisateurs
Et aucune validation manuelle n'est requise
Scénario: Mode piéton sans points GPS obligatoires
Étant donné un audio-guide en mode "🚶 Piéton"
Quand le créateur ajoute une séquence
Alors le champ "Point GPS" est optionnel
Et le champ "Rayon déclenchement" est masqué
Et un message info s'affiche: "Mode manuel : les séquences se déclenchent au clic utilisateur"
Scénario: Sauvegarde brouillon automatique
Étant donné que le créateur édite un audio-guide depuis 5 minutes
Quand il ajoute une nouvelle séquence
Alors l'audio-guide est sauvegardé en brouillon automatiquement
Et un toast "Brouillon sauvegardé" s'affiche brièvement
Scénario: Reprise d'un brouillon
Étant donné un audio-guide en brouillon "Safari du Paugre"
Et qu'il contient 3 séquences complètes
Quand le créateur retourne sur son dashboard
Alors le brouillon est visible avec le statut "📝 Brouillon"
Et un bouton "Continuer" est disponible
Et la progression "3/5 séquences" est affichée
Scénario: Suppression d'un brouillon
Étant donné un audio-guide en brouillon
Quand le créateur clique sur "🗑 Supprimer"
Alors une confirmation s'affiche:
"""
Supprimer ce brouillon ?
Toutes les séquences seront perdues.
[Annuler] [Supprimer définitivement]
"""
Scénario: Modification d'un audio-guide publié
Étant donné un audio-guide publié "Safari du Paugre"
Quand le créateur clique sur " Modifier"
Alors il peut modifier:
| élément modifiable | élément non modifiable |
| Titre | Mode de déplacement |
| Description | Points GPS |
| Tags | Rayons déclenchement |
| Séquences (ordre) | |
Et un avertissement s'affiche: "Les modifications structurelles nécessitent une nouvelle publication"
Scénario: Duplication d'un audio-guide existant
Étant donné un audio-guide publié "Visite Paris"
Quand le créateur clique sur "📋 Dupliquer"
Alors une copie est créée avec le titre "Visite Paris (copie)"
Et toutes les séquences sont copiées
Et le statut est "📝 Brouillon"
Et le créateur peut modifier avant publication
# Cas d'erreur
Scénario: Upload audio échoue (format non supporté)
Étant donné que le créateur upload un fichier "audio.wav"
Quand le format est vérifié
Alors un message d'erreur s'affiche: "Format non supporté. Utilisez MP3, AAC ou M4A"
Et le fichier est rejeté
Scénario: Upload audio échoue (taille trop grande)
Étant donné que le créateur upload un fichier de 250 MB
Quand la taille est vérifiée
Alors un message d'erreur s'affiche: "Fichier trop volumineux. Maximum 200 MB"
Et le fichier est rejeté
Scénario: Points GPS trop éloignés (alerte cohérence)
Étant donné un audio-guide en mode "Piéton"
Et une séquence au Louvre (Paris)
Quand le créateur ajoute une séquence à Lyon
Alors un avertissement s'affiche:
"""
Attention : distance importante entre points (465 km)
Vérifiez que le mode "Piéton" est approprié.
[Modifier le mode] [Continuer]
"""
Scénario: Pas de connexion lors de la sauvegarde
Étant donné que le créateur édite un audio-guide
Et que la connexion réseau est perdue
Quand il tente de sauvegarder
Alors le brouillon est sauvegardé localement
Et un message s'affiche: "Sauvegarde locale. Sera synchronisée à la reconnexion"
Et une icône " Hors ligne" s'affiche
Scénario: Reprise après perte de connexion
Étant donné un brouillon sauvegardé localement
Quand la connexion réseau est rétablie
Alors le brouillon est synchronisé automatiquement
Et un toast " Audio-guide synchronisé" s'affiche

View File

@@ -0,0 +1,349 @@
# language: fr
Fonctionnalité: Intégration audio-guides avec autres fonctionnalités
En tant qu'utilisateur
Je veux utiliser les audio-guides avec toutes les fonctionnalités de l'app
Afin d'avoir une expérience complète et cohérente
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté
# 16.14 - Téléchargement offline
Scénario: Téléchargement complet d'un audio-guide
Étant donné un audio-guide "Visite du Louvre" avec 12 séquences
Quand l'utilisateur clique sur " Télécharger pour écouter hors ligne"
Alors toutes les 12 séquences sont téléchargées
Et les métadonnées (titres, descriptions, GPS) sont sauvegardées
Et les images (cover, miniatures) sont mises en cache
Scénario: Affichage de la progression du téléchargement
Étant donné qu'un téléchargement d'audio-guide est en cours
Quand l'utilisateur consulte l'état
Alors la progression s'affiche:
"""
Téléchargement en cours...
Séquence 7/12 245 MB / 380 MB
64%
"""
Scénario: Téléchargement uniquement en WiFi (par défaut)
Étant donné que l'option "Télécharger uniquement en WiFi" est activée
Quand l'utilisateur lance un téléchargement sur réseau mobile
Alors un avertissement s'affiche:
"""
Téléchargement nécessite WiFi
Cet audio-guide pèse 380 MB.
[Attendre WiFi] [Télécharger quand même]
"""
Scénario: Gestion de l'espace de stockage
Étant donné que l'appareil a 500 MB d'espace libre
Et qu'un audio-guide pèse 380 MB
Quand l'utilisateur lance le téléchargement
Alors un avertissement s'affiche:
"""
Espace de stockage limité
Après téléchargement : 120 MB restants
[Continuer] [Gérer stockage] [Annuler]
"""
Scénario: Liste des audio-guides téléchargés
Étant donné que l'utilisateur a téléchargé 3 audio-guides
Quand il accède à "Bibliothèque > Téléchargés"
Alors il voit:
| audio_guide | taille | date_telechargement |
| Visite du Louvre | 380 MB | 2026-01-20 |
| Safari du Paugre | 245 MB | 2026-01-18 |
| Circuit Loire à Vélo | 520 MB | 2026-01-15 |
Scénario: Lecture hors connexion complète
Étant donné qu'un audio-guide est téléchargé
Et que l'utilisateur active le mode avion
Quand il lance l'audio-guide
Alors toutes les séquences sont lisibles
Et les métadonnées sont accessibles
Et les images s'affichent normalement
Et la progression est sauvegardée localement
Scénario: GPS fonctionne en mode avion (mode voiture)
Étant donné qu'un audio-guide voiture est téléchargé
Et que le mode avion est activé (avec GPS actif)
Quand l'utilisateur se déplace
Alors les déclenchements GPS fonctionnent normalement
Et la distance/ETA sont calculés
Parce que le GPS ne nécessite pas de connexion internet
Scénario: Suppression d'audio-guide téléchargé
Étant donné qu'un audio-guide téléchargé pèse 380 MB
Quand l'utilisateur clique sur "🗑 Supprimer téléchargement"
Alors une confirmation s'affiche
Et si confirmé, les 380 MB sont libérés
Et l'audio-guide reste accessible en streaming
Scénario: Mise à jour automatique si nouvelle version
Étant donné qu'un audio-guide téléchargé a été mis à jour par le créateur
Quand l'utilisateur se connecte en WiFi
Alors une notification s'affiche:
"""
🔄 Mise à jour disponible
"Visite du Louvre" - Nouvelle version
[Mettre à jour] [Plus tard]
"""
# 16.15 - Playlists et collections
Scénario: Ajout d'audio-guide à une playlist
Étant donné que l'utilisateur consulte un audio-guide
Quand il clique sur " Ajouter à une playlist"
Alors ses playlists s'affichent:
| playlist |
| 🗺 Voyages en France |
| 🏛 Musées parisiens |
| + Créer nouvelle playlist |
Scénario: Comportement audio-guide dans une playlist
Étant donné une playlist contenant 2 audio-guides et 1 podcast
Quand la lecture atteint un audio-guide
Alors l'audio-guide démarre à la séquence 1 (ou progression sauvegardée)
Et les séquences se jouent normalement
Quand l'audio-guide se termine (dernière séquence)
Alors le contenu suivant de la playlist démarre
Scénario: Audio-guide marqué comme "Favori"
Étant donné qu'un utilisateur aime un audio-guide
Quand il clique sur " Ajouter aux favoris"
Alors l'audio-guide est ajouté à la section "Favoris"
Et il est facilement accessible depuis le menu principal
Scénario: Collections thématiques d'audio-guides
Étant donné que RoadWave propose des collections éditoriales
Quand l'utilisateur accède à "Collections"
Alors il voit des collections comme:
| collection | nombre_audio_guides |
| 🏛 Musées de France | 12 |
| 🦁 Parcs animaliers | 8 |
| 🚴 Circuits vélo | 15 |
| 🚗 Routes touristiques | 10 |
# 16.16 - Partage d'audio-guide
Scénario: Bouton partager sur page audio-guide
Étant donné qu'un utilisateur consulte un audio-guide
Quand il clique sur " Partager"
Alors le menu de partage natif s'ouvre
Et le lien généré est "https://roadwave.fr/share/ag/louvre_123"
Scénario: Page web de partage pour audio-guide
Étant donné qu'un lien d'audio-guide partagé est ouvert sur le web
Quand la page se charge
Alors elle affiche:
| élément | exemple |
| Cover image 16:9 | Photo du Louvre |
| Titre | "Visite du Louvre" |
| Créateur | "@art_guide " |
| Badge type | "🎧 Audio-guide 12 séquences" |
| Durée totale | "45 minutes" |
| Mode | "🚶 Piéton" |
| Description | Texte complet |
| Preview séquence 1 | Player HTML5 (séquence intro) |
| Carte avec points GPS | Leaflet avec 12 markers |
| CTA téléchargement | Boutons App Store / Google Play |
Scénario: Deep link vers audio-guide spécifique
Étant donné que l'app est installée
Et qu'un lien "https://roadwave.fr/share/ag/louvre_123" est cliqué
Quand le système détecte l'app
Alors l'app s'ouvre directement sur l'audio-guide
Et l'utilisateur peut démarrer immédiatement
Scénario: Partage avec séquence spécifique
Étant donné qu'un utilisateur est sur la séquence 5 "La Joconde"
Quand il partage l'audio-guide
Alors le lien généré est "https://roadwave.fr/share/ag/louvre_123?seq=5"
Et le destinataire est dirigé vers la séquence 5 directement
# 16.17 - Notations et commentaires
Scénario: Note globale de l'audio-guide
Étant donné qu'un utilisateur termine un audio-guide
Quand la dernière séquence se termine
Alors une popup de notation s'affiche:
"""
Comment avez-vous trouvé cet audio-guide ?
[Ajouter un commentaire (optionnel)]
"""
Scénario: Note moyenne affichée sur la page
Étant donné qu'un audio-guide a reçu 150 notes
Et que la moyenne est 4.3/5
Quand la page est affichée
Alors la note " 4.3 (150 avis)" est visible
Scénario: Commentaires triés par pertinence
Étant donné qu'un audio-guide a 50 commentaires
Quand l'utilisateur consulte les avis
Alors les commentaires sont triés par défaut selon:
| critère | poids |
| Note élevée | 30% |
| Récent | 30% |
| Likes reçus | 40% |
Scénario: Réponse du créateur aux commentaires
Étant donné qu'un utilisateur laisse un commentaire négatif
Quand le créateur consulte son dashboard
Alors il peut répondre au commentaire
Et sa réponse apparaît en dessous avec badge "Créateur"
# 16.18 - Recommandations intelligentes
Scénario: Audio-guides similaires recommandés
Étant donné qu'un utilisateur termine "Visite du Louvre"
Quand il consulte les recommandations
Alors l'algorithme suggère des audio-guides basés sur:
| critère | exemple |
| Tags similaires | #Art #Histoire #Musée |
| Créateur identique | Autres audio-guides de @art_guide |
| Localisation proche | Autres musées parisiens |
| Mode de déplacement | Autres audio-guides piéton |
Scénario: Suggestion géographique contextuelle
Étant donné qu'un utilisateur est à Paris (GPS détecté)
Quand il ouvre l'onglet "Audio-guides"
Alors les audio-guides parisiens sont mis en avant
Et un filtre "🗺 Autour de moi" est pré-appliqué
Scénario: Badge "Populaire dans votre région"
Étant donné qu'un audio-guide a >100 écoutes dans la région Île-de-France
Et que l'utilisateur est en Île-de-France
Quand l'audio-guide est affiché
Alors un badge "🔥 Populaire près de chez vous" est visible
# 16.19 - Optimisations techniques
Scénario: Préchargement de la séquence suivante
Étant donné que la séquence 3 est en cours à 2:30/3:42
Quand il reste 60 secondes de lecture
Alors la séquence 4 est préchargée en arrière-plan
Et la transition est instantanée (0 latence)
Scénario: Buffer adaptatif selon connexion
Étant donné qu'un utilisateur est sur réseau 4G
Quand la séquence démarre
Alors 30 secondes d'audio sont bufferisées initialement
Et le buffering continue en arrière-plan
Plan du Scénario: Buffer selon qualité réseau
Étant donné qu'un utilisateur est sur réseau <reseau>
Quand une séquence démarre
Alors <buffer_secondes> secondes sont bufferisées
Exemples:
| reseau | buffer_secondes |
| WiFi | 60 |
| 5G | 45 |
| 4G | 30 |
| 3G | 20 |
Scénario: Compression audio adaptative
Étant donné qu'un utilisateur est sur connexion lente (3G)
Quand une séquence est streamée
Alors le CDN sert la version 64 kbps (au lieu de 128 kbps)
Et la qualité reste acceptable pour la voix
Scénario: Cache intelligent des séquences jouées
Étant donné qu'un utilisateur a écouté les séquences 1-5
Quand il clique sur "Précédent" pour réécouter la séquence 4
Alors la séquence 4 est chargée depuis le cache local
Et le chargement est instantané (pas de stream)
Scénario: Nettoyage automatique du cache
Étant donné que le cache audio occupe 500 MB
Et que la limite configurée est 300 MB
Quand le nettoyage automatique s'exécute
Alors les séquences les plus anciennes (non téléchargées) sont supprimées
Et le cache revient à 280 MB
# 16.20 - Analytics et tracking
Scénario: Tracking des événements clés
Étant donné qu'un utilisateur écoute un audio-guide
Quand il interagit avec l'application
Alors les événements suivants sont trackés:
| événement | données |
| audio_guide_started | audio_guide_id, mode, user_id |
| sequence_completed | sequence_id, completion_rate, duration |
| audio_guide_completed | audio_guide_id, total_time, sequences_count|
| point_gps_triggered | point_id, distance, auto_or_manual |
| point_gps_missed | point_id, distance, action_taken |
| paywall_displayed | audio_guide_id, sequence_number |
| premium_conversion | source: audio_guide_paywall |
Scénario: Heatmap des abandons par séquence
Étant donné qu'un audio-guide a été écouté 1000 fois
Quand le créateur consulte la heatmap
Alors il voit pour chaque séquence:
| sequence | starts | completions | abandon_rate |
| 1 | 1000 | 950 | 5% |
| 2 | 950 | 920 | 3% |
| 3 | 920 | 850 | 8% |
| ... | ... | ... | ... |
| 12 | 650 | 580 | 11% |
Scénario: Attribution GPS auto vs manuel
Étant donné un audio-guide voiture avec 8 points GPS
Quand les statistiques sont calculées
Alors le créateur voit:
| mode_declenchement | nombre |
| GPS automatique | 542 |
| Manuel | 123 |
| Point manqué | 89 |
# Cas d'erreur et edge cases
Scénario: Audio-guide avec une seule séquence (edge case)
Étant donné un audio-guide avec seulement 1 séquence
Quand il est publié
Alors un avertissement s'affiche:
"""
Un audio-guide doit contenir au minimum 2 séquences
Ajoutez au moins 1 séquence supplémentaire avant publication
"""
Scénario: Séquence manquante ou corrompue
Étant donné qu'une séquence 5 a un fichier audio corrompu
Quand l'utilisateur tente de la lire
Alors un message d'erreur s'affiche
Et un bouton " Passer à la suivante" est disponible
Et le créateur reçoit une notification de l'erreur
Scénario: GPS désactivé puis réactivé en cours de route
Étant donné un audio-guide voiture en cours
Et que l'utilisateur désactive le GPS
Quand il le réactive 10 minutes plus tard
Alors le déclenchement automatique reprend
Et les points GPS manqués entre-temps ne déclenchent pas de popup
Scénario: Modification d'audio-guide avec utilisateurs en cours
Étant donné qu'un audio-guide a 50 utilisateurs en cours d'écoute
Quand le créateur modifie une séquence
Alors les utilisateurs actuels conservent l'ancienne version
Et les nouveaux utilisateurs obtiennent la nouvelle version
Et un message informe les utilisateurs lors de la prochaine ouverture
Scénario: Suppression d'audio-guide par le créateur
Étant donné qu'un audio-guide a 20 utilisateurs avec progression
Quand le créateur supprime l'audio-guide
Alors une confirmation stricte est demandée
Et si confirmé, les progressions utilisateurs sont archivées (30 jours)
Et l'audio-guide devient inaccessible
Scénario: Signalement d'audio-guide pour contenu inapproprié
Étant donné qu'un utilisateur signale un audio-guide
Quand le signalement est modéré
Et jugé valide
Alors l'audio-guide est dépublié temporairement
Et le créateur reçoit une notification d'explication
Et il peut corriger puis republier

View File

@@ -0,0 +1,243 @@
# language: fr
Fonctionnalité: Audio-guide mode piéton (navigation manuelle)
En tant qu'utilisateur à pied
Je veux naviguer manuellement entre les séquences d'un audio-guide
Afin de contrôler mon rythme de visite
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté (gratuit)
Et qu'un audio-guide piéton "Visite du Louvre" est disponible avec 12 séquences
# 16.2.1 - Passage entre séquences
Scénario: Fin de séquence normale avec pause automatique
Étant donné que la séquence 1 "Introduction" est en cours de lecture
Quand la séquence se termine à 2:15
Alors le player se met en pause automatiquement
Et le message suivant s'affiche: "Séquence 1 terminée. Appuyez sur Suivant quand vous êtes prêt."
Et la barre de progression indique "1/12 complétée"
Scénario: Passage manuel à la séquence suivante
Étant donné que la séquence 1 est terminée et le player en pause
Quand l'utilisateur appuie sur le bouton [|] "Suivant"
Alors la séquence 2 "Pyramide du Louvre" démarre immédiatement
Et aucune latence n'est observée
Scénario: Séquence avec publicité (1/5 séquences)
Étant donné que la séquence 5 se termine
Et que c'est la 5ème séquence (1 pub toutes les 5)
Quand la séquence se termine
Alors la publicité s'enchaîne automatiquement (sans attente bouton)
Et la publicité se lit normalement
Et elle est skippable après 5 secondes
Scénario: Fin de publicité avec pause automatique
Étant donné qu'une publicité est en cours de lecture
Quand la publicité se termine
Alors le player se met en pause automatiquement
Et le message suivant s'affiche: "Séquence 6 prête. Appuyez sur Suivant."
Et l'utilisateur doit cliquer sur [|] pour continuer
Scénario: Flux complet séquence → pub → séquence
Étant donné que la séquence 5 démarre
Quand la séquence 5 se termine
Alors la publicité démarre automatiquement
Quand la publicité se termine
Alors le player se met en pause
Quand l'utilisateur clique sur [|]
Alors la séquence 6 démarre
Plan du Scénario: Fréquence de publicité configurable
Étant donné que l'utilisateur gratuit écoute un audio-guide
Et que la fréquence pub est configurée à <frequence>
Quand il termine la séquence <numero_sequence>
Alors une publicité est insérée : <pub_inseree>
Exemples:
| frequence | numero_sequence | pub_inseree |
| 1/5 | 5 | Oui |
| 1/5 | 10 | Oui |
| 1/5 | 4 | Non |
| 1/3 | 3 | Oui |
| 1/3 | 6 | Oui |
Scénario: Utilisateur Premium sans publicités
Étant donné que l'utilisateur "premium@example.com" est abonné Premium
Et qu'il écoute un audio-guide piéton
Quand il termine la séquence 5
Alors aucune publicité n'est insérée
Et le player se met en pause immédiatement
Et le message "Séquence 6 prête. Appuyez sur Suivant." s'affiche
# 16.2.2 - Navigation et contrôles
Scénario: Boutons de contrôle disponibles en mode piéton
Étant donné qu'un audio-guide piéton est en lecture
Quand l'utilisateur consulte les contrôles
Alors les boutons suivants sont visibles:
| bouton | fonction |
| [\|] Suivant | Passe à la séquence suivante |
| [\|] Précédent | Retour à la séquence précédente |
| [] Pause | Pause temporaire |
| [] Play | Reprend la lecture |
| [📋] Liste | Affiche toutes les séquences |
Scénario: Passage à la séquence suivante pendant la lecture
Étant donné que la séquence 3 "La Joconde" est en cours à 1:42/3:42
Quand l'utilisateur clique sur [|] "Suivant"
Alors la séquence 4 "Vénus de Milo" démarre immédiatement
Et la séquence 3 n'est pas marquée comme écoutée (car <80%)
Scénario: Retour à la séquence précédente (saut direct)
Étant donné que la séquence 5 est en cours de lecture
Quand l'utilisateur clique sur [|◀] "Précédent"
Alors la séquence 4 démarre depuis le début (0:00)
Et il n'y a pas de logique "replay si >10s" (contrairement au contenu classique)
Scénario: Pause et reprise pendant une séquence
Étant donné que la séquence 2 est en cours à 1:15/1:48
Quand l'utilisateur clique sur [] "Pause"
Alors la lecture se met en pause
Et la position 1:15 est conservée
Quand l'utilisateur clique sur [] "Play"
Alors la lecture reprend exactement à 1:15
Scénario: Interface liste des séquences
Étant donné qu'un audio-guide de 12 séquences est en cours
Quand l'utilisateur clique sur [📋] "Liste séquences"
Alors une liste complète s'affiche avec:
| élément | exemple |
| Numéro et titre | "3. La Joconde" |
| Durée | (3:42) |
| État | Écouté / En cours / À écouter |
| Date écoute (si écouté) | "Écouté le 15/01/2026" |
Scénario: Séquence en cours dans la liste
Étant donné que la séquence 3 est en cours à 1:22/3:42
Quand la liste des séquences est affichée
Alors la séquence 3 affiche:
"""
3. La Joconde (3:42) - EN COURS
1:22/3:42
"""
Scénario: Navigation libre vers séquence non encore écoutée
Étant donné que l'utilisateur est sur la séquence 3
Et que les séquences 4 à 12 n'ont pas été écoutées
Quand l'utilisateur clique sur "8. Les Appartements de Napoléon"
Alors la séquence 8 démarre immédiatement depuis 0:00
Et les séquences 4 à 7 restent marquées "À écouter"
Scénario: Retour à une séquence déjà écoutée
Étant donné que la séquence 2 "Pyramide du Louvre" a été écoutée à 100%
Et qu'elle est marquée "Écouté"
Quand l'utilisateur clique dessus dans la liste
Alors la séquence 2 démarre depuis 0:00
Et le statut est conservé
Scénario: Checkmarks sur séquences écoutées >80%
Étant donné que l'utilisateur écoute la séquence 2 de durée 1:48
Quand il écoute jusqu'à 1:30 (83% de complétion)
Et qu'il passe à la séquence suivante
Alors la séquence 2 est marquée "Écouté"
Et la date d'écoute est enregistrée
Scénario: Pas de checkmark si séquence écoutée <80%
Étant donné que l'utilisateur écoute la séquence 3 de durée 3:42
Quand il écoute jusqu'à 1:30 (40% de complétion)
Et qu'il passe à la séquence suivante
Alors la séquence 3 reste marquée "À écouter"
Scénario: Bouton "Tout afficher" si plus de 6 séquences
Étant donné un audio-guide avec 12 séquences
Quand la liste est affichée
Alors seules les 6 premières séquences sont visibles initialement
Et un bouton "Tout afficher " est présent
Quand l'utilisateur clique sur "Tout afficher "
Alors les 6 séquences restantes sont affichées
Scénario: Saut vers séquence spécifique depuis la barre de progression
Étant donné qu'un audio-guide est en cours
Quand l'utilisateur clique sur "3/12" dans la barre de progression
Alors la liste des séquences s'ouvre
Et la séquence en cours (3) est mise en surbrillance
# Sauvegarde progression
Scénario: Position exacte sauvegardée automatiquement
Étant donné que la séquence 5 est en cours à 2:34/4:10
Quand l'utilisateur quitte l'application
Alors la position 2:34 dans la séquence 5 est sauvegardée
Et la sauvegarde est effectuée localement (SQLite)
Et la sauvegarde est synchronisée sur le cloud (PostgreSQL)
Scénario: Reprise après fermeture de l'application
Étant donné que l'utilisateur a quitté l'app à la séquence 5 position 2:34
Quand il rouvre l'audio-guide
Alors une popup de reprise s'affiche
Quand il clique sur " Reprendre"
Alors la lecture reprend à la séquence 5 position 2:34 exacte
# Cas d'usage réels
Scénario: Visiteur qui connaît déjà certaines œuvres
Étant donné qu'un visiteur du Louvre démarre l'audio-guide
Et qu'il connaît déjà "La Joconde" (séquence 3)
Quand il arrive à la séquence 3
Et qu'il clique sur [|] "Suivant" après 10 secondes
Alors la séquence 4 démarre immédiatement
Et la séquence 3 n'est pas marquée comme écoutée
Scénario: Visiteur qui veut voir une œuvre éloignée
Étant donné qu'un visiteur est à la séquence 2
Et qu'il aperçoit "La Victoire de Samothrace" (séquence 8) physiquement
Quand il ouvre la liste et clique sur la séquence 8
Alors la séquence 8 démarre immédiatement
Et il peut écouter la description même si les séquences 3-7 ne sont pas écoutées
Scénario: Visiteur qui prend une pause café
Étant donné qu'un visiteur écoute la séquence 6
Quand il clique sur [] "Pause"
Et qu'il ferme l'application pendant 30 minutes
Quand il rouvre l'application
Alors la séquence 6 reprend à la position exacte où il s'était arrêté
Scénario: Visiteur qui revient le lendemain
Étant donné qu'un visiteur a écouté les séquences 1-5 hier
Et qu'il revient au musée aujourd'hui
Quand il ouvre l'audio-guide
Alors une popup propose " Reprendre" (séquence 6)
Et les séquences 1-5 sont marquées "Écouté"
# Cas d'erreur
Scénario: Séquence audio corrompue ou indisponible
Étant donné que la séquence 7 a un fichier audio corrompu
Quand l'utilisateur tente de la lire
Alors un message d'erreur s'affiche:
"""
Cette séquence est temporairement indisponible.
[ Passer à la suivante] [🔄 Réessayer]
"""
Scénario: Perte de connexion pendant le chargement
Étant donné que l'utilisateur lance la séquence 4
Et que la connexion réseau est perdue
Quand le chargement échoue
Alors un message s'affiche: "Connexion perdue. Vérifiez votre réseau."
Et un bouton "🔄 Réessayer" est disponible
Scénario: Batterie faible en cours de visite
Étant donné que la batterie de l'appareil est à 5%
Quand l'utilisateur écoute une séquence
Alors une notification système s'affiche: "Batterie faible. Progression sauvegardée."
Et la position est sauvegardée localement toutes les 10 secondes
Scénario: Mode piéton sans points GPS (pas d'alerte localisation)
Étant donné un audio-guide en mode piéton
Et que le GPS est désactivé
Quand l'utilisateur démarre l'audio-guide
Alors aucune alerte GPS ne s'affiche
Et l'audio-guide fonctionne normalement (navigation 100% manuelle)

View File

@@ -0,0 +1,400 @@
# language: fr
Fonctionnalité: Audio-guide mode voiture (GPS automatique)
En tant qu'utilisateur en voiture
Je veux que les séquences se déclenchent automatiquement selon ma position GPS
Afin de profiter d'une expérience guidée hands-free
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté (gratuit)
Et qu'un audio-guide voiture "Safari du Paugre" est disponible avec 8 séquences
Et que le GPS est activé
# 16.3.1 - Déclenchement et contrôles
Scénario: Distinction audio-guides vs contenus géolocalisés simples
Étant donné que l'utilisateur est en mode voiture
Quand il écoute un contenu géolocalisé simple (1 séquence unique)
Alors une notification avec compteur 71 est affichée 7s avant le point
Et il doit valider avec "Suivant" + décompte 5s
Et ce contenu compte 1/6 dans le quota horaire
Quand il démarre un audio-guide multi-séquences
Alors les séquences se déclenchent au point GPS exact (rayon 30m)
Et aucun compteur 7s n'est affiché (juste notification "Ding" + toast 2s)
Et l'audio-guide entier compte 1/6 dans le quota
Scénario: Démarrage automatique au premier point GPS
Étant donné que l'utilisateur démarre l'audio-guide "Safari du Paugre"
Et que le point de départ est à (43.1234, 2.5678) avec rayon 30m
Quand l'utilisateur entre dans le rayon de 30m
Alors la séquence 1 "Introduction - Point d'accueil" démarre automatiquement
Et une notification sonore "Ding" est jouée (non intrusif)
Et un toast s'affiche brièvement pendant 2s: "Introduction - Point d'accueil"
Et aucun compteur 71 n'est affiché (contrairement aux contenus géolocalisés simples)
Scénario: Déclenchement automatique séquence suivante
Étant donné que la séquence 1 est terminée
Et que l'utilisateur se déplace vers le point GPS 2 (43.1245, 2.5690)
Quand l'utilisateur entre dans le rayon de 30m du point 2
Alors la séquence 2 "Enclos des lions" démarre automatiquement
Et une notification "Ding" + toast "Enclos des lions" s'affiche
Scénario: Navigation manuelle conservée (bouton Suivant actif)
Étant donné que la séquence 1 est en cours
Et que l'utilisateur est encore loin du point GPS 2 (distance 500m)
Quand l'utilisateur clique sur [|] "Suivant"
Alors la séquence 2 démarre immédiatement
Et aucune vérification GPS n'est effectuée
Scénario: Navigation manuelle conservée (bouton Précédent actif)
Étant donné que la séquence 3 est en cours
Quand l'utilisateur clique sur [|] "Précédent"
Alors la séquence 2 démarre depuis le début
Et aucune vérification GPS n'est effectuée
Scénario: Tous les boutons de contrôle restent actifs
Étant donné qu'un audio-guide voiture est en cours
Quand l'utilisateur consulte les contrôles
Alors les boutons suivants sont actifs:
| bouton | état | comportement |
| [\|] Suivant | | Passe séquence suivante immédiate |
| [\|] Précédent | | Retour séquence précédente |
| [] Pause | | Pause temporaire |
| [📋] Liste | | Saut direct possible |
Scénario: Use case - Embouteillage (séquence finie, point GPS loin)
Étant donné que la séquence 3 "Enclos des girafes" est terminée
Et que le point GPS 4 est à 2 km de distance (embouteillage)
Quand l'utilisateur clique manuellement sur [|] "Suivant"
Alors la séquence 4 démarre immédiatement
Et l'utilisateur peut continuer l'expérience sans attendre d'atteindre le point GPS
Scénario: Use case - Route fermée (point GPS inaccessible)
Étant donné que le point GPS 5 est sur une route fermée
Et que l'utilisateur ne peut pas s'en approcher
Quand l'utilisateur clique sur [|] "Suivant"
Alors la séquence 5 démarre quand même
Et l'audio-guide continue normalement
Scénario: Use case - Passager manipule l'application
Étant donné que l'utilisateur est passager (non conducteur)
Et que la vitesse du véhicule est 45 km/h
Quand le passager clique sur [|] "Suivant"
Alors la séquence suivante démarre
Et un avertissement s'affiche pendant 3 secondes
Scénario: Avertissement sécurité si vitesse >10 km/h
Étant donné que la vitesse actuelle est 35 km/h
Quand l'utilisateur clique sur un bouton (Suivant ou Précédent)
Alors l'action est exécutée immédiatement (pas de blocage)
Et un toast s'affiche pendant 3 secondes:
"""
Manipulation en conduite détectée.
Pour votre sécurité, demandez à un passager.
"""
Plan du Scénario: Avertissement selon la vitesse
Étant donné que la vitesse actuelle est <vitesse> km/h
Quand l'utilisateur clique sur un bouton de navigation
Alors l'avertissement est affiché : <avertissement>
Exemples:
| vitesse | avertissement |
| 5 | Non |
| 10 | Non |
| 11 | Oui |
| 35 | Oui |
| 90 | Oui |
# 16.3.2 - Affichage distance et guidage
Scénario: Affichage entre deux séquences avec progress bar
Étant donné que la séquence 2 "Les lions" vient de se terminer
Et que le prochain point GPS 3 "Enclos des girafes" est à 500m
Quand l'interface bascule en mode "attente prochain point"
Alors l'écran affiche:
| élément | description |
| Statut séquence | " Séquence 2/8 terminée" |
| Nom séquence | "Les lions" |
| Progress bar | Barre dynamique remplie selon distance (0%) |
| Distance prochain point| "500 mètres" |
| ETA | " 1 minute 30" |
| Direction | |
| Vitesse actuelle | "28 km/h" |
| Bouton "Rejouer séq." | Permet de réécouter la séquence qui vient de finir |
Scénario: Progress bar dynamique vers le prochain point
Étant donné que la distance initiale vers le prochain point était 500m
Et que la séquence précédente est terminée
Quand l'utilisateur se rapproche du prochain point
Et que la distance actuelle est 175m
Alors la progress bar affiche "65%" remplie
Et le calcul est: 100 - (175 / 500 * 100) = 65%
Et la barre se met à jour chaque seconde
Scénario: Bouton "Rejouer séq." pour réécouter
Étant donné que la séquence 3 vient de se terminer
Et que l'interface "attente prochain point" est affichée
Quand l'utilisateur clique sur [ Rejouer séq.]
Alors la séquence 3 redémarre depuis 0:00
Et l'utilisateur peut la réécouter (utile si distraction)
Scénario: Interface en conduite avec distance et ETA
Étant donné que la séquence 2 est en cours
Et que le prochain point GPS 3 "Enclos des girafes" est à 320m
Et que la vitesse actuelle est 28 km/h
Quand l'interface est affichée
Alors les informations suivantes sont visibles:
| information | valeur |
| Nom prochain point | "Enclos des girafes" |
| Distance | "320 mètres" |
| ETA | " 40 secondes" |
| Direction | (flèche direction) |
| Vitesse actuelle | "28 km/h" |
| Vitesse recommandée | "20-30 km/h" |
Scénario: Mise à jour de la distance en temps réel
Étant donné que la distance au prochain point est 500m
Quand 10 secondes s'écoulent et que l'utilisateur se rapproche
Alors la distance est mise à jour chaque seconde
Et la nouvelle distance "450m" s'affiche
Scénario: Mise à jour de l'ETA en temps réel
Étant donné que l'ETA est " 2 minutes"
Et que la vitesse est constante à 30 km/h
Quand l'utilisateur se rapproche du point
Alors l'ETA est recalculé chaque seconde
Et il diminue progressivement: " 1 minute 50", " 1 minute 40", etc.
Plan du Scénario: Format d'affichage de la distance
Étant donné que la distance au prochain point est <distance_metres>
Quand l'interface est mise à jour
Alors la distance affichée est "<affichage>"
Exemples:
| distance_metres | affichage |
| 50 | 50 m |
| 320 | 320 m |
| 980 | 980 m |
| 1200 | 1.2 km |
| 5400 | 5.4 km |
Plan du Scénario: Format d'affichage de l'ETA
Étant donné que l'ETA calculé est <secondes> secondes
Quand l'interface est mise à jour
Alors l'ETA affiché est "<affichage>"
Exemples:
| secondes | affichage |
| 30 | 30 secondes |
| 75 | 1 minute |
| 150 | 2 minutes |
| 400 | 6 minutes |
Scénario: Calcul de la direction (flèche 8 directions)
Étant donné que la position actuelle est (43.1234, 2.5678)
Et que le prochain point est au nord-est (angle 45°)
Quand la direction est calculée
Alors la flèche "" est affichée
Plan du Scénario: Flèches de direction selon l'angle
Étant donné que l'angle vers le prochain point est <angle>°
Quand la direction est calculée
Alors la flèche "<fleche>" est affichée
Exemples:
| angle | fleche |
| 0 | |
| 45 | |
| 90 | |
| 135 | |
| 180 | |
| 225 | |
| 270 | |
| 315 | |
Scénario: Mise à jour de la direction toutes les 5 secondes
Étant donné que la direction actuelle est (nord)
Et que l'utilisateur tourne vers l'est
Quand 5 secondes s'écoulent
Alors la direction est recalculée
Et la nouvelle flèche (nord-est) s'affiche
Scénario: Message "En attente de déplacement" si vitesse <5 km/h
Étant donné que la vitesse actuelle est 2 km/h (arrêté)
Quand l'ETA est calculé
Alors le message "En attente de déplacement" s'affiche
Et l'ETA n'est pas calculé (car vitesse insuffisante)
Scénario: Simplicité de l'interface (pas de carte miniature)
Étant donné qu'un audio-guide voiture est en cours
Quand l'interface est affichée
Alors aucune carte miniature n'est présente
Et seuls les éléments essentiels sont affichés:
| élément |
| Distance |
| ETA |
| Direction (flèche) |
| Vitesse |
| Contrôles audio |
# 16.3.3 - Rayon de déclenchement et tolérance
Scénario: Rayon de déclenchement par défaut en mode voiture
Étant donné un audio-guide voiture
Quand un point GPS est défini
Alors le rayon de déclenchement est 30 mètres par défaut
Et le rayon de tolérance "point manqué" est 100 mètres
Scénario: Déclenchement dans le rayon (30m)
Étant donné que le point GPS 3 est défini avec rayon 30m
Quand l'utilisateur entre à 25m du point
Alors la séquence 3 se déclenche automatiquement
Scénario: Pas de déclenchement hors rayon
Étant donné que le point GPS 3 a un rayon de 30m
Quand l'utilisateur passe à 45m du point
Alors la séquence 3 ne se déclenche pas automatiquement
Scénario: Point manqué dans rayon de tolérance (100m)
Étant donné que l'utilisateur passe à 60m du point GPS 4 (hors rayon 30m)
Et que 60m < 100m (rayon tolérance)
Quand le point est détecté comme manqué
Alors un toast s'affiche: "⚠️ Point manqué : Enclos des éléphants"
Et une popup s'affiche pendant 5 secondes avec 3 options
Scénario: Popup "Point manqué" avec 3 actions
Étant donné qu'un point GPS a été manqué (distance 60m)
Quand la popup s'affiche
Alors les options suivantes sont disponibles:
| bouton | icône | comportement |
| Écouter quand même | 🔊 | Lance séquence immédiatement (même hors zone) |
| Passer au suivant | ⏭️ | Skip séquence, continue vers prochain point |
| Faire demi-tour | 🔙 | Ouvre GPS externe (Google Maps/Waze) vers point |
Scénario: Action "Écouter quand même"
Étant donné qu'un point GPS est manqué
Quand l'utilisateur clique sur "🔊 Écouter quand même"
Alors la séquence correspondante démarre immédiatement
Et l'utilisateur peut continuer sa route
Scénario: Action "Passer au suivant"
Étant donné qu'un point GPS 5 est manqué
Quand l'utilisateur clique sur "⏭️ Passer au suivant"
Alors la séquence 5 est ignorée (non écoutée)
Et l'application attend le point GPS 6
Et la distance vers le point 6 s'affiche
Scénario: Action "Faire demi-tour"
Étant donné qu'un point GPS est manqué à (43.1250, 2.5700)
Quand l'utilisateur clique sur "🔙 Faire demi-tour"
Alors l'application détecte l'app GPS installée (Google Maps ou Waze)
Et ouvre la navigation GPS externe vers (43.1250, 2.5700)
Scénario: Point manqué au-delà du rayon de tolérance (>100m)
Étant donné que l'utilisateur passe à 150m du point GPS 6
Quand la distance est détectée
Alors aucune popup ne s'affiche (point trop loin)
Et l'utilisateur peut naviguer manuellement avec [|]
Plan du Scénario: Gestion selon la distance au point
Étant donné un point GPS avec rayon 30m et tolérance 100m
Quand l'utilisateur passe à <distance> du point
Alors le comportement est <comportement>
Exemples:
| distance | comportement |
| 20m | Déclenchement automatique séquence |
| 40m | Rien (hors rayon, pas encore tolérance) |
| 60m | Popup "Point manqué" avec 3 options |
| 110m | Rien (trop loin, hors tolérance) |
Scénario: Configuration rayon personnalisé par le créateur
Étant donné qu'un créateur définit un rayon de 50m (au lieu de 30m)
Quand un utilisateur entre à 45m du point
Alors la séquence se déclenche automatiquement
Et le rayon personnalisé est respecté
Scénario: Rayon minimum et maximum configurables
Étant donné qu'un créateur configure un rayon
Quand il ajuste le curseur
Alors les valeurs disponibles sont de 10m à 200m
Et le rayon par défaut suggéré est 30m pour la voiture
# Cas d'usage réels
Scénario: Safari-parc avec déclenchement automatique fluide
Étant donné qu'un utilisateur roule dans un safari à 20 km/h
Quand il passe devant "Enclos des lions" (point GPS 2)
Alors la séquence 2 démarre automatiquement sans intervention
Et il peut se concentrer sur la conduite et l'observation
Scénario: Détour imprévu (travaux sur la route)
Étant donné qu'un utilisateur prend un détour à cause de travaux
Et que le point GPS 4 devient inaccessible
Quand il est loin du point (>100m)
Et qu'il clique manuellement sur [|]
Alors la séquence 4 démarre quand même
Et l'expérience continue sans blocage
Scénario: Passager qui navigue librement
Étant donné qu'un passager utilise l'application
Et que le conducteur roule à 50 km/h
Quand le passager clique sur "Précédent" pour réécouter
Alors l'action est exécutée immédiatement
Et un warning apparaît brièvement (sensibilisation)
Scénario: Embouteillage prolongé
Étant donné que la séquence 3 est terminée depuis 10 minutes
Et que l'utilisateur est bloqué dans un embouteillage
Et que le point GPS 4 est encore à 1.5 km
Quand l'utilisateur clique sur [|]
Alors la séquence 4 démarre immédiatement
Et l'utilisateur peut passer le temps en écoutant
# Cas d'erreur
Scénario: GPS désactivé en mode voiture
Étant donné qu'un audio-guide voiture est démarré
Et que le GPS est désactivé
Quand l'application détecte l'absence de GPS
Alors une alerte s'affiche:
"""
GPS requis pour le mode Voiture
Activez la localisation pour profiter du déclenchement automatique.
[Activer GPS] [Passer en mode Manuel]
"""
Scénario: Action "Passer en mode Manuel"
Étant donné que le GPS est désactivé
Quand l'utilisateur clique sur "Passer en mode Manuel"
Alors l'audio-guide bascule en navigation 100% manuelle
Et les boutons [|] et [|] permettent de naviguer
Et aucun déclenchement GPS n'est tenté
Scénario: Précision GPS insuffisante
Étant donné que le signal GPS a une précision de ±150m
Quand l'utilisateur approche d'un point GPS avec rayon 30m
Alors un avertissement s'affiche:
"""
Signal GPS imprécis (±150m)
Le déclenchement automatique peut être perturbé.
Utilisez les boutons manuels si nécessaire.
"""
Scénario: Perte signal GPS en cours de route
Étant donné qu'un audio-guide voiture est en cours
Quand le signal GPS est perdu (tunnel, parking souterrain)
Alors un toast s'affiche: "Signal GPS perdu. Navigation manuelle active."
Et les boutons de navigation restent actifs
Quand le signal GPS revient
Alors un toast s'affiche: "Signal GPS rétabli"
Et le déclenchement automatique est réactivé
Scénario: Dépassement de la vitesse recommandée
Étant donné qu'un audio-guide recommande 20-30 km/h
Et que l'utilisateur roule à 65 km/h
Quand la vitesse est détectée
Alors l'affichage vitesse est en orange: " 65 km/h"
Et un message info s'affiche: "Vitesse élevée. Risque de manquer des points."

View File

@@ -0,0 +1,274 @@
# language: fr
Fonctionnalité: Audio-guides modes vélo et transport
En tant qu'utilisateur à vélo ou en transport en commun
Je veux profiter d'un guidage GPS adapté à mon mode de déplacement
Afin d'avoir une expérience optimisée avec tolérances appropriées
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté
Et que le GPS est activé
# 16.4 - Modes Vélo et Transport
Plan du Scénario: Paramètres par mode de déplacement
Étant donné un audio-guide configuré en mode <mode>
Alors les paramètres suivants sont appliqués:
| paramètre | valeur |
| Rayon déclenchement | <rayon_declenchement> |
| Rayon tolérance "point manqué" | <rayon_tolerance> |
| Vitesse recommandée | <vitesse_recommandee> |
| Seuil warning sécurité | <seuil_warning> |
Exemples:
| mode | rayon_declenchement | rayon_tolerance | vitesse_recommandee | seuil_warning |
| Voiture | 30m | 100m | 20-50 km/h | >10 km/h |
| Vélo | 50m | 75m | 10-25 km/h | >5 km/h |
| Transport | 100m | 150m | Variable | Désactivé |
# Mode Vélo
Scénario: Déclenchement automatique avec rayon 50m (mode vélo)
Étant donné un audio-guide vélo "Circuit des châteaux de la Loire"
Et que le point GPS 3 a un rayon de 50m
Quand l'utilisateur à vélo entre à 45m du point
Alors la séquence 3 "Château de Chambord" se déclenche automatiquement
Scénario: Rayon plus large justifié pour le vélo
Étant donné qu'un cycliste roule sur piste cyclable
Et que sa vitesse varie entre 8 et 22 km/h (arrêts fréquents)
Et que le tracé est moins prévisible qu'en voiture
Quand un point GPS avec rayon 50m est défini
Alors le rayon plus large compense la variabilité de trajectoire
Scénario: Warning sécurité dès 5 km/h en vélo
Étant donné un audio-guide vélo en cours
Et que la vitesse actuelle est 12 km/h
Quand l'utilisateur clique sur [|] "Suivant"
Alors l'action est exécutée
Et un warning s'affiche: " Manipulation en déplacement détecté. Pour votre sécurité, arrêtez-vous."
Plan du Scénario: Warning vélo selon la vitesse
Étant donné que la vitesse actuelle à vélo est <vitesse> km/h
Quand l'utilisateur clique sur un bouton de navigation
Alors le warning est affiché : <warning>
Exemples:
| vitesse | warning |
| 0 | Non |
| 4 | Non |
| 6 | Oui |
| 15 | Oui |
| 25 | Oui |
Scénario: Tolérance GPS moins stricte en vélo
Étant donné qu'un cycliste passe à 65m du point GPS 4
Et que le rayon de déclenchement est 50m
Et que le rayon de tolérance est 75m
Quand la distance est détectée
Alors la popup "Point manqué" s'affiche avec 3 options
Et le système tolère l'écart (trajectoire vélo moins prévisible)
Scénario: Affichage adapté au vélo
Étant donné un audio-guide vélo en cours
Quand l'interface est affichée
Alors les informations suivantes sont visibles:
| information | valeur |
| Icône mode | 🚴 |
| Distance prochain point| "450 m" |
| ETA | " 2 minutes" |
| Direction | |
| Vitesse actuelle | "18 km/h" |
| Vitesse recommandée | "10-25 km/h" |
Scénario: Cas d'usage - Piste cyclable avec arrêts fréquents
Étant donné qu'un cycliste suit un circuit nature
Et qu'il s'arrête régulièrement (feux, photos, fatigue)
Quand il s'arrête à 40m d'un point GPS (rayon 50m)
Alors la séquence se déclenche automatiquement
Et le rayon large permet le déclenchement malgré l'arrêt
Scénario: Cas d'usage - Circulation mixte piétons/vélos
Étant donné qu'un cycliste roule sur voie partagée
Et qu'il doit ralentir fréquemment pour éviter les piétons
Quand sa vitesse varie entre 5 et 20 km/h
Alors le système s'adapte avec le rayon 50m
Et le déclenchement reste fiable
# Mode Transport
Scénario: Déclenchement automatique avec rayon 100m (mode transport)
Étant donné un audio-guide transport "Ligne touristique Paris"
Et que le point GPS "Tour Eiffel" a un rayon de 100m
Quand le bus touristique entre à 85m du point
Alors la séquence "Tour Eiffel" se déclenche automatiquement
Scénario: Rayon très large justifié pour le transport
Étant donné qu'un bus touristique suit une ligne fixe
Et qu'il effectue des arrêts fréquents (stations)
Et que l'utilisateur n'a aucun contrôle sur la trajectoire
Quand un point GPS avec rayon 100m est défini
Alors le rayon large compense les arrêts et la ligne fixe
Scénario: Pas de warning sécurité en mode transport
Étant donné un audio-guide transport en cours
Et que le bus roule à 50 km/h
Quand l'utilisateur clique sur [|] "Suivant"
Alors l'action est exécutée immédiatement
Et aucun warning n'est affiché
Parce que l'utilisateur est passager (pas conducteur)
Scénario: Vitesse recommandée "Selon ligne"
Étant donné un audio-guide transport
Quand l'interface est affichée
Alors la vitesse recommandée indique "Selon ligne"
Et aucune valeur fixe n'est affichée (car ligne de transport varie)
Scénario: Tolérance horaire pour retards
Étant donné qu'un bus touristique est en retard de 3 minutes
Et qu'il arrive au point GPS "Musée du Louvre" avec retard
Quand il entre dans le rayon de 100m
Alors la séquence se déclenche normalement
Et le système tolère le retard (pas de pénalité temporelle)
Scénario: Tolérance spatiale très large (150m)
Étant donné qu'un bus passe à 120m du point GPS "Arc de Triomphe"
Et que le rayon de déclenchement est 100m
Et que le rayon de tolérance est 150m
Quand la distance est détectée
Alors la popup "Point manqué" s'affiche avec 3 options
Scénario: Affichage adapté au transport
Étant donné un audio-guide transport en cours
Quand l'interface est affichée
Alors les informations suivantes sont visibles:
| information | valeur |
| Icône mode | 🚌 |
| Distance prochain point| "1.2 km" |
| ETA | " 3 minutes" |
| Direction | |
| Vitesse actuelle | "35 km/h" |
| Vitesse recommandée | "Selon ligne" |
Scénario: Cas d'usage - Bus touristique hop-on hop-off
Étant donné un bus touristique "Paris Open Tour"
Et qu'il suit un circuit fixe avec 15 arrêts
Quand il approche de chaque arrêt
Alors la séquence correspondante se déclenche automatiquement
Et l'utilisateur n'a rien à faire (expérience passive)
Scénario: Cas d'usage - Train panoramique
Étant donné un train touristique "Ligne des Alpes"
Et qu'il roule à vitesse variable (20-80 km/h)
Quand il passe près de points d'intérêt
Alors les séquences se déclenchent avec rayon 100m
Et le système compense la vitesse élevée
# Comportements identiques à la voiture
Scénario: Navigation manuelle conservée (vélo et transport)
Étant donné un audio-guide en mode <mode>
Quand l'utilisateur clique sur [|] ou [|]
Alors les boutons manuels fonctionnent normalement
Et aucune vérification GPS n'est effectuée
Exemples:
| mode |
| Vélo |
| Transport |
Scénario: Affichage distance + ETA + direction (tous modes)
Étant donné un audio-guide en mode <mode>
Quand l'interface est affichée
Alors les informations distance, ETA et direction sont affichées
Et le format est identique au mode voiture
Exemples:
| mode |
| Vélo |
| Transport |
Scénario: Gestion "Point manqué" identique
Étant donné un audio-guide en mode <mode>
Quand un point GPS est manqué (dans rayon tolérance)
Alors la popup avec 3 options s'affiche:
| option |
| 🔊 Écouter quand même |
| Passer au suivant |
| 🔙 Faire demi-tour |
Exemples:
| mode |
| Vélo |
| Transport |
# Publicités (identique tous modes)
Plan du Scénario: Insertion publicité dans tous les modes
Étant donné un utilisateur gratuit écoute un audio-guide en mode <mode>
Quand la séquence 5 se termine (1 pub / 5 séquences)
Alors la publicité s'enchaîne automatiquement
Et elle est skippable après 5 secondes
Exemples:
| mode |
| Voiture |
| Vélo |
| Transport |
| Piéton |
# Cas d'erreur
Scénario: GPS imprécis en forêt (vélo)
Étant donné un cycliste dans une forêt dense
Et que la précision GPS est ±80m
Quand il approche d'un point GPS avec rayon 50m
Alors un avertissement s'affiche:
"""
Signal GPS imprécis (±80m)
Le déclenchement peut être perturbé.
Utilisez les boutons manuels si nécessaire.
"""
Scénario: Bus dévié de son itinéraire (transport)
Étant donné un bus touristique avec déviation
Et que plusieurs points GPS deviennent inaccessibles
Quand l'utilisateur est informé
Alors un message s'affiche:
"""
Itinéraire modifié
Certains points ne seront pas atteints.
Utilisez la navigation manuelle.
"""
Scénario: Changement de mode en cours de route
Étant donné un audio-guide démarré en mode "Vélo"
Quand l'utilisateur décide de continuer à pied
Et qu'il ouvre les paramètres
Alors il peut changer le mode vers "Piéton"
Et les rayons sont reconfigurés automatiquement
Et une confirmation s'affiche:
"""
Mode changé : 🚶 Piéton
Navigation manuelle activée.
"""
Scénario: Détection automatique incohérente
Étant donné qu'un utilisateur marche rapidement (7 km/h)
Et que le système détecte "Vélo" par erreur
Quand la suggestion s'affiche
Alors l'utilisateur peut cliquer sur "Changer"
Et sélectionner manuellement "Piéton"
Scénario: Batterie en mode vélo longue distance
Étant donné un circuit vélo de 50 km avec 20 séquences
Et que l'utilisateur roule pendant 3 heures
Quand la batterie atteint 15%
Alors une notification suggère:
"""
🔋 Batterie à 15%
Recommandé : activer mode économie d'énergie
(Désactive affichage continu distance)
[Activer] [Ignorer]
"""

View File

@@ -0,0 +1,307 @@
# language: fr
Fonctionnalité: Audio-guides Premium et monétisation
En tant que créateur
Je veux pouvoir proposer des audio-guides Premium
Afin de monétiser mon contenu de qualité
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que le créateur "guide@example.com" est connecté et vérifié
# 16.9 - Audio-guides Premium
Scénario: Création d'un audio-guide Premium
Étant donné que le créateur crée un audio-guide "Visite VIP Versailles"
Quand il accède aux paramètres de monétisation (étape 4)
Alors il peut choisir:
| option | description |
| Gratuit | Accessible à tous (avec pubs) |
| Premium | Réservé abonnés Premium |
Scénario: Badge Premium visible sur l'audio-guide
Étant donné un audio-guide configuré en Premium
Quand il est affiché dans les résultats de recherche
Alors un badge "👑 Premium" est visible
Et la cover image a un cadre doré subtil
Scénario: Preview 3 premières séquences pour utilisateurs gratuits
Étant donné un audio-guide Premium "Visite VIP Versailles" avec 15 séquences
Et qu'un utilisateur gratuit ouvre l'audio-guide
Quand il consulte la liste des séquences
Alors les séquences affichent:
| séquence | état |
| 1 | Accessible (preview) |
| 2 | Accessible (preview) |
| 3 | Accessible (preview) |
| 4 | 🔒 Réservé Premium |
| 5-15 | 🔒 Réservé Premium |
Scénario: Écoute des 3 premières séquences sans blocage
Étant donné un utilisateur gratuit
Et un audio-guide Premium avec preview
Quand il écoute les séquences 1, 2 et 3
Alors aucune publicité n'est insérée (preview = teasing)
Et l'écoute est fluide
Scénario: Paywall après la 3ème séquence
Étant donné qu'un utilisateur gratuit termine la séquence 3
Quand la séquence se termine
Alors un overlay paywall s'affiche immédiatement:
"""
👑 Contenu réservé Premium
Continuez cette expérience exclusive
et accédez à 12 séquences supplémentaires
Sans publicité
Accès illimité à tous les contenus Premium
Téléchargement offline
Audio haute qualité
[Passer Premium - 4.99/mois]
[Découvrir d'autres audio-guides gratuits]
"""
Scénario: Bouton "Passer Premium" vers tunnel d'abonnement
Étant donné que l'overlay paywall Premium est affiché
Quand l'utilisateur clique sur "Passer Premium"
Alors il est redirigé vers la page d'abonnement Mangopay
Et l'audio-guide actuel est marqué en "pending" (reprise après souscription)
Scénario: Reprise automatique après souscription Premium
Étant donné qu'un utilisateur s'est abonné Premium depuis un paywall audio-guide
Quand l'abonnement est activé
Alors il est redirigé vers l'audio-guide automatiquement
Et la séquence 4 démarre immédiatement
Et un toast de bienvenue s'affiche: " Bienvenue Premium ! Profitez de votre audio-guide"
Scénario: Utilisateur Premium - Accès complet immédiat
Étant donné qu'un utilisateur Premium ouvre un audio-guide Premium
Quand il consulte la liste des séquences
Alors toutes les 15 séquences sont accessibles
Et aucun paywall ne s'affiche
Et aucune publicité n'est insérée
Scénario: Pas de preview si l'audio-guide a <3 séquences
Étant donné un audio-guide Premium avec seulement 2 séquences
Quand un utilisateur gratuit tente de l'ouvrir
Alors un paywall s'affiche immédiatement (avant lecture)
Et aucune preview n'est disponible
# 16.10 - Revenus créateur
Scénario: Rémunération créateur pour audio-guide Premium
Étant donné un créateur avec un audio-guide Premium
Et que 50 utilisateurs Premium ont écouté l'audio-guide ce mois
Quand la répartition des revenus est calculée
Alors le créateur reçoit 70% des revenus proportionnels
Et la formule est: (Écoutes créateur / Total écoutes Premium) × 70% pool Premium
Scénario: Dashboard revenus par audio-guide
Étant donné qu'un créateur a 3 audio-guides Premium publiés
Quand il consulte son dashboard revenus
Alors il voit pour chaque audio-guide:
| audio_guide | ecoutes_mois | revenus_estime |
| Visite VIP Versailles | 142 | 45.20 |
| Secrets du Louvre | 89 | 28.50 |
| Châteaux de la Loire | 203 | 64.80 |
Scénario: Comparaison gratuit vs Premium
Étant donné qu'un créateur a publié 2 audio-guides:
| titre | type | ecoutes_mois | revenus |
| Tour de Paris | Gratuit | 1200 | 12.50 |
| Visite VIP Versailles| Premium | 142 | 45.20 |
Quand il consulte son dashboard
Alors il peut comparer les performances
Et constater que Premium génère plus de revenus par écoute
Scénario: Seuil minimum de paiement (20€)
Étant donné qu'un créateur a généré 18 de revenus ce mois
Quand le paiement mensuel est traité
Alors le montant est reporté au mois suivant
Et un message s'affiche: "Seuil minimum non atteint (20). Montant reporté."
Scénario: Paiement automatique mensuel
Étant donné qu'un créateur a généré 138.50 de revenus en janvier
Quand le 5 février arrive
Alors le paiement est initié automatiquement via Mangopay
Et le créateur reçoit une notification: "Paiement de 138.50 en cours"
Et les fonds arrivent sous 2-3 jours ouvrés
# 16.11 - Publicités dans audio-guides gratuits
Scénario: Insertion publicité toutes les 5 séquences (gratuit)
Étant donné un audio-guide gratuit avec 12 séquences
Et un utilisateur gratuit
Quand il termine la séquence 5
Alors une publicité démarre automatiquement
Quand il termine la séquence 10
Alors une deuxième publicité démarre
Scénario: Publicité après séquence en mode piéton (avec pause)
Étant donné un audio-guide piéton gratuit
Quand la séquence 5 se termine
Alors la publicité démarre automatiquement (pas d'attente bouton)
Et la pub est skippable après 5 secondes
Quand la publicité se termine
Alors le player se met en pause
Et l'utilisateur doit cliquer sur [|] pour continuer
Scénario: Publicité en mode voiture/vélo/transport (automatique)
Étant donné un audio-guide voiture gratuit
Quand la séquence 5 se termine
Alors la publicité démarre automatiquement
Quand la publicité se termine
Alors la séquence 6 démarre automatiquement (pas de pause)
Parce que l'utilisateur est en conduite (mode hands-free)
Scénario: Publicités géolocalisées dans audio-guides
Étant donné un audio-guide dans la région "Île-de-France"
Quand une publicité doit être insérée
Alors l'API publicitaire filtre par:
| critère | valeur |
| Géolocalisation | Île-de-France |
| Catégorie | Tourisme, Culture |
| Langue | Français |
Scénario: Comptabilisation des impressions pub pour créateur
Étant donné qu'un audio-guide gratuit génère 200 écoutes complètes
Et que chaque écoute complète = 2 publicités (séq. 5 et 10)
Quand les revenus pub sont calculés
Alors 400 impressions sont comptabilisées
Et le créateur reçoit 0.80 (400 × 0.002)
# 16.12 - Stratégies de conversion
Scénario: CTA Premium après audio-guide gratuit complété
Étant donné qu'un utilisateur gratuit complète un audio-guide gratuit
Quand il termine la dernière séquence
Alors un overlay s'affiche:
"""
🎉 Audio-guide complété !
Vous avez aimé cette expérience ?
Découvrez nos audio-guides Premium pour aller plus loin
[Découvrir Premium] [Fermer]
"""
Scénario: Recommandations d'audio-guides Premium après gratuit
Étant donné qu'un utilisateur termine un audio-guide gratuit "Tour de Paris"
Quand l'overlay de fin s'affiche
Alors 3 audio-guides Premium similaires sont suggérés:
| titre | type | créateur |
| Secrets de Montmartre | Premium | @paris_stories |
| Visite VIP Musée d'Orsay | Premium | @art_guide |
| Paris hors des sentiers | Premium | @explore_paris |
Scénario: Badge "Premium recommandé" sur audio-guides populaires
Étant donné un audio-guide Premium avec >500 écoutes et note >4.5/5
Quand il est affiché dans les résultats de recherche
Alors un badge " Premium recommandé" est visible
Et il est mis en avant dans les résultats
Scénario: Conversion tracking pour attribution créateur
Étant donné qu'un utilisateur découvre Premium via un audio-guide créateur
Quand il s'abonne
Alors la conversion est trackée:
| donnée | valeur |
| source_conversion | audio_guide_paywall |
| audio_guide_id | visite_vip_versailles_123 |
| creator_id | guide_versailles_456 |
Et le créateur bénéficie d'un bonus de conversion
# 16.13 - Offres spéciales
Scénario: Essai gratuit 7 jours Premium via audio-guide
Étant donné qu'un utilisateur gratuit atteint le paywall d'un audio-guide Premium
Et qu'il n'a jamais essayé Premium
Quand l'overlay s'affiche
Alors une offre d'essai est proposée:
"""
👑 Essayez Premium gratuitement pendant 7 jours
Accès complet à cet audio-guide
Tous les contenus Premium débloqués
Sans engagement, annulable à tout moment
[Démarrer l'essai gratuit] [Plus tard]
"""
Scénario: Activation immédiate après essai gratuit
Étant donné qu'un utilisateur démarre un essai gratuit 7 jours
Quand l'essai est activé
Alors l'audio-guide Premium démarre immédiatement
Et toutes les séquences sont débloquées
Et aucune publicité n'est insérée
Scénario: Rappel 2 jours avant fin d'essai
Étant donné qu'un utilisateur a démarré un essai gratuit le 15/01
Quand le 20/01 arrive (J-2)
Alors une notification est envoyée:
"""
Votre essai Premium se termine dans 2 jours
Continuez à profiter de tous les audio-guides Premium
pour seulement 4.99/mois
[Rester Premium] [Gérer abonnement]
"""
# Cas d'usage
Scénario: Créateur mix gratuit + Premium
Étant donné qu'un créateur a publié 5 audio-guides:
| titre | type |
| Découverte de Paris | Gratuit |
| Visite VIP Louvre | Premium |
| Balade Montmartre | Gratuit |
| Secrets Versailles | Premium |
| Visite express Orsay | Gratuit |
Quand un utilisateur découvre son profil
Alors les audio-guides gratuits servent de teasing
Et les audio-guides Premium sont mis en avant avec badge
Scénario: Utilisateur hésite à s'abonner
Étant donné qu'un utilisateur atteint le paywall d'un audio-guide Premium
Et qu'il clique sur "Découvrir d'autres audio-guides gratuits"
Quand il revient 2 jours plus tard sur le même audio-guide
Alors le paywall s'affiche à nouveau
Et une réduction temporaire est proposée: "Offre spéciale : -20% premier mois"
# Cas d'erreur
Scénario: Échec du paiement Premium via paywall
Étant donné qu'un utilisateur tente de s'abonner Premium
Quand le paiement Mangopay échoue
Alors un message d'erreur s'affiche:
"""
Paiement refusé
Vérifiez vos informations bancaires ou contactez votre banque.
[Réessayer] [Annuler]
"""
Scénario: Abonnement Premium expiré pendant écoute
Étant donné qu'un utilisateur Premium écoute un audio-guide Premium
Et que son abonnement expire pendant l'écoute (séquence 8/15)
Quand l'expiration est détectée
Alors l'écoute continue jusqu'à la fin de la séquence en cours
Et un overlay s'affiche ensuite:
"""
Votre abonnement Premium a expiré
Renouvelez pour continuer à profiter des contenus exclusifs
[Renouveler - 4.99/mois] [Plus tard]
"""
Scénario: Créateur change audio-guide de gratuit à Premium
Étant donné qu'un audio-guide gratuit a 50 utilisateurs avec progression
Quand le créateur le passe en Premium
Alors les utilisateurs ayant déjà commencé gardent l'accès complet
Et seuls les nouveaux utilisateurs sont soumis au paywall
Et un message de transparence s'affiche:
"""
Cet audio-guide est maintenant Premium
Vous conservez votre accès car vous l'aviez démarré avant le changement.
"""

View File

@@ -0,0 +1,310 @@
# language: fr
Fonctionnalité: Sauvegarde et reprise de progression audio-guide
En tant qu'utilisateur
Je veux que ma progression soit sauvegardée automatiquement
Afin de pouvoir reprendre mon audio-guide là où je me suis arrêté
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté
# 16.5 - Sauvegarde de progression
Scénario: Sauvegarde automatique toutes les 10 secondes
Étant donné qu'un audio-guide "Visite du Louvre" est en cours
Et que la séquence 3 est à la position 1:24
Quand 10 secondes s'écoulent
Alors la progression est sauvegardée automatiquement:
| donnée | valeur |
| audio_guide_id | louvre_123 |
| sequence_actuelle | 3 |
| position_audio | 1:24 |
| timestamp | 2026-01-22 14:35:42 |
| sequences_ecoutees | [1, 2] |
Scénario: Sauvegarde locale (SQLite) pour rapidité
Étant donné qu'une sauvegarde est déclenchée
Quand la progression est enregistrée
Alors les données sont écrites en SQLite local
Et l'écriture prend moins de 50ms
Et l'application reste fluide
Scénario: Synchronisation cloud en arrière-plan
Étant donné qu'une sauvegarde locale est effectuée
Quand 30 secondes s'écoulent
Alors la progression est synchronisée vers PostgreSQL cloud
Et la synchronisation s'effectue en arrière-plan
Et elle n'impacte pas les performances
Scénario: Sauvegarde immédiate lors de la fermeture
Étant donné qu'un audio-guide est en cours à la séquence 4 position 2:15
Quand l'utilisateur ferme l'application
Alors la progression est sauvegardée immédiatement (local + cloud)
Et les données sont écrites avant la fermeture complète
Scénario: Sauvegarde des séquences complétées
Étant donné qu'un audio-guide de 12 séquences est en cours
Et que les séquences 1, 2, 4, 5 ont été écoutées à >80%
Quand la progression est sauvegardée
Alors les séquences complétées sont enregistrées:
"""json
{
"audio_guide_id": "louvre_123",
"completed_sequences": [1, 2, 4, 5],
"current_sequence": 6,
"current_position": "0:42",
"last_played_at": "2026-01-22T14:35:42Z"
}
"""
Scénario: Historique des écoutes pour statistiques
Étant donné qu'un utilisateur a écouté 3 séquences d'un audio-guide
Quand les données sont sauvegardées
Alors l'historique d'écoute inclut:
| sequence_id | started_at | completed_at | completion_rate |
| 1 | 2026-01-22 14:10:00 | 2026-01-22 14:12:15 | 100% |
| 2 | 2026-01-22 14:12:20 | 2026-01-22 14:14:08 | 100% |
| 3 | 2026-01-22 14:14:15 | 2026-01-22 14:17:45 | 92% |
# 16.6 - Reprise de progression
Scénario: Popup de reprise au redémarrage
Étant donné que l'utilisateur a quitté l'app à la séquence 6 position 2:34
Quand il rouvre l'audio-guide "Visite du Louvre"
Alors une popup s'affiche:
"""
Continuer où vous vous étiez arrêté ?
📍 Séquence 6/12 : "Vénus de Milo"
Position : 2:34 / 4:10
[ Reprendre] [🔄 Recommencer]
"""
Scénario: Action "Reprendre" - Position exacte restaurée
Étant donné qu'une popup de reprise est affichée
Quand l'utilisateur clique sur " Reprendre"
Alors la séquence 6 "Vénus de Milo" se charge
Et la position exacte 2:34 est restaurée
Et la lecture démarre automatiquement après 1 seconde
Scénario: Action "Recommencer" - Réinitialisation complète
Étant donné qu'une popup de reprise est affichée
Quand l'utilisateur clique sur "🔄 Recommencer"
Alors l'audio-guide redémarre depuis la séquence 1 position 0:00
Et toutes les séquences sont marquées "À écouter"
Et l'historique d'écoute est réinitialisé pour cette session
Scénario: Reprise après 7 jours d'inactivité
Étant donné qu'un utilisateur a arrêté un audio-guide le 15/01/2026
Et qu'il le rouvre le 22/01/2026 (7 jours plus tard)
Quand l'audio-guide se charge
Alors la popup de reprise s'affiche normalement
Et toutes les données de progression sont conservées
Scénario: Reprise sur un autre appareil (synchronisation cloud)
Étant donné qu'un utilisateur écoute un audio-guide sur iPhone
Et qu'il quitte à la séquence 4 position 1:20
Quand il ouvre le même audio-guide sur iPad
Alors la popup de reprise s'affiche avec la progression iPhone
Et il peut reprendre exactement où il s'était arrêté
Scénario: Conflit de synchronisation (dernier appareil gagne)
Étant donné qu'un utilisateur écoute sur iPhone à la séquence 3
Et simultanément sur iPad à la séquence 7
Quand les deux appareils synchronisent
Alors la progression la plus récente (timestamp) est conservée
Et l'appareil avec ancienne progression affiche une notification:
"""
Progression mise à jour
Une écoute plus récente a été détectée.
Séquence 7 restaurée.
"""
Scénario: Mode hors-ligne - Sauvegarde locale uniquement
Étant donné qu'un utilisateur écoute un audio-guide hors connexion
Et qu'il atteint la séquence 5
Quand la progression est sauvegardée
Alors les données sont écrites localement (SQLite)
Et une icône " Non synchronisé" s'affiche discrètement
Scénario: Synchronisation automatique à la reconnexion
Étant donné que l'utilisateur a écouté hors ligne jusqu'à la séquence 8
Et que 5 progressions locales ne sont pas synchronisées
Quand la connexion réseau est rétablie
Alors les 5 progressions sont synchronisées automatiquement
Et un toast s'affiche brièvement: " Progression synchronisée"
Scénario: Suppression de la progression (recommencer proprement)
Étant donné qu'un utilisateur est à la séquence 10/12
Quand il ouvre les paramètres de l'audio-guide
Et qu'il clique sur "🔄 Réinitialiser progression"
Alors une confirmation s'affiche:
"""
Réinitialiser cet audio-guide ?
Toutes les séquences seront marquées comme non écoutées.
[Annuler] [Réinitialiser]
"""
Et si confirmé, la progression est effacée
# 16.7 - Statistiques d'écoute
Scénario: Taux de complétion global de l'audio-guide
Étant donné un audio-guide de 12 séquences
Et que l'utilisateur a écouté complètement 8 séquences
Et partiellement 1 séquence (45%)
Quand les statistiques sont calculées
Alors le taux de complétion affiché est "67%" (8/12)
Scénario: Badge "Audio-guide complété" à 100%
Étant donné un audio-guide de 12 séquences
Quand l'utilisateur écoute la 12ème séquence à 100%
Alors un badge " Audio-guide complété" s'affiche
Et une notification de félicitations est envoyée
Et le statut "Complété le 22/01/2026" est visible dans l'historique
Scénario: Temps total passé sur l'audio-guide
Étant donné qu'un utilisateur a écouté un audio-guide sur 2 sessions:
| session | durée |
| 1 | 25 min |
| 2 | 18 min |
Quand les statistiques sont calculées
Alors le temps total est "43 minutes"
Et il est affiché dans l'historique personnel
Scénario: Liste des audio-guides "En cours" dans le profil
Étant donné qu'un utilisateur a 3 audio-guides en cours:
| audio_guide | progression |
| Visite du Louvre | 6/12 |
| Safari du Paugre | 3/8 |
| Circuit Loire à Vélo | 12/15 |
Quand il consulte son profil "Audio-guides"
Alors la section "📍 En cours" affiche les 3 audio-guides
Et chaque élément montre la progression sous forme de barre
Scénario: Liste des audio-guides "Complétés" dans le profil
Étant donné qu'un utilisateur a complété 2 audio-guides:
| audio_guide | date_completion |
| Tour de Paris | 2026-01-15 |
| Découverte de Lyon | 2026-01-20 |
Quand il consulte son profil "Audio-guides"
Alors la section " Complétés" affiche les 2 audio-guides
Et la date de complétion est visible
Scénario: Badge "Complétiste" pour 10 audio-guides complétés
Étant donné qu'un utilisateur complète son 10ème audio-guide
Quand la complétion est enregistrée
Alors un badge "🏆 Complétiste" est débloqué
Et il apparaît sur son profil
Et une notification est envoyée:
"""
🎉 Badge débloqué : Complétiste
Vous avez complété 10 audio-guides !
"""
Plan du Scénario: Niveaux de badges selon nombre d'audio-guides complétés
Étant donné qu'un utilisateur complète <nombre> audio-guides
Quand le badge est attribué
Alors il reçoit le badge "<badge>"
Exemples:
| nombre | badge |
| 1 | 🎧 Premier audio-guide |
| 5 | 🗺 Explorateur |
| 10 | 🏆 Complétiste |
| 25 | 🌟 Expert |
| 50 | 💎 Maître audio-guideur |
# 16.8 - Métriques créateur
Scénario: Dashboard créateur - Statistiques par audio-guide
Étant donné qu'un créateur a publié l'audio-guide "Visite du Louvre"
Quand il consulte son dashboard
Alors les métriques suivantes sont affichées:
| métrique | valeur |
| Écoutes totales | 1542 |
| Écoutes complètes (>80%) | 892 |
| Taux de complétion moyen | 58% |
| Temps d'écoute total | 423h |
| Séquence la plus écoutée | Séq. 3 |
| Séquence la moins écoutée | Séq. 11 |
Scénario: Graphique de complétion par séquence
Étant donné un audio-guide de 12 séquences
Quand le créateur consulte les statistiques détaillées
Alors un graphique en barres affiche:
| séquence | taux_completion |
| 1 | 100% |
| 2 | 95% |
| 3 | 89% |
| ... | ... |
| 12 | 58% |
Scénario: Détection des points d'abandon
Étant donné qu'un audio-guide a un taux de complétion de 58%
Et que 35% des utilisateurs abandonnent à la séquence 7
Quand le créateur consulte les insights
Alors un avertissement s'affiche:
"""
Point d'abandon détecté
35% des utilisateurs abandonnent à la séquence 7 "Aile Richelieu"
Durée : 8 min
Suggestion : Réduire la durée ou rendre plus captivant
"""
Scénario: Heatmap géographique des écoutes
Étant donné un audio-guide géolocalisé
Quand le créateur consulte la heatmap
Alors une carte affiche:
| élément | description |
| Densité d'écoutes | Zones rouge/orange/jaune selon écoutes |
| Points GPS | Marqueurs sur chaque point |
| Statistiques par point | Nombre d'écoutes par zone |
Scénario: Temps moyen par séquence
Étant donné qu'un créateur analyse son audio-guide
Quand il consulte les statistiques temporelles
Alors il voit pour chaque séquence:
| séquence | durée_audio | temps_ecoute_moyen | ecart |
| 1 | 2:15 | 2:10 | -5s |
| 2 | 1:48 | 1:30 | -18s |
| 3 | 3:42 | 3:40 | -2s |
Scénario: Notification créateur pour milestone
Étant donné qu'un audio-guide atteint 1000 écoutes
Quand le seuil est franchi
Alors une notification est envoyée au créateur:
"""
🎉 Félicitations !
Votre audio-guide "Visite du Louvre" a atteint 1000 écoutes !
Taux de complétion : 58%
"""
# Cas d'erreur et limites
Scénario: Corruption de données de sauvegarde
Étant donné qu'une sauvegarde locale (SQLite) est corrompue
Quand l'application tente de charger la progression
Alors une récupération depuis le cloud est tentée
Et si réussie, les données cloud sont restaurées
Et la base locale est reconstruite
Scénario: Échec de synchronisation cloud
Étant donné que l'API cloud est indisponible
Quand une tentative de synchronisation est effectuée
Alors l'application continue avec sauvegarde locale uniquement
Et un retry automatique est programmé dans 5 minutes
Et l'icône " Non synchronisé" reste affichée
Scénario: Suppression accidentelle de progression (récupération)
Étant donné qu'un utilisateur réinitialise un audio-guide par erreur
Quand il contacte le support dans les 7 jours
Alors l'équipe peut restaurer la progression depuis les backups
Et les données sont récupérables (backup quotidien conservé 30 jours)
Scénario: Nettoyage automatique des vieilles progressions
Étant donné qu'une progression n'a pas été mise à jour depuis 6 mois
Quand le nettoyage automatique s'exécute
Alors la progression est archivée (mais pas supprimée)
Et l'utilisateur peut la restaurer via l'historique

View File

@@ -0,0 +1,142 @@
# language: fr
Fonctionnalité: Pas de dégradation temporelle des jauges
En tant que système de recommandation
Je veux que les jauges n'évoluent que par les actions utilisateur
Afin d'avoir un comportement prévisible et fiable
Contexte:
Étant donné que l'API RoadWave est disponible
Et qu'un utilisateur est connecté
Scénario: Aucune dégradation automatique avec le temps
Étant donné que ma jauge "Économie" est à 80%
Et que je n'écoute aucun contenu pendant 30 jours
Quand je me reconnecte après 30 jours
Alors ma jauge "Économie" est toujours à 80%
Et aucune dégradation temporelle n'a été appliquée
Scénario: Jauges conservées après 6 mois d'inactivité
Étant donné que mes jauges sont:
| catégorie | niveau |
| Automobile | 75% |
| Voyage | 60% |
| Musique | 45% |
Et que je pars en vacances pendant 6 mois sans utiliser l'app
Quand je me reconnecte après 6 mois
Alors mes jauges sont exactement les mêmes:
| catégorie | niveau |
| Automobile | 75% |
| Voyage | 60% |
| Musique | 45% |
Scénario: Évolution naturelle par les actions
Étant donné que j'aimais "Économie" il y a 1 an (jauge 80%)
Et que depuis, je skip tous les contenus "Économie"
Et que j'ai skippé 50 contenus "Économie" en 1 an
Alors ma jauge "Économie" descend naturellement via les skips
Et atteint environ 55% (80% - 50 × 0.5% = 55%)
Et la dégradation vient des actions, pas du temps
Scénario: Pas de cron job de dégradation
Étant donné que le système vérifie les jauges quotidiennement
Quand un utilisateur n'a pas d'activité depuis 90 jours
Alors aucun job de dégradation n'est exécuté
Et les jauges restent inchangées
Et aucune ressource CPU n'est consommée pour la dégradation
Scénario: Comportement prévisible après absence
Étant donné que ma jauge "Sport" était à 70%
Et que je n'utilise pas l'app pendant 1 an
Quand je reviens et demande des recommandations
Alors mes recommandations reflètent toujours mes goûts d'avant
Et je reçois du contenu "Sport" prioritaire
Et le comportement est cohérent et prévisible
Scénario: Réinitialiser manuellement mes centres d'intérêt
Étant donné que je veux repartir de zéro
Quand je vais dans les paramètres
Et que je clique sur "Réinitialiser mes centres d'intérêt"
Et que je confirme l'action
Alors toutes mes jauges reviennent à 50%
Et je vois le message "Vos centres d'intérêt ont été réinitialisés"
Scénario: Confirmation avant réinitialisation
Étant donné que je suis dans les paramètres
Quand je clique sur "Réinitialiser mes centres d'intérêt"
Alors je vois un message de confirmation:
| titre | Êtes-vous sûr ? |
| message | Cette action remettra toutes vos jauges à 50% |
| actions | Confirmer / Annuler |
Scénario: Annuler la réinitialisation
Étant donné que j'ai cliqué sur "Réinitialiser mes centres d'intérêt"
Et que la confirmation est affichée
Quand je clique sur "Annuler"
Alors mes jauges ne sont pas modifiées
Et je reviens aux paramètres
Scénario: Raison de réinitialisation - changement de vie
Étant donné que j'utilisais RoadWave pour mes trajets professionnels
Et que mes jauges reflétaient "Économie" (85%) et "Technologie" (75%)
Et que je change de vie et deviens musicien
Quand je réinitialise mes centres d'intérêt
Alors je peux repartir avec toutes les jauges à 50%
Et découvrir du contenu "Musique" et "Culture" sans biais
Scénario: Pas de suggestion automatique de réinitialisation
Étant donné que je n'ai pas utilisé l'app depuis 1 an
Quand je me reconnecte
Alors aucune suggestion de réinitialisation n'est affichée
Et mes jauges sont conservées telles quelles
Et je garde le contrôle total
Scénario: Historique conservé après réinitialisation
Étant donné que j'ai écouté 500 contenus
Quand je réinitialise mes centres d'intérêt
Alors mes jauges reviennent à 50%
Mais mon historique d'écoute est conservé
Et je peux toujours consulter mes anciens contenus écoutés
Scénario: Évolution future basée sur nouvelles actions
Étant donné que j'ai réinitialisé mes jauges à 50%
Quand j'écoute 5 contenus "Voyage" à >80%
Alors ma jauge "Voyage" monte à 60% (50% + 5 × 2%)
Et l'algorithme recommence à apprendre mes nouvelles préférences
Scénario: Respect de l'historique utilisateur
Étant donné qu'un utilisateur aime "Cryptomonnaie" depuis 2 ans
Et que sa jauge est à 90%
Quand 2 ans s'écoulent sans dégradation temporelle
Alors sa jauge reste à 90%
Car l'historique de ses goûts est respecté
Et le système ne fait pas d'"oubli" artificiel
Scénario: Coût infrastructure zéro
Étant donné qu'aucune dégradation temporelle n'existe
Quand le système calcule les jauges
Alors aucun calcul de date n'est nécessaire
Et aucun batch nocturne ne tourne
Et aucun bug de fuseau horaire ne peut survenir
Et le coût CPU est minimal
Scénario: UX prévisible - jauge = actions
Étant donné qu'un utilisateur consulte sa jauge "Sport" à 65%
Quand il se demande pourquoi elle est à 65%
Alors il peut retracer ses actions:
| action | impact |
| 10 likes automatiques | +10% |
| 3 abonnements Sport | +15% |
| 5 skips de contenu non-Sport| 0% |
Et il comprend que c'est le reflet exact de ses actions
Et il n'y a pas de mystère ou automatisme caché
Scénario: Statistiques affichées sans date
Étant donné que je consulte mes centres d'intérêt
Quand je vois mes jauges
Alors je vois:
| information | affiché |
| Niveau actuel | 75% |
| Évolution depuis début | +25% |
| Dernière mise à jour | |
Et aucune date n'est affichée car non pertinente
Et seules les actions comptent

View File

@@ -0,0 +1,193 @@
# language: fr
Fonctionnalité: Évolution des jauges d'intérêt
En tant que système de recommandation
Je veux faire évoluer les jauges d'intérêt selon les actions utilisateur
Afin d'affiner les recommandations personnalisées
Contexte:
Étant donné que l'API RoadWave est disponible
Et qu'un utilisateur est connecté
Scénario: Like automatique renforcé après écoute ≥80%
Étant donné qu'un contenu de 5 minutes est tagué "Automobile"
Et que ma jauge "Automobile" est à 45%
Quand j'écoute le contenu pendant 4 minutes 30 secondes (90%)
Alors je reçois un like automatique renforcé
Et ma jauge "Automobile" augmente de 2%
Et ma jauge "Automobile" est maintenant à 47%
Scénario: Like automatique renforcé exactement à 80%
Étant donné qu'un contenu de 10 minutes est tagué "Voyage"
Et que ma jauge "Voyage" est à 60%
Quand j'écoute le contenu pendant exactement 8 minutes (80%)
Alors je reçois un like automatique renforcé
Et ma jauge "Voyage" augmente de 2%
Et ma jauge "Voyage" est maintenant à 62%
Scénario: Like automatique standard après écoute 30-79%
Étant donné qu'un contenu de 5 minutes est tagué "Automobile"
Et que ma jauge "Automobile" est à 45%
Quand j'écoute le contenu pendant 2 minutes 30 secondes (50%)
Alors je reçois un like automatique standard
Et ma jauge "Automobile" augmente de 1%
Et ma jauge "Automobile" est maintenant à 46%
Scénario: Like automatique standard à 30% exactement
Étant donné qu'un contenu de 10 minutes est tagué "Musique"
Et que ma jauge "Musique" est à 40%
Quand j'écoute le contenu pendant exactement 3 minutes (30%)
Alors je reçois un like automatique standard
Et ma jauge "Musique" augmente de 1%
Scénario: Like automatique standard à 79%
Étant donné qu'un contenu de 10 minutes est tagué "Sport"
Et que ma jauge "Sport" est à 55%
Quand j'écoute le contenu pendant 7 minutes 54 secondes (79%)
Alors je reçois un like automatique standard
Et ma jauge "Sport" augmente de 1%
Et ma jauge "Sport" est maintenant à 56%
Scénario: Like explicite (manuel) +2%
Étant donné qu'un contenu est tagué "Économie"
Et que ma jauge "Économie" est à 70%
Quand j'écoute le contenu partiellement
Et que je clique manuellement sur le bouton "Like"
Alors ma jauge "Économie" augmente de 2%
Et ma jauge "Économie" est maintenant à 72%
Scénario: Like manuel cumulable avec like automatique
Étant donné qu'un contenu de 5 minutes est tagué "Automobile"
Et que ma jauge "Automobile" est à 45%
Quand j'écoute le contenu pendant 2 minutes 30 secondes (50%)
Alors je reçois un like automatique standard (+1%)
Quand je clique ensuite sur le bouton "Like"
Alors ma jauge augmente encore de 2% (like manuel)
Et ma jauge "Automobile" a augmenté de 3% au total
Et ma jauge "Automobile" est maintenant à 48%
Scénario: Abonnement créateur impacte tous ses tags
Étant donné qu'un créateur publie des contenus tagués "Automobile" et "Technologie"
Et que mes jauges sont:
| catégorie | niveau |
| Automobile | 50% |
| Technologie | 45% |
Quand je m'abonne à ce créateur
Alors ma jauge "Automobile" augmente de 5%
Et ma jauge "Technologie" augmente de 5%
Et mes nouvelles jauges sont:
| catégorie | niveau |
| Automobile | 55% |
| Technologie | 50% |
Scénario: Skip rapide (<10s) diminue la jauge
Étant donné qu'un contenu est tagué "Économie"
Et que ma jauge "Économie" est à 45%
Quand je skip le contenu après 5 secondes
Alors ma jauge "Économie" diminue de 0.5%
Et ma jauge "Économie" est maintenant à 44.5%
Scénario: Skip à exactement 10s ne diminue pas la jauge
Étant donné qu'un contenu est tagué "Politique"
Et que ma jauge "Politique" est à 50%
Quand je skip le contenu après exactement 10 secondes
Alors ma jauge "Politique" ne change pas
Et reste à 50%
Scénario: Skip tardif (≥30%) est neutre
Étant donné qu'un contenu de 10 minutes est tagué "Musique"
Et que ma jauge "Musique" est à 60%
Quand j'écoute pendant 3 minutes (30%)
Et que je skip ensuite
Alors ma jauge "Musique" ne diminue pas (signal neutre)
Et ma jauge reste à 60% (plus le +1% de like auto si applicable)
Scénario: Contenu avec plusieurs tags impacte toutes les jauges
Étant donné qu'un contenu est tagué "Automobile" et "Voyage"
Et que mes jauges sont:
| catégorie | niveau |
| Automobile | 45% |
| Voyage | 60% |
Quand j'écoute le contenu à 90%
Alors les deux jauges augmentent de 2%
Et mes nouvelles jauges sont:
| catégorie | niveau |
| Automobile | 47% |
| Voyage | 62% |
Scénario: Contenu avec 3 tags impacte les 3 jauges
Étant donné qu'un contenu est tagué "Sport", "Santé" et "Technologie"
Et que mes jauges sont à 50% pour chaque catégorie
Quand je skip rapidement après 5 secondes
Alors les 3 jauges diminuent de 0.5%
Et toutes passent à 49.5%
Scénario: Jauges bornées - ne peut pas dépasser 100%
Étant donné que ma jauge "Cryptomonnaie" est à 99%
Et qu'un contenu tagué "Cryptomonnaie" est disponible
Quand j'écoute le contenu à 95% (like auto renforcé +2%)
Alors ma jauge "Cryptomonnaie" passe à 100% (maximum)
Et ne dépasse pas 100%
Scénario: Jauges bornées - ne peut pas descendre sous 0%
Étant donné que ma jauge "Politique" est à 0.3%
Et qu'un contenu tagué "Politique" est disponible
Quand je skip rapidement après 3 secondes (-0.5%)
Alors ma jauge "Politique" passe à 0% (minimum)
Et ne devient pas négative
Scénario: Calcul immédiat à chaque action
Étant donné que ma jauge "Voyage" est à 50%
Quand j'écoute un contenu "Voyage" à 85%
Alors la jauge est mise à jour immédiatement (pas de batch)
Et passe à 52%
Quand je demande mes recommandations dans la seconde suivante
Alors l'algorithme utilise déjà la valeur 52%
Scénario: Like manuel après écoute <30% (pas de like auto)
Étant donné qu'un contenu de 10 minutes est tagué "Culture"
Et que ma jauge "Culture" est à 60%
Quand j'écoute pendant 2 minutes (20%)
Alors je ne reçois pas de like automatique
Quand je clique sur le bouton "Like"
Alors ma jauge "Culture" augmente de 2% uniquement
Et ma jauge "Culture" est maintenant à 62%
Scénario: Unlike retire le like manuel
Étant donné que j'ai liké manuellement un contenu "Sport"
Et que ma jauge "Sport" est passée de 55% à 57% (+2%)
Quand je clique sur "Unlike"
Alors ma jauge "Sport" diminue de 2%
Et ma jauge "Sport" revient à 55%
Scénario: Unlike ne peut pas retirer un like automatique
Étant donné que j'ai écouté un contenu "Musique" à 90%
Et que j'ai reçu un like automatique renforcé (+2%)
Et que ma jauge "Musique" est à 52%
Quand j'essaie de faire "Unlike"
Alors l'action n'est pas disponible
Et ma jauge reste à 52%
Car les likes automatiques ne peuvent pas être retirés
Scénario: Tags définis par créateur à la publication
Étant donné que je suis un créateur
Quand je publie un contenu
Alors je dois sélectionner 1 à 3 tags
Et ces tags sont fixés après publication
Et impacteront les jauges de tous les auditeurs
Plan du Scénario: Calculs avec différentes durées d'écoute
Étant donné qu'un contenu de 10 minutes est tagué "Voyage"
Et que ma jauge "Voyage" est à 50%
Quand j'écoute pendant <duree> (<pourcentage>)
Alors ma jauge évolue de <impact>
Et ma nouvelle jauge est à <nouveau_niveau>
Exemples:
| duree | pourcentage | impact | nouveau_niveau |
| 1 min | 10% | 0% | 50% |
| 3 min | 30% | +1% | 51% |
| 5 min | 50% | +1% | 51% |
| 7.9 min | 79% | +1% | 51% |
| 8 min | 80% | +2% | 52% |
| 9.5 min | 95% | +2% | 52% |
| 5 sec | <1% | -0.5% | 49.5% |

View File

@@ -0,0 +1,147 @@
# language: fr
Fonctionnalité: Jauge initiale et cold start
En tant que nouvel utilisateur
Je veux que mes jauges d'intérêt démarrent de manière neutre
Afin de découvrir du contenu sans biais initial
Contexte:
Étant donné que l'API RoadWave est disponible
Scénario: Inscription - toutes les jauges à 50%
Quand je m'inscris sur RoadWave
Alors toutes mes jauges d'intérêt sont initialisées à 50%
Et je ne dois pas remplir de questionnaire
Et l'inscription est ultra-rapide
Scénario: Liste des catégories disponibles
Étant donné que je suis un nouvel utilisateur
Quand je consulte mes centres d'intérêt
Alors je vois les catégories suivantes à 50%:
| catégorie |
| Automobile |
| Voyage |
| Famille |
| Amour |
| Musique |
| Économie |
| Cryptomonnaie |
| Politique |
| Culture générale |
| Sport |
| Technologie |
| Santé |
Scénario: Cold start - premier contenu écouté
Étant donné que je viens de m'inscrire
Et que toutes mes jauges sont à 50%
Quand j'écoute mon premier podcast "Automobile" à 90%
Alors ma jauge "Automobile" monte à 52% (+2%)
Et toutes les autres jauges restent à 50%
Scénario: Cold start - premier skip
Étant donné que je viens de m'inscrire
Et que toutes mes jauges sont à 50%
Quand je skip rapidement un contenu "Économie"
Alors ma jauge "Économie" descend à 49.5% (-0.5%)
Et toutes les autres jauges restent à 50%
Scénario: Après 10 écoutes, profil commence à se dessiner
Étant donné que je suis un nouvel utilisateur
Et que j'ai écouté:
| contenu | tags | completion |
| Contenu 1 | Automobile | 90% |
| Contenu 2 | Automobile, Sport | 85% |
| Contenu 3 | Voyage | 75% |
| Contenu 4 | Économie | skip 5s |
| Contenu 5 | Automobile | 95% |
| Contenu 6 | Sport | 80% |
| Contenu 7 | Politique | skip 8s |
| Contenu 8 | Voyage | 88% |
| Contenu 9 | Automobile | 92% |
| Contenu 10 | Technologie | 40% |
Alors mes jauges reflètent mes préférences:
| catégorie | tendance |
| Automobile | Forte hausse (>55%) |
| Voyage | Hausse modérée (~53%) |
| Sport | Hausse modérée (~53%) |
| Économie | Baisse légère (~49.5%) |
| Politique | Baisse légère (~49.5%) |
| Technologie | Neutre (~51%) |
Scénario: Pas de questionnaire onboarding par défaut
Quand je termine l'inscription
Alors aucun questionnaire de centres d'intérêt n'est affiché
Et je peux commencer à écouter immédiatement
Et l'algorithme apprend naturellement
Scénario: Algorithme avec jauges à 50% - chances égales
Étant donné que toutes mes jauges sont à 50%
Quand l'algorithme calcule les recommandations
Alors tous les types de contenus ont une chance égale
Et aucun biais initial n'est appliqué
Et la géolocalisation prime sur les intérêts
Scénario: Questionnaire optionnel après 3 écoutes (post-MVP)
Étant donné que j'ai écouté 3 contenus
Quand je termine ma 3ème écoute
Alors je vois une notification in-app optionnelle:
| titre | Améliorez vos recommandations |
| message | Sélectionnez vos centres d'intérêt |
| actions | Configurer maintenant / Plus tard |
Scénario: Remplir le questionnaire optionnel (post-MVP)
Étant donné que le questionnaire optionnel est affiché
Quand je sélectionne les centres d'intérêt suivants:
| catégorie |
| Automobile |
| Voyage |
| Sport |
Alors les jauges sélectionnées passent à 70%
Et les jauges non sélectionnées passent à 30%
Et je vois le message "Vos préférences ont été enregistrées"
Scénario: Skipper le questionnaire optionnel (post-MVP)
Étant donné que le questionnaire optionnel est affiché
Quand je clique sur "Plus tard"
Alors toutes mes jauges conservent 50%
Et l'algorithme continue d'apprendre naturellement
Et je ne suis plus sollicité
Scénario: Comportement déterministe et testable
Étant donné deux nouveaux utilisateurs A et B
Quand les deux s'inscrivent au même moment
Alors leurs jauges sont identiques (toutes à 50%)
Et leurs recommandations initiales sont identiques (basées sur géo uniquement)
Scénario: Équité entre créateurs au cold start
Étant donné qu'un nouvel utilisateur s'inscrit
Et qu'il existe 1000 contenus de catégories variées dans sa zone
Quand l'algorithme génère les premières recommandations
Alors tous les contenus ont une pondération intérêts identique (50%)
Et seuls la géolocalisation et l'engagement différencient les contenus
Et aucun créateur n'a d'avantage initial
Scénario: Catégories extensibles
Étant donné que RoadWave ajoute une nouvelle catégorie "Gastronomie"
Quand je consulte mes centres d'intérêt
Alors je vois la nouvelle catégorie "Gastronomie" à 50%
Et je peux commencer à l'explorer normalement
Scénario: Voir l'évolution de mes jauges
Étant donné que je suis un utilisateur avec historique
Quand je consulte mes centres d'intérêt dans les paramètres
Alors je vois mes jauges actuelles:
| catégorie | niveau | evolution |
| Automobile | 67% | +17% |
| Voyage | 82% | +32% |
| Économie | 34% | -16% |
| Sport | 50% | 0% |
Et je comprends mes préférences actuelles
Scénario: Friction zéro à l'inscription
Étant donné que je veux m'inscrire rapidement
Quand je remplis les 4 champs obligatoires
Et que je clique sur "S'inscrire"
Alors mon compte est créé immédiatement
Et je peux commencer à écouter dans les 30 secondes
Et aucune configuration supplémentaire n'est requise

View File

@@ -0,0 +1,425 @@
# language: fr
Fonctionnalité: Synchronisation actions offline
En tant qu'utilisateur
Je veux que mes actions offline soient synchronisées quand je me reconnecte
Afin de ne perdre aucune interaction même sans connexion
Contexte:
Étant donné que j'utilise l'application RoadWave
# ===== ACTIONS STOCKÉES LOCALEMENT =====
Scénario: Like d'un contenu en mode offline
Étant donné que je n'ai aucune connexion Internet
Quand je like un contenu téléchargé
Alors l'action est enregistrée localement dans SQLite:
```sql
INSERT INTO pending_actions (type, content_id, created_at)
VALUES ('like', 'abc123', '2025-06-15 14:30:00');
```
Et l'UI affiche immédiatement le like (optimistic update)
Scénario: Unlike d'un contenu en mode offline
Étant donné que je n'ai aucune connexion Internet
Et que j'avais liké un contenu
Quand je retire mon like
Alors l'action est enregistrée localement:
```sql
INSERT INTO pending_actions (type, content_id, created_at)
VALUES ('unlike', 'abc123', '2025-06-15 14:35:00');
```
Et l'UI retire immédiatement le like
Scénario: Abonnement à un créateur en mode offline
Étant donné que je n'ai aucune connexion Internet
Quand je m'abonne à un créateur
Alors l'action est enregistrée localement:
```sql
INSERT INTO pending_actions (type, creator_id, created_at)
VALUES ('subscribe', 'creator456', '2025-06-15 14:40:00');
```
Et l'UI affiche immédiatement "Abonné "
Scénario: Désabonnement d'un créateur en mode offline
Étant donné que je n'ai aucune connexion Internet
Et que j'étais abonné à un créateur
Quand je me désabonne
Alors l'action est enregistrée localement:
```sql
INSERT INTO pending_actions (type, creator_id, created_at)
VALUES ('unsubscribe', 'creator456', '2025-06-15 14:45:00');
```
Et l'UI affiche "S'abonner"
Scénario: Signalement d'un contenu en mode offline
Étant donné que je n'ai aucune connexion Internet
Quand je signale un contenu pour "Contenu inapproprié"
Alors l'action est enregistrée localement:
```sql
INSERT INTO pending_actions (type, content_id, reason, created_at)
VALUES ('report', 'abc123', 'Contenu inapproprié', '2025-06-15 14:50:00');
```
Et je vois "Signalement enregistré. Sera envoyé à la reconnexion."
Scénario: Progression audio-guide en mode offline
Étant donné que je n'ai aucune connexion Internet
Et que j'écoute un audio-guide multi-séquences
Quand je termine la séquence 3/10
Alors la progression est enregistrée localement:
```sql
INSERT INTO pending_actions (type, guide_id, sequence_id, created_at)
VALUES ('guide_progress', 'guide789', 'seq003', '2025-06-15 15:00:00');
```
Et ma progression est sauvegardée
Scénario: Multiple actions offline stockées en queue
Étant donné que je n'ai aucune connexion Internet pendant 2 jours
Quand j'effectue plusieurs actions:
| action | cible |
| like | contenu A |
| like | contenu B |
| subscribe | créateur X |
| unlike | contenu C |
| report | contenu D |
Alors les 5 actions sont stockées dans pending_actions
Et elles seront synchronisées dans l'ordre à la reconnexion
# ===== SYNCHRONISATION AUTOMATIQUE =====
Scénario: Détection reconnexion Internet
Étant donné que j'étais en mode offline
Quand l'app détecte une reconnexion Internet
Alors le processus de synchronisation démarre automatiquement
Et je vois une notification "Synchronisation en cours..."
Scénario: Récupération queue locale pendant sync
Étant donné que la synchronisation démarre
Quand l'app récupère les actions en attente
Alors une requête SQL est exécutée:
```sql
SELECT * FROM pending_actions ORDER BY created_at ASC;
```
Et toutes les actions sont récupérées dans l'ordre chronologique
Scénario: Envoi batch API des actions
Étant donné que 15 actions sont en attente
Quand le batch est envoyé au backend
Alors une requête POST /sync/actions est faite:
```json
{
"actions": [
{"type": "like", "content_id": "abc123", "timestamp": "2025-06-15T14:30:00Z"},
{"type": "subscribe", "creator_id": "creator456", "timestamp": "2025-06-15T14:40:00Z"},
{"type": "unlike", "content_id": "def789", "timestamp": "2025-06-15T14:50:00Z"},
...
]
}
```
Et toutes les actions sont groupées en une seule requête
Scénario: Backend traite chaque action
Étant donné que le backend reçoit le batch d'actions
Quand il traite chaque action
Alors pour chaque action:
| étape | détail |
| Validation | Vérifier user_id, content_id valides |
| Vérification existence | Contenu/créateur existe toujours ? |
| Application action | INSERT/UPDATE/DELETE en base |
| Mise à jour compteurs | Likes, abonnés, etc. |
| Impact sur algorithme | Mise à jour jauges si nécessaire |
Scénario: Confirmation réception et suppression queue locale
Étant donné que le backend a traité toutes les actions avec succès
Quand la confirmation est reçue par l'app
Alors les actions sont supprimées de la queue locale:
```sql
DELETE FROM pending_actions WHERE id IN (1, 2, 3, ..., 15);
```
Et la table pending_actions est vidée
Scénario: Toast confirmation synchronisation
Étant donné que 15 actions ont été synchronisées
Quand la synchronisation se termine
Alors je vois un toast:
"""
Synchronisation réussie
3 likes, 1 abonnement et 1 signalement synchronisés.
"""
Scénario: Synchronisation silencieuse si peu d'actions
Étant donné que j'ai seulement 2 actions en attente
Quand la synchronisation se termine
Alors aucun toast n'est affiché (sync silencieuse)
Et l'expérience reste fluide
Mais je peux voir le détail dans l'historique des syncs
# ===== GESTION ERREURS SYNC =====
Scénario: Échec synchronisation - Retry automatique
Étant donné que la synchronisation échoue (erreur réseau)
Quand l'échec est détecté
Alors un retry automatique est programmé dans 30 secondes
Et les actions restent dans pending_actions
Scénario: 3 tentatives échouées - Notification utilisateur
Étant donné que 3 tentatives de synchronisation ont échoué
Quand la 3ème tentative échoue
Alors je reçois une notification:
"""
Impossible de synchroniser vos actions
15 actions en attente de synchronisation.
Vérifiez votre connexion et réessayez.
[Réessayer maintenant] [Plus tard]
```
Scénario: Actions conservées jusqu'à sync réussie
Étant donné que la synchronisation échoue plusieurs fois
Quand les tentatives continuent d'échouer
Alors les actions restent dans pending_actions
Et aucune action n'est perdue
Et elles seront envoyées dès que la connexion sera stable
Scénario: Rétention max 7 jours - Purge automatique
Étant donné qu'une action est en attente depuis 7 jours
Quand le système détecte cette ancienneté
Alors l'action est automatiquement supprimée de la queue
Et je vois "1 action trop ancienne supprimée (>7 jours)"
Et cela évite une queue infinie
Scénario: Justification rétention 7 jours
Étant donné qu'un utilisateur ne se connecte jamais pendant 2 semaines
Quand ses actions ont >7 jours
Alors elles sont purgées automatiquement
Car après 7 jours, l'action perd sa pertinence
Et évite une queue qui grandit indéfiniment
Scénario: Retry manuel après échec
Étant donné que la synchronisation a échoué
Quand je clique sur "Réessayer maintenant"
Alors une nouvelle tentative de synchronisation est lancée immédiatement
Et si elle réussit, les actions sont synchronisées
# ===== CONFLITS CONTENUS SUPPRIMÉS =====
Scénario: Backend retourne contenus supprimés
Étant donné que j'ai liké un contenu offline
Mais que le contenu a été supprimé entre temps
Quand le backend traite la synchronisation
Alors il retourne:
```json
{
"status": "partial_success",
"deleted_content_ids": [123, 456],
"failed_actions": [
{"type": "like", "content_id": "123", "reason": "content_deleted"}
]
}
```
Scénario: App supprime fichiers locaux contenus supprimés
Étant donné que le backend retourne deleted_content_ids: [123, 456]
Quand l'app traite la réponse
Alors elle supprime les fichiers locaux des contenus 123 et 456
Et libère l'espace disque
Et les actions associées sont retirées de la queue
Scénario: Contenu supprimé en cours d'écoute
Étant donné que j'écoute le contenu 123 en offline
Et que la sync détecte que le contenu a été supprimé
Quand la lecture actuelle se termine
Alors l'app attend 2 secondes
Et passe automatiquement au contenu suivant
Et le fichier du contenu 123 est supprimé en arrière-plan
Scénario: Toast notification contenu retiré
Étant donné que 2 contenus téléchargés ont été supprimés
Quand la synchronisation se termine
Alors je vois un toast:
"""
🗑 2 contenus téléchargés ont été retirés
Raison: Violation des règles de la plateforme
"""
Scénario: Contenu modéré après téléchargement
Étant donné que j'ai téléchargé un contenu qui est ensuite modéré
Quand la synchronisation détecte la modération
Alors le contenu est immédiatement supprimé du device
Et je ne peux plus l'écouter
Et cela garantit la conformité même offline
# ===== JUSTIFICATIONS =====
Scénario: Justification pas de conflit possible
Étant donné que les actions offline sont unilatérales (likes, abonnements)
Quand elles sont synchronisées
Alors il n'y a pas de conflit de version possible
Car l'utilisateur ajoute/retire simplement des préférences
Et pas de merge complexe nécessaire
Scénario: Justification UX fluide offline
Étant donné que toutes les actions fonctionnent offline
Quand l'utilisateur interagit sans connexion
Alors l'expérience est identique au mode online
Et l'utilisateur n'est pas bloqué
Et peut utiliser l'app normalement
Scénario: Justification batch = Économie requêtes
Étant donné que 15 actions sont en attente
Quand elles sont synchronisées en batch
Alors 1 seule requête HTTP est envoyée (vs 15 si individuelles)
Et cela économise la bande passante et la batterie
Et réduit la charge serveur
Scénario: Justification conformité modération offline
Étant donné qu'un contenu illégal est modéré pendant qu'un user est offline
Quand le user se reconnecte
Alors le contenu est immédiatement supprimé de son device
Et cela garantit que les contenus illégaux disparaissent même offline
# ===== STATISTIQUES ET MONITORING =====
Scénario: Historique synchronisations
Étant donné que j'accède à "Paramètres > Synchronisation"
Quand je consulte l'historique
Alors je vois:
| date | actions sync | statut |
| 15/06/2025 14:30:00 | 15 | Réussi |
| 14/06/2025 09:15:00 | 7 | Réussi |
| 13/06/2025 18:45:00 | 3 | Échec |
Scénario: Détail d'une synchronisation
Étant donné que je clique sur une ligne de l'historique
Quand le détail s'affiche
Alors je vois:
```
Synchronisation du 15/06/2025 14:30:00
Actions synchronisées:
3 likes
1 abonnement
1 signalement
10 progressions audio-guides
Durée: 1.2s
Statut: Réussi
```
Scénario: Compteur actions en attente visible
Étant donné que j'ai 12 actions en attente de synchronisation
Quand j'accède à l'onglet Profil
Alors je vois un badge "12" sur l'icône de synchronisation
Et je sais qu'il y a des actions en attente
Scénario: Synchronisation manuelle forcée
Étant donné que je veux forcer une synchronisation immédiate
Quand je vais dans "Paramètres > Synchronisation"
Et que je clique sur "Synchroniser maintenant"
Alors la synchronisation démarre immédiatement
Et toutes les actions en attente sont envoyées
Scénario: Statistiques utilisateur - Syncs effectuées
Étant donné que j'accède à mes statistiques
Quand je consulte la section Synchronisation
Alors je vois:
| métrique | valeur |
| Synchronisations depuis début | 87 |
| Actions synchronisées total | 1,234 |
| Taux de succès | 94% |
| Dernière sync | Il y a 2h|
Scénario: Statistiques admin - Volume synchronisations
Étant donné qu'un admin consulte les métriques de synchronisation
Quand il accède au dashboard
Alors il voit:
| métrique | valeur |
| Synchronisations/jour | 45,678 |
| Actions synchronisées/jour | 234,567 |
| Taux succès sync | 96.5% |
| Temps moyen traitement batch | 0.8s |
| Actions en attente (global) | 12,345 |
Scénario: Alerte admin si taux échec sync >10%
Étant donné que le taux d'échec sync dépasse 10%
Quand le système détecte cette anomalie
Alors une alerte est envoyée:
"""
Taux échec synchronisation anormal: 12.3%
Échecs aujourd'hui: 5,621 / 45,678 syncs
Causes principales:
- Timeout serveur: 3,245
- Erreur réseau client: 1,876
- Données invalides: 500
Action recommandée: Vérifier charge serveur + logs erreurs
"""
# ===== TESTS PERFORMANCE =====
Scénario: Synchronisation rapide <2s
Étant donné que j'ai 20 actions en attente
Quand la synchronisation démarre
Alors le traitement prend <2 secondes
Et je ne remarque aucun ralentissement de l'app
Scénario: Synchronisation de gros batch (100 actions)
Étant donné que je n'ai pas synchronisé pendant 1 semaine
Et que j'ai 100 actions en attente
Quand la synchronisation démarre
Alors le batch de 100 actions est traité en <5 secondes
Et toutes les actions sont synchronisées avec succès
Scénario: Gestion charge serveur - 10 000 syncs simultanées
Étant donné que 10 000 utilisateurs se reconnectent simultanément
Quand chacun envoie un batch de 20 actions
Alors le serveur traite 200 000 actions
Et grâce au traitement asynchrone (queue Redis), le temps de réponse reste <3s
Et aucun timeout n'est constaté
Scénario: Stockage SQLite optimisé
Étant donné que la table pending_actions stocke des centaines d'actions
Quand des requêtes sont exécutées
Alors la table est indexée sur created_at
Et les requêtes SELECT et DELETE sont instantanées (<10ms)
Et l'expérience utilisateur reste fluide
Scénario: Nettoyage automatique table pending_actions
Étant donné que la table pending_actions grossit avec le temps
Quand les actions sont synchronisées et supprimées
Alors la table est automatiquement optimisée (VACUUM sur SQLite)
Et l'espace disque est libéré
Et les performances restent optimales
# ===== EDGE CASES =====
Scénario: Action dupliquée - Idempotence
Étant donné que j'ai liké un contenu offline
Et que la sync échoue et retry
Quand le backend reçoit 2 fois le même like
Alors il applique l'idempotence (1 seul like enregistré)
Et le compteur de likes n'est pas faussé
Scénario: Séquence like/unlike offline
Étant donné que j'ai liké puis unliké un contenu offline
Quand les 2 actions sont synchronisées
Alors le backend applique les 2 actions dans l'ordre
Et le résultat final est "pas de like" (état correct)
Scénario: Abonnement puis désabonnement offline
Étant donné que je me suis abonné puis désabonné d'un créateur offline
Quand les 2 actions sont synchronisées
Alors le backend applique les 2 actions dans l'ordre
Et le résultat final est "pas abonné"
Et les jauges évoluent correctement (+5% puis -5% = 0% net)
Scénario: Créateur supprimé pendant offline
Étant donné que je me suis abonné à un créateur offline
Mais que le créateur a supprimé son compte entre temps
Quand la sync traite l'abonnement
Alors le backend retourne "creator_deleted"
Et l'action est ignorée silencieusement
Et aucune erreur n'est affichée à l'utilisateur

View File

@@ -0,0 +1,409 @@
# language: fr
Fonctionnalité: Téléchargement de contenus offline
En tant qu'utilisateur
Je veux télécharger des contenus pour les écouter sans connexion
Afin de profiter de RoadWave même dans les zones sans réseau
Contexte:
Étant donné que je suis connecté à l'application RoadWave
# ===== SÉLECTION ZONE GÉOGRAPHIQUE =====
Scénario: Option "Autour de moi" - Rayon 50 km
Étant donné que je suis à Paris (position GPS détectée)
Quand je sélectionne "Télécharger > Autour de moi"
Alors l'app recherche tous les contenus géolocalisés dans un rayon de 50 km
Et je vois une liste de contenus de Paris et banlieue proche
Et l'estimation affiche "~150 contenus disponibles"
Scénario: Option "Ma ville" - Limite administrative détectée
Étant donné que je suis à Lyon (position GPS détectée)
Quand je sélectionne "Télécharger > Ma ville"
Alors l'app détecte automatiquement "Lyon" comme ville
Et recherche tous les contenus géolocalisés "Lyon"
Et je vois uniquement les contenus de la ville de Lyon (pas banlieue)
Scénario: Option "Mon département" - Sélection dans liste
Étant donné que je veux télécharger des contenus pour un département
Quand je sélectionne "Télécharger > Mon département"
Alors je vois une liste de tous les départements français:
| département |
| 01 - Ain |
| 02 - Aisne |
| 75 - Paris |
| 69 - Rhône |
| ... |
Et je peux choisir un département
Scénario: Sélection département et téléchargement contenus
Étant donné que je sélectionne "75 - Paris" dans la liste des départements
Quand la sélection est confirmée
Alors l'app recherche tous les contenus géolocalisés "Paris"
Et je vois "~234 contenus disponibles pour Paris"
Scénario: Option "Ma région" - Sélection dans liste
Étant donné que je veux télécharger des contenus pour une région
Quand je sélectionne "Télécharger > Ma région"
Alors je vois une liste de toutes les régions françaises:
| région |
| Auvergne-Rhône-Alpes |
| Bretagne |
| Île-de-France |
| Nouvelle-Aquitaine |
| Occitanie |
| ... |
Et je peux choisir une région
Scénario: Sélection région et téléchargement contenus
Étant donné que je sélectionne "Bretagne" dans la liste des régions
Quand la sélection est confirmée
Alors l'app recherche tous les contenus géolocalisés des départements bretons:
| département |
| Côtes-d'Armor (22) |
| Finistère (29) |
| Ille-et-Vilaine (35) |
| Morbihan (56) |
Et je vois "~487 contenus disponibles pour Bretagne"
Scénario: Recherche manuelle ville
Étant donné que je veux télécharger des contenus pour une ville spécifique
Quand je tape "Marseille" dans la barre de recherche
Alors l'app propose des suggestions:
| suggestion |
| Marseille (13) |
| Marseille-en-Beauvaisis |
Et je peux sélectionner "Marseille (13)"
Scénario: Recherche manuelle avec autocomplétion
Étant donné que je tape "Ly" dans la barre de recherche
Quand l'autocomplétion s'active
Alors je vois des suggestions:
| suggestion |
| Lyon (69) |
| Lys-lez-Lannoy |
Et je peux affiner ma recherche
# ===== LIMITES TÉLÉCHARGEMENT =====
Scénario: Utilisateur gratuit - Limite 50 contenus max
Étant donné que je suis un utilisateur gratuit
Et que j'ai déjà téléchargé 45 contenus
Quand j'accède à la page Téléchargements
Alors je vois "45 / 50 contenus téléchargés"
Et je peux télécharger 5 contenus supplémentaires maximum
Scénario: Utilisateur gratuit - Tentative dépasser limite 50
Étant donné que je suis gratuit et j'ai déjà 50 contenus téléchargés
Quand j'essaie de télécharger un 51ème contenu
Alors le téléchargement est refusé
Et je vois le message:
"""
📥 Limite atteinte (50 contenus)
Vous avez atteint la limite de téléchargements gratuits.
Options:
Supprimez des contenus existants pour en télécharger de nouveaux
Passez Premium pour des téléchargements illimités
[Gérer mes téléchargements] [Découvrir Premium]
"""
Scénario: Utilisateur Premium - Téléchargements illimités
Étant donné que je suis un utilisateur Premium
Et que j'ai déjà téléchargé 245 contenus
Quand j'accède à la page Téléchargements
Alors je vois "245 contenus (3.2 GB)"
Et aucune limite n'est affichée
Et je peux télécharger autant de contenus que je veux
Scénario: Limite Premium = Espace disque disponible
Étant donné que je suis Premium
Et que mon device a 500 MB d'espace disque disponible
Quand j'essaie de télécharger 100 contenus (2 GB)
Alors le téléchargement échoue après ~50 contenus (500 MB)
Et je vois "Espace disque insuffisant. Libérez de l'espace pour continuer."
Scénario: Calcul temps écoute disponible gratuit
Étant donné que je suis gratuit avec 50 contenus téléchargés
Et que la durée moyenne d'un contenu est 5 minutes
Quand je calcule le temps d'écoute disponible
Alors 50 contenus × 5 min = 250 minutes = 4h10 d'écoute
Et cela suffit pour un trajet quotidien ou road trip court
Scénario: Calcul temps écoute disponible Premium illimité
Étant donné que je suis Premium avec 300 contenus téléchargés
Et que la durée moyenne est 5 minutes
Quand je calcule le temps d'écoute disponible
Alors 300 contenus × 5 min = 1500 minutes = 25h d'écoute
Et cela suffit pour un road trip de plusieurs jours
# ===== CONNEXION WIFI / MOBILE =====
Scénario: Téléchargement par défaut en WiFi uniquement
Étant donné que je suis connecté en WiFi
Quand je clique sur "Télécharger 20 contenus"
Alors le téléchargement démarre immédiatement
Et aucune popup de confirmation n'apparaît
Scénario: Tentative téléchargement en données mobiles - Popup confirmation
Étant donné que je suis connecté en 4G (pas de WiFi)
Quand je clique sur "Télécharger 20 contenus"
Alors une popup apparaît:
"""
📡 Vous n'êtes pas connecté en WiFi
Télécharger via données mobiles consommera environ 72 MB.
[Attendre WiFi] [Continuer quand même]
"""
Scénario: Calcul estimation consommation data mobile
Étant donné que je veux télécharger 20 contenus
Et que la durée moyenne est 5 minutes
Et que la qualité Standard est 48 kbps Opus
Quand l'estimation est calculée
Alors consommation = 20 contenus × 5 min × 48 kbps / 8 = 72 MB
Et ce montant est affiché dans la popup
Scénario: Confirmation téléchargement en données mobiles
Étant donné que je vois la popup de confirmation données mobiles
Quand je clique sur "Continuer quand même"
Alors le téléchargement démarre immédiatement via 4G
Et la consommation data est comptabilisée sur mon forfait mobile
Scénario: Refus téléchargement données mobiles - Attendre WiFi
Étant donné que je vois la popup de confirmation données mobiles
Quand je clique sur "Attendre WiFi"
Alors les téléchargements sont mis en file d'attente
Et ils démarreront automatiquement quand le WiFi sera détecté
Scénario: Détection automatique WiFi et reprise téléchargements
Étant donné que j'ai mis 20 contenus en file d'attente (attente WiFi)
Quand l'app détecte une connexion WiFi
Alors les téléchargements démarrent automatiquement
Et je reçois une notification "Téléchargements en cours via WiFi"
# ===== QUALITÉ AUDIO =====
Scénario: Qualité Standard (48 kbps) par défaut
Étant donné que je configure mes téléchargements
Quand j'accède aux paramètres de qualité
Alors la qualité "Standard (48 kbps - ~20 MB/h)" est sélectionnée par défaut
Et elle est disponible pour tous (gratuit + Premium)
Scénario: Qualité Basse (24 kbps) disponible pour tous
Étant donné que j'ai peu d'espace disque disponible
Quand je sélectionne qualité "Basse (24 kbps - ~10 MB/h)"
Alors mes prochains téléchargements seront en 24 kbps
Et l'espace utilisé sera divisé par 2 par rapport à Standard
Et cette option est disponible pour gratuit + Premium
Scénario: Qualité Haute (64 kbps) réservée Premium
Étant donné que je suis un utilisateur gratuit
Quand je consulte les options de qualité
Alors l'option "Haute (64 kbps - ~30 MB/h)" est grisée
Et je vois "👑 Premium uniquement"
Et je ne peux pas la sélectionner
Scénario: Utilisateur Premium peut choisir qualité Haute
Étant donné que je suis un utilisateur Premium
Quand je consulte les options de qualité
Alors l'option "Haute (64 kbps - ~30 MB/h)" est disponible
Et je peux la sélectionner pour mes téléchargements
Et la qualité audio sera excellente (meilleure restitution voix et ambiances)
Scénario: Comparaison taille fichiers selon qualité
Étant donné que je veux télécharger 50 contenus de 5 min chacun
Quand je compare les qualités
Alors les tailles totales sont:
| qualité | bitrate | taille totale |
| Basse | 24 kbps | ~250 MB |
| Standard | 48 kbps | ~500 MB |
| Haute | 64 kbps | ~650 MB |
Scénario: Justification Standard = Bon compromis
Étant donné que le contenu RoadWave est principalement de la voix
Quand la qualité Standard (48 kbps Opus) est utilisée
Alors la qualité est très correcte pour la voix
Et équivalente à la radio FM
Et le compromis qualité/taille est optimal
Scénario: Justification Haute réservée Premium = Incitation upgrade
Étant donné qu'un utilisateur gratuit veut la meilleure qualité
Quand il voit que Haute est réservée Premium
Alors cela l'incite à passer Premium pour 4.99/mois
Et c'est un avantage tangible supplémentaire de Premium
Scénario: Changement qualité après téléchargements existants
Étant donné que j'ai déjà téléchargé 30 contenus en qualité Standard
Quand je change la qualité vers Haute (si Premium)
Alors les 30 contenus existants restent en Standard
Et seuls les nouveaux téléchargements seront en Haute
Et je peux manuellement re-télécharger les 30 contenus pour les avoir en Haute
# ===== PROCESSUS DE TÉLÉCHARGEMENT =====
Scénario: Téléchargement individuel d'un contenu
Étant donné que je consulte la page d'un contenu
Quand je clique sur l'icône de téléchargement 📥
Alors le téléchargement démarre
Et une barre de progression apparaît
Et l'icône devient quand terminé
Scénario: Téléchargement batch de contenus sélectionnés
Étant donné que je consulte une liste de contenus pour "Paris"
Quand je sélectionne 15 contenus manuellement
Et que je clique sur "Télécharger la sélection"
Alors les 15 contenus sont téléchargés en parallèle (max 3 simultanés)
Et une notification affiche "15 contenus téléchargés"
Scénario: Téléchargement automatique recommandations zone
Étant donné que je sélectionne "Autour de moi" (Paris)
Quand je clique sur "Télécharger les 50 meilleurs contenus"
Alors l'algorithme sélectionne automatiquement les 50 contenus les mieux notés/récents
Et les télécharge tous
Et je n'ai pas besoin de choisir manuellement
Scénario: Barre de progression téléchargement global
Étant donné que je télécharge 20 contenus
Quand les téléchargements sont en cours
Alors je vois une barre de progression globale:
"""
📥 Téléchargement en cours...
7 / 20 contenus (35%)
~45 MB restants
Temps estimé: 2 min
"""
Scénario: Téléchargements en tâche de fond
Étant donné que je lance le téléchargement de 30 contenus
Quand je ferme l'app ou passe à une autre activité
Alors les téléchargements continuent en arrière-plan
Et je reçois une notification quand tous sont terminés
Scénario: Pause et reprise téléchargements
Étant donné que je télécharge 20 contenus
Quand je clique sur "Pause"
Alors les téléchargements en cours se terminent
Et les téléchargements en attente sont mis en pause
Et je peux cliquer sur "Reprendre" plus tard
Scénario: Annulation téléchargements
Étant donné que je télécharge 20 contenus
Quand je clique sur "Annuler"
Alors tous les téléchargements sont arrêtés
Et les fichiers partiels sont supprimés
Et l'espace disque est libéré
Scénario: Gestion erreurs téléchargement
Étant donné que je télécharge un contenu
Mais que la connexion Internet coupe au milieu
Quand la connexion revient
Alors le téléchargement reprend automatiquement où il s'était arrêté
Et aucune perte de progression n'a lieu
Scénario: Retry automatique après échec
Étant donné qu'un téléchargement échoue 3 fois consécutives
Quand l'échec est détecté
Alors le contenu est marqué "Échec"
Et je vois une notification "3 contenus n'ont pas pu être téléchargés"
Et je peux retry manuellement en cliquant sur "Réessayer"
# ===== GESTION CONTENUS TÉLÉCHARGÉS =====
Scénario: Liste contenus téléchargés
Étant donné que j'ai téléchargé 45 contenus
Quand j'accède à "Téléchargements"
Alors je vois la liste complète de mes 45 contenus
Et pour chaque contenu: titre, créateur, durée, taille, date téléchargement
Scénario: Tri contenus téléchargés
Étant donné que je consulte ma liste de téléchargements
Quand je clique sur "Trier par"
Alors je peux trier par:
| critère | ordre |
| Date téléchargement | Plus récent / Plus ancien|
| Titre | A-Z / Z-A |
| Créateur | A-Z / Z-A |
| Durée | Plus long / Plus court |
| Taille | Plus gros / Plus petit |
Scénario: Recherche dans contenus téléchargés
Étant donné que j'ai 200 contenus téléchargés
Quand je tape "Tesla" dans la barre de recherche
Alors seuls les contenus contenant "Tesla" s'affichent
Et je peux rapidement trouver un contenu spécifique
Scénario: Suppression individuelle contenu téléchargé
Étant donné que je veux supprimer un contenu téléchargé
Quand je swipe left (iOS) ou long press (Android) sur le contenu
Et que je clique sur "Supprimer"
Alors le fichier est supprimé du device
Et l'espace disque est libéré
Et le compteur est décrémenté (ex: 45/50 44/50)
Scénario: Suppression batch contenus téléchargés
Étant donné que je veux supprimer plusieurs contenus
Quand je sélectionne 10 contenus
Et que je clique sur "Supprimer la sélection"
Alors les 10 fichiers sont supprimés
Et ~100 MB d'espace disque sont libérés
Et une notification confirme "10 contenus supprimés"
Scénario: Suppression tous les contenus téléchargés
Étant donné que j'ai 45 contenus téléchargés
Quand je clique sur "Supprimer tout"
Et que je confirme l'action
Alors tous les 45 contenus sont supprimés
Et l'espace disque total est libéré (~450 MB)
Et le compteur repasse à 0/50
Scénario: Espace disque utilisé visible
Étant donné que j'ai téléchargé 45 contenus
Quand j'accède à la page Téléchargements
Alors je vois l'espace disque utilisé:
"""
📥 Téléchargements
45 / 50 contenus
Espace utilisé: 478 MB
"""
Scénario: Statistiques téléchargements
Étant donné que j'accède à mes statistiques
Quand je consulte la section Téléchargements
Alors je vois:
| métrique | valeur |
| Contenus actuellement téléchargés | 45 |
| Espace disque utilisé | 478 MB |
| Contenus téléchargés depuis début | 287 |
| Total data téléchargée | 3.2 GB |
| Téléchargements via WiFi | 92% |
| Téléchargements via mobile | 8% |
# ===== LECTURE OFFLINE =====
Scénario: Lecture contenu téléchargé sans connexion
Étant donné que je n'ai aucune connexion Internet (mode avion)
Et que j'ai des contenus téléchargés
Quand je lance un contenu téléchargé
Alors la lecture démarre normalement depuis le fichier local
Et aucune erreur de connexion n'apparaît
Scénario: Badge "Téléchargé" sur contenus offline
Étant donné que j'ai téléchargé certains contenus
Quand je consulte une liste de contenus
Alors les contenus téléchargés ont un badge "Offline"
Et je sais immédiatement lesquels sont disponibles sans connexion
Scénario: Filtre "Téléchargés uniquement"
Étant donné que je veux voir uniquement mes contenus offline
Quand j'active le filtre "Téléchargés uniquement"
Alors seuls les contenus téléchargés s'affichent
Et je peux facilement naviguer dans mon catalogue offline
Scénario: Playlist offline automatique
Étant donné que j'ai téléchargé 45 contenus
Quand j'accède à "Téléchargements"
Alors je peux lancer une playlist aléatoire de mes 45 contenus
Et profiter d'une écoute continue offline

View File

@@ -0,0 +1,335 @@
# language: fr
Fonctionnalité: Validité et renouvellement contenus offline
En tant qu'utilisateur
Je veux que mes contenus téléchargés restent valides un certain temps
Afin de garantir la légalité et la fraîcheur du contenu
Contexte:
Étant donné que je suis connecté à l'application RoadWave
Et que j'ai des contenus téléchargés
# ===== DURÉE DE VALIDITÉ =====
Scénario: Validité de 30 jours après téléchargement
Étant donné que je télécharge un contenu le 1er juin 2025
Quand le téléchargement est terminé
Alors le contenu est valide jusqu'au 1er juillet 2025 (30 jours)
Et la date d'expiration est stockée en local
Scénario: Affichage date expiration sur contenu téléchargé
Étant donné que j'ai téléchargé un contenu il y a 20 jours
Quand je consulte les détails du contenu
Alors je vois "Expire dans 10 jours"
Et je sais combien de temps il reste avant expiration
Scénario: Standard industrie aligné (Spotify, YouTube, Deezer)
Étant donné que Spotify, YouTube Music et Deezer utilisent 30 jours
Quand RoadWave fixe également 30 jours
Alors c'est le standard accepté par les utilisateurs
Et il n'y a pas de confusion avec les autres plateformes
Scénario: Justification 30 jours - Force reconnexion régulière
Étant donné qu'un utilisateur ne se connecte jamais
Quand ses contenus expirent après 30 jours
Alors il est obligé de se reconnecter pour les renouveler
Et le système peut vérifier:
| vérification |
| Abonnement Premium toujours actif|
| Contenus non modérés/supprimés |
| Métadonnées à jour |
Scénario: Justification 30 jours - Évite stockage obsolète
Étant donné qu'un contenu a été modéré après téléchargement
Quand le contenu expire après 30 jours maximum
Alors le contenu illégal est automatiquement supprimé
Et ne reste pas indéfiniment sur le device
# ===== RENOUVELLEMENT AUTOMATIQUE =====
Scénario: Détection WiFi et contenus >25 jours
Étant donné que j'ai des contenus téléchargés il y a 26 jours
Quand l'app détecte une connexion WiFi
Alors une requête GET /offline/contents/refresh est envoyée
Et le backend vérifie chaque contenu
Scénario: Vérification abonnement Premium toujours actif
Étant donné qu'un contenu téléchargé en Premium est à renouveler
Quand le backend vérifie le statut
Et que l'abonnement Premium est toujours actif
Alors la validité est renouvelée à 30 jours supplémentaires
Scénario: Abonnement Premium expiré - Contenu non renouvelé
Étant donné qu'un contenu Premium téléchargé est à renouveler
Quand le backend vérifie le statut
Et que l'abonnement Premium a expiré
Alors le contenu n'est pas renouvelé
Et il sera supprimé à l'expiration (J-0)
Et l'utilisateur voit "Contenu Premium expiré (abonnement inactif)"
Scénario: Vérification contenu pas modéré/supprimé
Étant donné qu'un contenu téléchargé est à renouveler
Quand le backend vérifie le statut
Et que le contenu a été modéré ou supprimé entre temps
Alors le contenu n'est pas renouvelé
Et sera supprimé immédiatement du device
Et l'utilisateur voit "1 contenu retiré (violation règles)"
Scénario: Mise à jour métadonnées lors du renouvellement
Étant donné qu'un contenu téléchargé est renouvelé
Quand le backend traite le renouvellement
Alors les métadonnées sont mises à jour:
| métadonnée | mise à jour si changée |
| Titre | |
| Nom créateur | |
| Description | |
| Tags | |
| Statut Premium | |
Et l'utilisateur voit les infos à jour
Scénario: Pas de re-téléchargement audio si fichier OK
Étant donné qu'un contenu est renouvelé
Quand le fichier audio local est intact
Alors seules les métadonnées sont mises à jour
Et le fichier audio n'est pas re-téléchargé
Et cela économise la bande passante
Scénario: Re-téléchargement audio si fichier corrompu
Étant donné qu'un contenu est renouvelé
Quand le fichier audio local est corrompu (checksum invalide)
Alors le fichier audio est re-téléchargé entièrement
Et le nouveau fichier remplace le corrompu
Scénario: Renouvellement silencieux si WiFi régulier
Étant donné que je me connecte en WiFi tous les jours
Quand mes contenus atteignent 25-30 jours
Alors ils sont automatiquement renouvelés en arrière-plan
Et je ne vois aucune notification (processus transparent)
Et mes contenus restent valides indéfiniment
Scénario: Renouvellement batch de plusieurs contenus
Étant donné que j'ai 30 contenus à renouveler
Quand le renouvellement automatique se déclenche
Alors une requête batch est envoyée:
```json
POST /offline/contents/refresh
{
"content_ids": ["abc123", "def456", "ghi789", ...]
}
```
Et le backend traite les 30 contenus en une seule requête
Et cela économise les requêtes HTTP
Scénario: Temps de traitement renouvellement
Étant donné que 30 contenus sont à renouveler
Quand la requête batch est traitée
Alors le backend répond en <2 secondes
Et les métadonnées sont mises à jour localement
Et l'utilisateur ne remarque aucun ralentissement
# ===== NOTIFICATIONS EXPIRATION =====
Scénario: Notification J-3 avant expiration
Étant donné que j'ai 15 contenus qui expirent dans 3 jours
Quand le système vérifie les expirations
Alors je reçois une notification:
"""
⚠️ 15 contenus expirent dans 3 jours
Connectez-vous en WiFi pour les renouveler automatiquement.
"""
Et je peux agir avant l'expiration
Scénario: Pas de notification si connexion WiFi régulière
Étant donné que je me connecte en WiFi tous les jours
Et que mes contenus sont automatiquement renouvelés
Quand le système vérifie les expirations
Alors aucune notification J-3 n'est envoyée
Car les contenus sont déjà renouvelés silencieusement
Scénario: Notification uniquement si contenus non renouvelés
Étant donné que j'ai 20 contenus dont 15 renouvelés et 5 non renouvelés
Quand le J-3 arrive pour les 5 non renouvelés
Alors je reçois "5 contenus expirent dans 3 jours"
Et seuls les contenus à risque sont mentionnés
Scénario: Action utilisateur après notification J-3
Étant donné que je reçois la notification J-3
Quand je clique sur la notification
Alors l'app s'ouvre sur la page Téléchargements
Et je vois les contenus qui vont expirer en rouge
Et je peux me connecter en WiFi pour les renouveler
Scénario: Suppression automatique J-0 (expiration)
Étant donné qu'un contenu n'a pas été renouvelé
Quand le jour d'expiration arrive (J-0)
Alors le fichier est automatiquement supprimé du device
Et l'espace disque est libéré
Et le compteur est décrémenté (ex: 45/50 → 44/50)
Scénario: Toast après suppression automatique J-0
Étant donné que 15 contenus viennent d'expirer
Quand l'utilisateur ouvre l'app
Alors il voit un toast:
"""
🗑️ 15 contenus expirés ont été supprimés
Reconnectez-vous en WiFi régulièrement pour éviter les expirations.
"""
Scénario: Liste contenus supprimés après expiration
Étant donné que 15 contenus ont expiré
Quand je consulte l'historique des suppressions
Alors je vois la liste des 15 contenus supprimés:
| titre | créateur | date expiration |
| Mon épisode préféré | JeanDupont | 15 juin 2025 |
| Road trip Bretagne | MarieLambert| 15 juin 2025 |
| ... | ... | ... |
Et je peux les re-télécharger si je veux
Scénario: Re-téléchargement après expiration
Étant donné qu'un contenu a expiré et été supprimé
Quand je retrouve ce contenu dans l'app
Alors le badge ✅ "Offline" n'est plus affiché
Et je peux le re-télécharger normalement
Et la validité repart à 30 jours
# ===== CAS PARTICULIERS =====
Scénario: Utilisateur ne se connecte jamais pendant 30 jours
Étant donné que je télécharge 50 contenus le 1er juin
Mais que je ne me connecte jamais en WiFi pendant 30 jours
Quand le 1er juillet arrive
Alors tous les 50 contenus expirent
Et sont automatiquement supprimés
Et je n'ai plus aucun contenu offline
Scénario: Utilisateur en zone blanche 30+ jours
Étant donné que je télécharge 50 contenus avant de partir en zone sans réseau
Et que je reste 45 jours sans connexion
Quand les contenus expirent après 30 jours
Alors ils sont supprimés même si je ne peux pas me connecter
Et je perds l'accès à mes contenus offline
Scénario: Recommandation téléchargement avant zone blanche longue
Étant donné que je prépare un road trip de 60 jours
Quand je consulte la FAQ
Alors je vois la recommandation:
"""
⚠️ Road trips >30 jours
Les contenus téléchargés expirent après 30 jours.
Pour les longs voyages sans connexion:
Téléchargez de nouveaux contenus tous les 25 jours si possible
Ou planifiez une reconnexion WiFi tous les 25 jours
"""
Scénario: Changement statut Premium en gratuit pendant validité
Étant donné que je suis Premium et j'ai téléchargé 200 contenus
Quand mon abonnement Premium expire
Et que je repasse en gratuit
Alors au prochain renouvellement, seulement 50 contenus sont conservés
Et les 150 autres sont supprimés (limite gratuit)
Et je vois "Limite gratuit (50 contenus) appliquée. 150 contenus supprimés."
Scénario: Sélection automatique 50 meilleurs contenus si passage gratuit
Étant donné que je repasse en gratuit avec 200 contenus téléchargés
Quand le système applique la limite de 50
Alors les 50 contenus les plus récemment écoutés sont conservés
Et les 150 autres sont supprimés
Et cela maximise les chances de garder les contenus que j'aime
Scénario: Contenus Premium exclusifs supprimés si abonnement expire
Étant donné que j'ai téléchargé 20 contenus Premium exclusifs
Quand mon abonnement Premium expire
Alors les 20 contenus Premium sont immédiatement supprimés
Car ils ne sont accessibles qu'aux abonnés Premium actifs
Et je vois "20 contenus Premium supprimés (abonnement expiré)"
# ===== STATISTIQUES ET MONITORING =====
Scénario: Affichage temps restant avant expiration
Étant donné que j'ai 45 contenus téléchargés
Quand je consulte la page Téléchargements
Alors je vois pour chaque contenu:
| contenu | temps restant |
| Mon épisode (récent)| Expire dans 28 jours |
| Road trip (ancien) | Expire dans 3 jours |
Et je sais lesquels sont prioritaires pour renouvellement
Scénario: Tri par date expiration
Étant donné que j'ai 45 contenus avec différentes dates d'expiration
Quand je trie par "Expiration"
Alors les contenus qui expirent le plus tôt apparaissent en premier
Et je peux voir rapidement lesquels nécessitent une reconnexion urgente
Scénario: Badge rouge si expiration <3 jours
Étant donné qu'un contenu expire dans 2 jours
Quand je consulte la liste des téléchargements
Alors le contenu a un badge rouge "⚠️ Expire bientôt"
Et il est visuellement mis en avant
Scénario: Statistiques utilisateur - Taux de renouvellement
Étant donné que j'accède à mes statistiques
Quand je consulte la section Téléchargements
Alors je vois:
| métrique | valeur |
| Contenus actuels | 45 |
| Contenus expirés depuis début | 87 |
| Contenus renouvelés (auto) | 234 |
| Taux renouvellement automatique | 73% |
Scénario: Statistiques admin - Taux expiration global
Étant donné qu'un admin consulte les métriques offline
Quand il accède au dashboard
Alors il voit:
| métrique | valeur |
| Contenus téléchargés actifs | 1,234,567 |
| Expirations ce mois | 45,678 |
| Taux expiration | 3.7% |
| Renouvellements automatiques/mois | 234,567 |
Scénario: Alerte admin si taux expiration >10%
Étant donné que le taux d'expiration mensuel dépasse 10%
Quand le système détecte cette anomalie
Alors une alerte est envoyée:
"""
Taux d'expiration anormal: 12.3%
Nombre expirations ce mois: 152,345
Causes possibles:
- Utilisateurs ne se connectent plus en WiFi
- Problème renouvellement automatique ?
- Churn utilisateurs augmenté ?
Action recommandée: Enquête technique + email rappel utilisateurs
"""
Scénario: Email rappel si pas de connexion WiFi depuis 20 jours
Étant donné que je n'ai pas connecté l'app en WiFi depuis 20 jours
Et que j'ai 45 contenus téléchargés
Quand le système détecte cette inactivité WiFi
Alors je reçois un email:
"""
📡 Connectez-vous en WiFi pour conserver vos téléchargements
Vous n'avez pas connecté RoadWave en WiFi depuis 20 jours.
Vos 45 contenus téléchargés expireront dans 10 jours si non renouvelés.
Connectez-vous en WiFi avant le 30 juin pour les renouveler automatiquement.
"""
Scénario: Performance renouvellement avec 10 000 utilisateurs simultanés
Étant donné que 10 000 utilisateurs se connectent en WiFi simultanément
Quand chacun demande le renouvellement de 50 contenus
Alors le serveur traite 500 000 vérifications
Et grâce au cache Redis et index PostgreSQL, le temps de réponse reste <3s
Et les serveurs gèrent la charge sans problème
Scénario: Logs audit renouvellements
Étant donné qu'un contenu est renouvelé
Quand l'opération se termine
Alors un log est enregistré:
| timestamp | user_id | content_id | action | résultat |
| 2025-06-15 14:30:00 | abc123 | xyz789 | renew | success (+30d) |
| 2025-06-15 14:30:01 | abc123 | def456 | renew | failed (deleted)|
Et ces logs aident à débugger les problèmes

View File

@@ -0,0 +1,195 @@
# language: fr
Fonctionnalité: Actions complémentaires - Mode piéton
En tant qu'auditeur en mode piéton
Je veux accéder à des actions avancées depuis l'application mobile
Afin de liker explicitement, m'abonner ou signaler du contenu
Contexte:
Étant donné que l'API RoadWave est disponible
Et qu'un utilisateur est connecté
Et qu'il est en mode piéton (vitesse < 5 km/h)
Scénario: Like explicite avec bouton cœur
Étant donné que j'écoute un contenu tagué "Automobile"
Et que ma jauge "Automobile" est à 60%
Quand je clique sur le bouton cœur "Like"
Alors ma jauge "Automobile" augmente de 2%
Et une animation de cœur rouge s'affiche
Et une vibration courte est déclenchée
Et ma jauge "Automobile" est maintenant à 62%
Scénario: Like explicite cumulable avec like automatique
Étant donné que j'ai écouté un contenu "Voyage" à 85%
Et que j'ai reçu un like automatique renforcé (+2%)
Et que ma jauge "Voyage" est à 52%
Quand je clique sur le bouton cœur "Like"
Alors ma jauge "Voyage" augmente encore de 2%
Et ma jauge "Voyage" passe à 54%
Et les deux likes sont cumulés
Scénario: Unlike retire le like manuel uniquement
Étant donné que j'ai liké manuellement un contenu "Sport"
Et que ma jauge "Sport" est à 57%
Quand je clique à nouveau sur le bouton cœur (toggle)
Alors le cœur redevient vide (unlike)
Et ma jauge "Sport" diminue de 2%
Et ma jauge "Sport" revient à 55%
Scénario: Unlike ne retire pas le like automatique
Étant donné que j'ai écouté un contenu "Musique" à 90%
Et que j'ai reçu un like automatique renforcé (+2%)
Et que ma jauge "Musique" est à 52%
Et que je n'ai PAS liké manuellement
Quand je consulte l'interface
Alors le bouton "Unlike" n'est pas disponible
Et le cœur reste grisé (aucun like manuel)
Et ma jauge reste à 52%
Scénario: Abonnement à un créateur
Étant donné qu'un créateur publie des contenus tagués "Automobile" et "Technologie"
Et que mes jauges sont:
| catégorie | niveau |
| Automobile | 50% |
| Technologie | 45% |
Quand je clique sur "S'abonner" sur le profil du créateur
Alors ma jauge "Automobile" augmente de 5%
Et ma jauge "Technologie" augmente de 5%
Et une animation d'étoile dorée s'affiche
Et un badge "Abonné ✓" apparaît sur le profil
Et mes nouvelles jauges sont:
| catégorie | niveau |
| Automobile | 55% |
| Technologie | 50% |
Scénario: Désabonnement d'un créateur
Étant donné que je suis abonné à un créateur
Et que mes jauges "Automobile" et "Technologie" sont à 55% et 50%
Quand je clique sur "Se désabonner"
Alors ma jauge "Automobile" diminue de 5%
Et ma jauge "Technologie" diminue de 5%
Et le badge "Abonné ✓" disparaît
Et mes nouvelles jauges sont:
| catégorie | niveau |
| Automobile | 50% |
| Technologie | 45% |
Scénario: Signalement d'un contenu inapproprié
Étant donné que j'écoute un contenu
Quand je clique sur le menu contextuel "⋮"
Et que je sélectionne "Signaler"
Alors un formulaire de signalement s'ouvre
Et je dois sélectionner une catégorie:
| Catégorie |
| Haine et violence |
| Contenu sexuel |
| Illégalité |
| Droits d'auteur |
| Spam |
| Désinformation (fake news) |
| Autre |
Et je peux ajouter un commentaire optionnel
Et le signalement est envoyé au flux de modération
Scénario: Feedback visuel pour like explicite
Étant donné que je clique sur le bouton cœur
Quand le like est enregistré
Alors une animation de cœur rouge se lance (0.5s)
Et le cœur reste rouge plein
Et une vibration haptique courte est déclenchée (iOS: .light, Android: 50ms)
Et un badge "♥ Ajouté à vos favoris" s'affiche 2 secondes
Scénario: Feedback visuel pour abonnement
Étant donné que je clique sur "S'abonner"
Quand l'abonnement est enregistré
Alors une animation d'étoile dorée se lance (0.8s)
Et le bouton devient "Abonné ✓" avec badge doré
Et une notification "Abonné à [Créateur]" s'affiche
Et les contenus du créateur seront boostés +30% dans l'algo
Scénario: Menu contextuel avec toutes les options
Étant donné que j'utilise l'app en mode piéton
Quand je clique sur le menu "⋮" (3 points verticaux)
Alors les options disponibles sont:
| Option |
| Like (cœur) |
| S'abonner au créateur |
| Signaler |
| Partager |
| Voir le profil du créateur |
| Télécharger (mode offline) |
Et toutes les options sont cliquables
Scénario: Persistance des likes manuels en base de données
Étant donné que je like manuellement 5 contenus
Quand je ferme l'application
Et que je me reconnecte plus tard
Alors tous mes likes manuels sont toujours présents
Et les cœurs rouges sont affichés sur les contenus likés
Et mes jauges reflètent toujours l'impact (+2% × 5 likes)
Scénario: Liste "Mes contenus likés" accessible dans profil
Étant donné que j'ai liké manuellement 10 contenus
Quand j'accède à mon profil utilisateur
Alors je vois une section "❤️ Mes favoris"
Et la liste affiche les 10 contenus likés
Et je peux cliquer pour réécouter
Et je peux retirer un like (unlike) depuis cette liste
Scénario: Liste "Mes abonnements" accessible dans profil
Étant donné que je suis abonné à 5 créateurs
Quand j'accède à mon profil utilisateur
Alors je vois une section "⭐ Mes abonnements"
Et la liste affiche les 5 créateurs avec leurs avatars
Et je peux accéder au profil de chaque créateur
Et je peux me désabonner depuis cette liste
Scénario: Impact abonnement sur tous les tags du créateur
Étant donné qu'un créateur a publié des contenus avec ces tags:
| Contenu | Tags |
| C1 | Automobile, Voyage |
| C2 | Automobile, Technologie |
| C3 | Voyage, Famille |
Et que mes jauges sont toutes à 50%
Quand je m'abonne à ce créateur
Alors les jauges impactées sont:
| Tag | Impact |
| Automobile | +5% |
| Voyage | +5% |
| Technologie | +5% |
| Famille | +5% |
Et toutes les autres jauges restent à 50%
Scénario: Limite d'abonnements (200 maximum)
Étant donné que je suis abonné à 200 créateurs
Quand j'essaie de m'abonner à un 201ème créateur
Alors un message "Limite de 200 abonnements atteinte" s'affiche
Et je dois me désabonner d'un créateur existant pour en ajouter un nouveau
Scénario: Confirmation avant désabonnement
Étant donné que je suis abonné à un créateur
Quand je clique sur "Se désabonner"
Alors une popup de confirmation s'affiche:
"""
Se désabonner de @CreateurNom ?
Vous ne recevrez plus de notifications pour ses contenus.
Vos jauges diminueront de 5%.
"""
Et je dois confirmer pour valider
Et je peux annuler pour conserver l'abonnement
Plan du Scénario: Cumul like automatique + like manuel
Étant donné qu'un contenu est tagué "Sport"
Et que ma jauge "Sport" est à 50%
Quand j'écoute à <pourcentage>% (like auto <auto>)
Et que je like manuellement (+2%)
Alors l'impact total est <total>
Et ma nouvelle jauge est <nouveau_niveau>
Exemples:
| pourcentage | auto | total | nouveau_niveau |
| 10 | 0 | +2% | 52% |
| 30 | +1% | +3% | 53% |
| 50 | +1% | +3% | 53% |
| 80 | +2% | +4% | 54% |
| 95 | +2% | +4% | 54% |

View File

@@ -0,0 +1,177 @@
# language: fr
Fonctionnalité: Commande "Précédent"
En tant qu'auditeur
Je veux que le bouton "Précédent" ait un comportement intelligent
Afin de rejouer le contenu actuel ou revenir au précédent selon la progression
Contexte:
Étant donné que l'API RoadWave est disponible
Et qu'un utilisateur est connecté
Scénario: Précédent après <10s revient au contenu précédent
Étant donné que j'ai écouté le contenu "A" pendant 2 minutes
Et que j'écoute maintenant le contenu "B" depuis 5 secondes
Quand j'appuie sur "Précédent"
Alors la lecture revient au contenu "A"
Et la position de lecture est à 2 minutes (position exacte sauvegardée)
Et le contenu "B" reste en historique
Scénario: Précédent après ≥10s rejoue le contenu actuel
Étant donné que j'écoute le contenu "C" depuis 15 secondes
Quand j'appuie sur "Précédent"
Alors le contenu "C" rejoue depuis le début (position 0:00)
Et la lecture ne revient pas au contenu précédent
Et la progress bar revient à 0%
Scénario: Précédent exactement à 10s rejoue le contenu actuel
Étant donné que j'écoute le contenu "D" depuis exactement 10 secondes
Quand j'appuie sur "Précédent"
Alors le contenu "D" rejoue depuis le début
Et la lecture ne revient pas au contenu précédent
Scénario: Précédent sur le premier contenu de session
Étant donné que je viens de démarrer l'application
Et que j'écoute le contenu "Premier" depuis 3 secondes
Quand j'appuie sur "Précédent"
Alors le contenu "Premier" rejoue depuis le début
Et aucun contenu précédent n'existe
Scénario: Historique de navigation limité à 10 contenus
Étant donné que j'ai écouté 10 contenus [C1, C2, ..., C10]
Et que l'historique Redis contient 10 entrées
Quand je passe au contenu C11
Alors le contenu C1 est supprimé de l'historique (FIFO)
Et l'historique contient [C2, C3, ..., C10, C11]
Et la taille reste à 10 contenus maximum
Scénario: Position exacte sauvegardée dans l'historique
Étant donné que j'écoute le contenu "A" (durée 5 minutes)
Quand j'atteins 2 minutes 30 secondes
Et que j'appuie sur "Suivant"
Alors l'historique enregistre:
| content_id | position_seconds | listened_at |
| A | 150 | 2026-01-21T10:30:00 |
Quand je reviens au contenu "A" via "Précédent"
Alors la lecture reprend exactement à 2 minutes 30 secondes
Scénario: Navigation arrière sur plusieurs contenus
Étant donné que j'ai écouté dans l'ordre: A (2min), B (30s), C (3min)
Et que j'écoute maintenant D depuis 1 seconde
Quand j'appuie sur "Précédent" (1ère fois)
Alors je reviens au contenu C à la position 3 minutes
Quand j'appuie sur "Précédent" (<10s sur C)
Alors je reviens au contenu B à la position 30 secondes
Quand j'appuie sur "Précédent" (<10s sur B)
Alors je reviens au contenu A à la position 2 minutes
Scénario: Précédent après milieu du contenu rejoue depuis début
Étant donné que j'écoute un contenu de 5 minutes
Quand j'atteins 2 minutes 30 secondes (milieu)
Et que j'appuie sur "Précédent"
Alors le contenu actuel rejoue depuis 0:00
Et je ne reviens pas au contenu précédent
Scénario: Enchaînement Suivant puis Précédent rapide
Étant donné que j'écoute le contenu "A" depuis 1 minute
Quand j'appuie sur "Suivant"
Alors le contenu "B" démarre
Quand j'appuie immédiatement sur "Précédent" (2s après)
Alors je reviens au contenu "A" à la position 1 minute
Et le contenu "B" reste dans l'historique
Scénario: Transition fluide avec animation 0.3s
Étant donné que j'appuie sur "Précédent"
Quand le changement de contenu se produit
Alors la transition audio utilise un fade out/in de 0.3 secondes
Et la progress bar revient avec une animation fluide
Et l'interface ne montre aucun message de confirmation
Scénario: Historique survit au changement de réseau
Étant donné que j'ai un historique de 5 contenus en cache Redis
Quand je perds la connexion réseau temporairement
Et que je reviens en ligne
Alors l'historique de navigation est toujours disponible
Et je peux toujours utiliser "Précédent"
Scénario: Historique stocké en Redis avec structure complète
Étant donné que j'ai écouté 3 contenus
Quand je consulte le cache Redis
Alors la structure est:
"""
user:{user_id}:history = [
{content_id: "C3", position_seconds: 45, listened_at: "2026-01-21T10:33:00Z"},
{content_id: "C2", position_seconds: 120, listened_at: "2026-01-21T10:30:00Z"},
{content_id: "C1", position_seconds: 180, listened_at: "2026-01-21T10:27:00Z"}
]
"""
Et l'ordre est du plus récent au plus ancien
Scénario: Précédent sur contenu en cours au début (<10s) du premier
Étant donné que je démarre une session avec le contenu "Initial"
Et que j'écoute depuis 3 secondes
Quand j'appuie sur "Précédent"
Alors le contenu "Initial" rejoue depuis le début
Et aucune erreur n'est générée
Et l'historique reste vide
Scénario: Compteur de temps respecte les seuils exacts
Étant donné que j'écoute un contenu
Quand le temps écoulé est de 9.9 secondes
Et que j'appuie sur "Précédent"
Alors je reviens au contenu précédent
Quand le temps écoulé est de 10.0 secondes
Et que j'appuie sur "Précédent"
Alors le contenu actuel rejoue depuis le début
Scénario: Progress bar visuelle reflète le retour exact
Étant donné que j'ai écouté le contenu "A" jusqu'à 75% (3min45 sur 5min)
Et que je suis passé au contenu "B"
Quand je reviens au contenu "A" via "Précédent"
Alors la progress bar affiche 75%
Et l'indicateur de temps affiche "3:45 / 5:00"
Et la lecture reprend exactement à cet endroit
Scénario: Métadonnées d'historique incluent timestamp précis
Étant donné que j'écoute un contenu "X" pendant 45 secondes à 10:30:15
Quand je passe au contenu suivant
Alors l'historique enregistre:
| content_id | position_seconds | listened_at |
| X | 45 | 2026-01-21T10:30:15Z |
Et le timestamp précis permet l'analyse d'usage
Scénario: Suppression FIFO respecte l'ordre chronologique
Étant donné un historique de [C1@10:00, C2@10:02, ..., C10@10:20]
Quand j'ajoute C11 à 10:22
Alors C1 (le plus ancien) est supprimé
Et l'historique contient [C2@10:02, ..., C11@10:22]
Et la taille reste exactement 10 entrées
Plan du Scénario: Comportement selon temps écouté
Étant donné que j'écoute un contenu depuis <temps> secondes
Quand j'appuie sur "Précédent"
Alors l'action est <comportement>
Exemples:
| temps | comportement |
| 1 | revenir au contenu précédent |
| 5 | revenir au contenu précédent |
| 9 | revenir au contenu précédent |
| 10 | rejouer le contenu actuel depuis 0:00 |
| 11 | rejouer le contenu actuel depuis 0:00 |
| 30 | rejouer le contenu actuel depuis 0:00 |
| 180 | rejouer le contenu actuel depuis 0:00 |
Plan du Scénario: Positions de reprise exactes
Étant donné que j'écoute un contenu de 10 minutes
Quand j'atteins <position> et passe au suivant
Et que je reviens via "Précédent"
Alors la lecture reprend exactement à <position>
Exemples:
| position |
| 0:15 |
| 1:30 |
| 3:45 |
| 5:00 |
| 7:23 |
| 9:50 |

View File

@@ -0,0 +1,238 @@
# language: fr
Fonctionnalité: Commandes vocales CarPlay et Android Auto
En tant que conducteur avec CarPlay ou Android Auto
Je veux utiliser des commandes vocales pour interagir avec l'application
Afin de garder les mains sur le volant et les yeux sur la route
Contexte:
Étant donné que l'API RoadWave est disponible
Et qu'un utilisateur est connecté
Et que CarPlay ou Android Auto est activé
Scénario: Disponibilité des commandes vocales uniquement avec CarPlay/Android Auto
Étant donné que je conduis avec CarPlay activé
Quand je dis "Hey Siri"
Alors Siri est disponible pour les commandes RoadWave
Étant donné que je conduis avec Android Auto activé
Quand je dis "OK Google"
Alors Google Assistant est disponible pour les commandes RoadWave
Scénario: Parc automobile compatible avec vocal (30-40% en 2026)
Étant donné que nous sommes en 2026
Quand je consulte les statistiques du parc automobile EU
Alors environ 30-40% des véhicules ont CarPlay ou Android Auto
Et ces utilisateurs peuvent utiliser les commandes vocales
Et les 60-70% restants utilisent les commandes au volant uniquement
Scénario: Commande vocale "Like ce podcast" avec Siri
Étant donné que j'écoute un contenu tagué "Automobile"
Et que ma jauge "Automobile" est à 60%
Quand je dis "Hey Siri, like ce podcast"
Alors un like explicite (+2%) est enregistré
Et ma jauge "Automobile" passe à 62%
Et Siri confirme vocalement "J'ai ajouté ce contenu à vos favoris"
Et aucune interaction écran n'est requise
Scénario: Commande vocale "Like ce contenu" avec Google Assistant
Étant donné que j'écoute un contenu tagué "Voyage"
Quand je dis "OK Google, like ce contenu"
Alors un like explicite est enregistré (+2%)
Et Google Assistant confirme "J'ai liké ce contenu pour vous"
Et la commande fonctionne sans toucher l'écran
Scénario: Commande vocale "Abonne-moi à ce créateur"
Étant donné que j'écoute un contenu d'un créateur tagué "Automobile" et "Technologie"
Et que mes jauges sont à 50% et 45%
Quand je dis "Hey Siri, abonne-moi à ce créateur"
Alors l'abonnement est enregistré
Et mes jauges augmentent de 5% chacune (55% et 50%)
Et Siri confirme "Vous êtes maintenant abonné à [Nom du créateur]"
Scénario: Commande vocale "Passe au contenu suivant"
Étant donné que j'écoute un contenu "A"
Quand je dis "Hey Siri, passe au contenu suivant"
Alors le contenu "B" démarre immédiatement
Et la commande a le même effet que le bouton physique "Suivant"
Scénario: Commande vocale "Signale ce contenu"
Étant donné que j'écoute un contenu inapproprié
Quand je dis "OK Google, signale ce contenu"
Alors Google Assistant demande "Quelle catégorie ?"
Et je réponds vocalement "Spam"
Alors le signalement est enregistré avec la catégorie "Spam"
Et Google Assistant confirme "J'ai signalé ce contenu"
Scénario: Commande vocale avec catégorie de signalement
Étant donné que j'écoute un contenu
Quand je dis "Hey Siri, signale ce contenu pour haine"
Alors le signalement est enregistré avec la catégorie "Haine et violence"
Et Siri confirme "J'ai signalé ce contenu pour haine et violence"
Et le flux de modération reçoit le signalement
Scénario: Liste des catégories de signalement vocales supportées
Étant donné que je dis "signale ce contenu pour [catégorie]"
Quand la catégorie est:
| Mot-clé vocal | Catégorie mappée |
| "haine" | Haine et violence |
| "sexuel" | Contenu sexuel |
| "illégalité" | Illégalité |
| "droits d'auteur" | Droits d'auteur |
| "spam" | Spam |
| "fake news" | Désinformation |
| "autre" | Autre |
Alors le signalement est enregistré avec la bonne catégorie
Scénario: Commande vocale non reconnue - fallback
Étant donné que je dis "Hey Siri, super ce podcast"
Quand Siri ne reconnaît pas l'intent RoadWave
Alors Siri répond "Je ne comprends pas cette commande RoadWave"
Et elle suggère "Dites 'like ce podcast' ou 'passe au suivant'"
Scénario: Commandes vocales disponibles en conduite uniquement
Étant donné que je roule à 50 km/h
Quand j'utilise les commandes vocales
Alors toutes les commandes sont disponibles:
| Commande | Action |
| "Like ce podcast" | Like explicite +2% |
| "Abonne-moi à ce créateur" | Abonnement +5% |
| "Passe au suivant" | Contenu suivant |
| "Reviens au précédent" | Contenu précédent (règle 10s) |
| "Pause" | Pause lecture |
| "Reprends la lecture" | Play |
| "Signale ce contenu" | Signalement |
Scénario: Intent iOS personnalisé pour RoadWave
Étant donné que l'app iOS implémente les Intents
Quand je configure les Shortcuts iOS
Alors les intents suivants sont disponibles:
| Intent Name | Action |
| LikeCurrentContentIntent | Like explicite |
| SubscribeToCreatorIntent | Abonnement |
| ReportContentIntent | Signalement |
| SkipToNextContentIntent | Suivant |
Et Siri les reconnaît automatiquement
Scénario: Intent Android personnalisé pour RoadWave
Étant donné que l'app Android implémente les Voice Actions
Quand je configure les actions Google Assistant
Alors les actions suivantes sont disponibles:
| Action Name | Action |
| com.roadwave.LIKE_CONTENT | Like explicite |
| com.roadwave.SUBSCRIBE_CREATOR | Abonnement |
| com.roadwave.REPORT_CONTENT | Signalement |
| com.roadwave.SKIP_NEXT | Suivant |
Et Google Assistant les reconnaît
Scénario: Confirmation vocale après action réussie
Étant donné que je dis "Hey Siri, like ce podcast"
Quand l'action est enregistrée avec succès
Alors Siri répond immédiatement avec confirmation:
"""
J'ai ajouté ce contenu à vos favoris
"""
Et la réponse est naturelle et concise
Et elle ne distrait pas de la conduite
Scénario: Gestion d'erreur vocale si action échoue
Étant donné que je dis "Hey Siri, abonne-moi à ce créateur"
Et que j'ai atteint la limite de 200 abonnements
Quand Siri essaie d'enregistrer l'abonnement
Alors l'action échoue
Et Siri répond "Impossible de s'abonner, limite de 200 abonnements atteinte"
Et elle suggère "Désabonnez-vous d'un créateur pour continuer"
Scénario: Commandes vocales multilingues (français)
Étant donné que mon Siri est configuré en français
Quand je dis "Hey Siri, j'aime ce podcast"
Alors la commande est reconnue (variante de "like ce podcast")
Quand je dis "Hey Siri, mets une étoile"
Alors la commande est reconnue (variante de "like")
Scénario: Implémentation post-MVP (Sprint 5)
Étant donné que les commandes vocales sont une feature Sprint 5
Quand le MVP est lancé
Alors seules les commandes au volant physiques sont disponibles
Quand le Sprint 5 est déployé
Alors les intents iOS/Android sont activés
Et les commandes vocales deviennent disponibles
Scénario: Priorisation commandes vocales vs boutons physiques
Étant donné que je conduis avec CarPlay
Et que j'ai accès aux boutons physiques ET aux commandes vocales
Quand je veux liker un contenu
Alors je peux soit:
- Attendre l'arrêt et cliquer le bouton cœur (recommandé)
- Dire "Hey Siri, like ce podcast" (en conduite)
- Laisser le like automatique se faire (écoute 80%)
Et les 3 méthodes sont valides
Scénario: Statistiques d'usage des commandes vocales
Étant donné que 100 utilisateurs avec CarPlay utilisent RoadWave
Quand je consulte les analytics
Alors je peux voir:
| Métrique | Exemple valeur |
| Taux d'utilisation commandes vocal | 15% |
| Commande la plus utilisée | "Like" |
| Taux de reconnaissance réussie | 92% |
| Taux d'échec / incompréhension | 8% |
Scénario: Feedback haptique désactivé pour commandes vocales
Étant donné que je like un contenu via commande vocale
Quand l'action est enregistrée
Alors aucune vibration haptique n'est déclenchée
Et seule la confirmation vocale est donnée
Car je n'ai pas le téléphone en main
Scénario: Badge visuel mis à jour après commande vocale
Étant donné que je dis "Hey Siri, like ce podcast"
Quand l'action est enregistrée
Alors le badge " Ajouté à vos favoris" s'affiche sur l'écran CarPlay
Et le cœur devient rouge plein dans l'interface
Et la mise à jour est visible même sans toucher l'écran
Scénario: Commandes vocales avec contenu sans créateur
Étant donné que j'écoute un contenu anonyme (créateur supprimé)
Quand je dis "Hey Siri, abonne-moi à ce créateur"
Alors Siri répond "Ce créateur n'est plus disponible"
Et aucun abonnement n'est enregistré
Scénario: Limitation temporelle des commandes vocales
Étant donné que je dis "Hey Siri, like ce podcast"
Et que le contenu change 1 seconde après
Quand Siri traite la commande 2 secondes plus tard
Alors la commande s'applique au contenu qui était en lecture au moment de la commande
Et non au contenu actuel (système de timestamp)
Plan du Scénario: Commandes vocales avec différents assistants
Étant donné que j'utilise <assistant>
Quand je dis <commande>
Alors l'action <action> est exécutée
Et la confirmation est <confirmation>
Exemples:
| assistant | commande | action | confirmation |
| Siri | "Like ce podcast" | Like +2% | "Ajouté à vos favoris" |
| Google Assistant | "Like ce contenu" | Like +2% | "J'ai liké ce contenu" |
| Siri | "Abonne-moi à ce créateur" | Abonnement +5% | "Vous êtes abonné" |
| Google Assistant | "Abonne-moi à ce créateur" | Abonnement +5% | "Abonnement enregistré" |
| Siri | "Signale ce contenu" | Signalement | "J'ai signalé ce contenu" |
| Google Assistant | "Signale ce contenu" | Signalement | "Contenu signalé" |
Plan du Scénario: Mapping catégories signalement vocal
Étant donné que je dis "signale ce contenu pour <mot_cle>"
Quand <mot_cle> est reconnu
Alors la catégorie mappée est <categorie>
Exemples:
| mot_cle | categorie |
| haine | Haine et violence |
| violence | Haine et violence |
| sexuel | Contenu sexuel |
| porno | Contenu sexuel |
| illégal | Illégalité |
| terrorisme | Illégalité |
| copyright | Droits d'auteur |
| droits auteur | Droits d'auteur |
| spam | Spam |
| fake news | Désinformation |
| fausse info | Désinformation |

View File

@@ -0,0 +1,205 @@
# language: fr
Fonctionnalité: Commandes au volant et interactions simplifiées
En tant que conducteur en sécurité
Je veux utiliser uniquement les commandes simplifiées au volant
Afin de naviguer sans distraction et en toute sécurité
Contexte:
Étant donné que l'API RoadWave est disponible
Et qu'un utilisateur est connecté
Et que l'application est connectée via CarPlay ou Android Auto
Scénario: Trois commandes disponibles au volant uniquement
Étant donné que je conduis à 50 km/h
Quand je consulte les commandes physiques disponibles
Alors seules 3 actions sont disponibles:
| Commande | Action |
| Suivant | Passer au contenu suivant |
| Précédent | Revenir au précédent (règle 10s) |
| Play/Pause | Pause/reprise avec fade 0.3s |
Et aucune commande complexe n'est proposée
Scénario: Commande "Suivant" au volant
Étant donné que j'écoute un contenu "A"
Quand j'appuie sur le bouton physique "Suivant" au volant
Alors le contenu "B" démarre immédiatement
Et aucune action supplémentaire n'est requise
Et l'interface ne demande aucune confirmation
Scénario: Commande "Précédent" au volant respecte règle 10s
Étant donné que j'écoute un contenu depuis 5 secondes
Quand j'appuie sur "Précédent" au volant
Alors je reviens au contenu précédent (règle <10s)
Étant donné que j'écoute un contenu depuis 15 secondes
Quand j'appuie sur "Précédent" au volant
Alors le contenu actuel rejoue depuis le début (règle ≥10s)
Scénario: Commande "Play/Pause" avec fade audio
Étant donné qu'un contenu est en lecture
Quand j'appuie sur "Pause" au volant
Alors la lecture se met en pause avec un fade out de 0.3 secondes
Et la position de lecture est sauvegardée
Quand j'appuie sur "Play" au volant
Alors la lecture reprend avec un fade in de 0.3 secondes
Et la reprise se fait à la position exacte
Scénario: Aucune commande complexe supportée
Étant donné que je conduis
Quand j'essaie un appui long sur "Suivant"
Alors l'action n'est pas détectée (non supporté iOS/Android)
Quand j'essaie un double-appui sur "Pause"
Alors l'action n'est pas détectée
Et seules les actions simples (clic simple) fonctionnent
Scénario: Compatibilité 100% tous véhicules
Étant donné que je conduis une voiture avec commandes basiques
Et que mon véhicule a seulement Suivant/Précédent/Pause
Quand j'utilise RoadWave
Alors toutes les fonctions essentielles sont accessibles
Et je n'ai pas besoin de boutons supplémentaires
Scénario: Feedback visuel discret après action
Étant donné que j'appuie sur "Suivant"
Quand le contenu change
Alors l'interface CarPlay/Android Auto affiche le nouveau titre
Et aucune popup ne bloque la vue
Et le changement est fluide et immédiat
Scénario: Like automatique renforcé après écoute ≥80%
Étant donné que j'écoute un contenu de 5 minutes tagué "Automobile"
Quand j'écoute pendant 4 minutes 30 secondes (90%)
Alors un like automatique renforcé (+2 points) est enregistré
Et un badge discret "♥ Ajouté à vos favoris" s'affiche 2 secondes
Et aucune action manuelle n'est requise
Scénario: Like automatique standard après écoute 30-79%
Étant donné que j'écoute un contenu de 5 minutes tagué "Voyage"
Quand j'écoute pendant 2 minutes (40%)
Et que j'appuie sur "Suivant"
Alors un like automatique standard (+1 point) est enregistré
Et un badge discret s'affiche brièvement
Et je peux continuer à conduire sans interruption
Scénario: Signal négatif après skip rapide <10s
Étant donné que j'écoute un contenu tagué "Politique"
Quand j'appuie sur "Suivant" après seulement 5 secondes
Alors un signal négatif (-0.5 point) est enregistré
Et la jauge "Politique" diminue légèrement
Et aucun message n'est affiché (transparence)
Scénario: Pas de like si écoute <30%
Étant donné que j'écoute un contenu de 10 minutes
Quand j'écoute pendant 2 minutes (20%)
Et que j'appuie sur "Suivant"
Alors aucun like n'est enregistré
Et les jauges ne changent pas
Et le système considère l'écoute comme neutre
Scénario: Badge de feedback visuel disparaît après 2 secondes
Étant donné que je reçois un like automatique
Quand le badge "♥ Ajouté à vos favoris" apparaît
Alors il reste visible 2 secondes en bas de l'écran
Et il disparaît automatiquement sans action
Et il ne bloque pas la vue de la route
Scénario: Tracking du temps d'écoute précis côté client
Étant donné que je démarre la lecture d'un contenu
Quand le player audio iOS/Android enregistre le temps
Alors le startTime est enregistré à la milliseconde
Quand j'arrête la lecture (Suivant, Pause, ou fin)
Alors la durée exacte écoutée est calculée
Et le pourcentage (durée / durée_totale * 100) est envoyé à l'API
Scénario: API reçoit les événements d'écoute pour calcul
Étant donné que j'écoute un contenu de 5 minutes à 80%
Quand l'événement est envoyé à l'API
Alors le backend reçoit:
"""
{
"content_id": "abc123",
"duration": 240,
"content_total": 300,
"percentage": 80.0,
"action": "completed"
}
"""
Et le backend calcule le like automatique (+2 points)
Et les jauges sont mises à jour immédiatement (Redis + PostgreSQL)
Scénario: Actions différentes selon arrêt du contenu
Étant donné que j'écoute un contenu
Quand j'appuie sur "Suivant"
Alors l'action envoyée est "skipped"
Quand le contenu se termine naturellement
Alors l'action envoyée est "completed"
Quand j'appuie sur "Pause"
Alors l'action envoyée est "paused"
Et le backend traite chaque action différemment
Scénario: Calcul immédiat côté backend sans délai
Étant donné que l'API reçoit un événement d'écoute
Quand le backend traite l'événement
Alors les jauges sont mises à jour immédiatement (< 100ms)
Et les nouvelles recommandations utilisent les valeurs actualisées
Et il n'y a aucun batch différé
Scénario: Compatibilité iOS avec AVPlayer
Étant donné que l'app iOS utilise AVPlayer
Quand les commandes physiques sont interceptées
Alors les événements MPRemoteCommandCenter sont capturés:
| Commande | Événement iOS |
| Suivant | nextTrackCommand |
| Précédent | previousTrackCommand |
| Play/Pause | playCommand / pauseCommand |
Et le tracking du temps utilise CMTime
Scénario: Compatibilité Android avec MediaSession
Étant donné que l'app Android utilise MediaPlayer
Quand les commandes physiques sont interceptées
Alors les événements MediaSession sont capturés:
| Commande | Action Android |
| Suivant | ACTION_SKIP_TO_NEXT |
| Précédent | ACTION_SKIP_TO_PREVIOUS |
| Play/Pause | ACTION_PLAY / ACTION_PAUSE |
Et le tracking du temps utilise SystemClock.elapsedRealtime()
Scénario: Sécurité maximale - pas de distraction
Étant donné que je conduis à 80 km/h
Quand j'utilise RoadWave avec les commandes au volant
Alors je n'ai jamais besoin de regarder mon téléphone
Et je n'ai jamais besoin de toucher l'écran CarPlay/Android Auto
Et toutes les actions sont accessibles via boutons physiques
Et les likes sont enregistrés automatiquement
Plan du Scénario: Calcul du like automatique selon pourcentage
Étant donné que j'écoute un contenu tagué "Sport"
Quand j'écoute pendant <pourcentage>%
Alors le like automatique est <type>
Et l'impact sur la jauge est <points>
Exemples:
| pourcentage | type | points |
| 10 | aucun | 0 |
| 25 | aucun | 0 |
| 29 | aucun | 0 |
| 30 | standard | +1 |
| 50 | standard | +1 |
| 79 | standard | +1 |
| 80 | renforcé | +2 |
| 90 | renforcé | +2 |
| 100 | renforcé | +2 |
Plan du Scénario: Signal négatif uniquement si skip très rapide
Étant donné que j'écoute un contenu
Quand je skip après <secondes> secondes
Alors le signal est <type>
Et l'impact est <points>
Exemples:
| secondes | type | points |
| 3 | négatif | -0.5 |
| 5 | négatif | -0.5 |
| 9 | négatif | -0.5 |
| 10 | neutre | 0 |
| 15 | neutre | 0 |
| 30 | neutre | 0 |

View File

@@ -0,0 +1,188 @@
# language: fr
Fonctionnalité: File d'attente et commande "Suivant"
En tant qu'auditeur en déplacement
Je veux que l'application pré-calcule intelligemment les prochains contenus
Afin d'avoir une navigation fluide sans latence
Contexte:
Étant donné que l'API RoadWave est disponible
Et qu'un utilisateur est connecté
Et que la géolocalisation est activée
Scénario: Pré-calcul initial de 5 contenus en cache
Étant donné que je viens de démarrer l'application
Et que je suis situé à Paris (48.8566, 2.3522)
Et que je suis en mode voiture (vitesse 5 km/h)
Quand l'application initialise la lecture
Alors une file d'attente de 5 contenus est pré-calculée
Et la file est stockée en cache Redis avec la clé "user:{user_id}:queue"
Et les métadonnées incluent ma position, le timestamp de calcul et le mode
Et le cache a un TTL de 15 minutes
Scénario: Commande "Suivant" sans latence
Étant donné qu'une file d'attente de 5 contenus est en cache
Et que j'écoute actuellement le contenu "A"
Quand j'appuie sur le bouton "Suivant"
Alors le contenu suivant démarre immédiatement (< 100ms)
Et le contenu est retiré de la file d'attente
Et il reste 4 contenus dans la file
Scénario: Recalcul automatique après déplacement >10km
Étant donné que la file a été calculée à Paris (48.8566, 2.3522)
Et que j'ai 5 contenus en cache
Quand je me déplace à Versailles (48.8049, 2.1204) soit 12km
Alors la file d'attente est invalidée automatiquement
Et une nouvelle file de 5 contenus est recalculée
Et elle est basée sur ma nouvelle position
Scénario: Recalcul automatique toutes les 10 minutes
Étant donné qu'une file a été calculée il y a 10 minutes
Et que ma position n'a pas changé
Quand le timer de rafraîchissement expire
Alors une nouvelle file de 5 contenus est recalculée
Et les anciens contenus non écoutés sont remplacés
Et les nouveaux contenus publiés depuis sont inclus
Scénario: Recalcul quand il reste moins de 3 contenus
Étant donné qu'il reste 3 contenus dans ma file d'attente
Quand j'appuie sur "Suivant"
Alors il reste 2 contenus
Et un recalcul asynchrone est déclenché en arrière-plan
Et 3 nouveaux contenus sont ajoutés à la file
Et la file contient maintenant 5 contenus
Scénario: Insertion prioritaire d'un contenu géolocalisé en mode voiture
Étant donné que j'ai une file de 5 contenus pré-calculée
Et que je suis en mode voiture
Et que je me déplace à 50 km/h vers un point avec contenu géolocalisé
Quand je suis à 98m du point (ETA = 7 secondes)
Alors une notification est envoyée (icône + compteur 71 + son)
Et je dois appuyer sur "Suivant" dans les 7 secondes pour valider
Quand j'appuie sur "Suivant"
Alors un décompte de 5 secondes démarre
Et après 5 secondes, le contenu géolocalisé s'insère et démarre
Et il remplace le contenu actuel dans la lecture
Scénario: Contenu géolocalisé ignoré est perdu (cooldown activé)
Étant donné qu'une notification géolocalisée est affichée (compteur 71)
Quand je ne clique pas sur "Suivant" pendant les 7 secondes
Alors la notification disparaît
Et le contenu géolocalisé est perdu (pas d'insertion dans la file)
Et un cooldown de 10 minutes est activé
Et aucune nouvelle notification géolocalisée ne sera envoyée pendant 10 minutes
Scénario: Validation d'une notification géolocalisée
Étant donné qu'une notification géolocalisée est affichée (compteur à 5)
Et que j'écoute un podcast
Quand j'appuie sur "Suivant"
Alors le compteur bascule à "5" (décompte final)
Et le podcast actuel continue de jouer
Et après 5 secondes, le contenu géolocalisé démarre
Et le podcast est mis en pause et sauvegardé dans l'historique
Scénario: Invalidation immédiate après modification des préférences
Étant donné que j'ai une file de 5 contenus en cache
Et que ma vitesse GPS est de 5 km/h (piéton)
Quand je modifie mes curseurs de préférences (géo/découverte/politique)
Alors la file d'attente est invalidée immédiatement
Et une nouvelle file est recalculée avec les nouvelles préférences
Et les anciens contenus en cache sont supprimés
Scénario: Blocage modification préférences en conduite (>10 km/h)
Étant donné que ma vitesse GPS est de 50 km/h (en voiture)
Quand j'essaie d'accéder aux réglages de préférences
Alors l'interface affiche "Paramètres verrouillés en conduite"
Et je ne peux pas modifier les curseurs géo/découverte/politique
Et un message "Arrêtez-vous pour modifier vos préférences" s'affiche
Scénario: Invalidation lors du démarrage d'un live suivi
Étant donné que je suis abonné au créateur "RadioVoyage"
Et que j'ai une file de 5 contenus en cache
Et que je suis dans la zone géographique du créateur
Quand le créateur "RadioVoyage" démarre une radio live
Alors je reçois une notification push
Et le contenu live s'insère en tête de la file d'attente
Et la file d'attente est recalculée
Scénario: Métadonnées de cache Redis
Étant donné qu'une file d'attente est calculée
Quand elle est stockée dans Redis
Alors la clé est "user:{user_id}:queue"
Et les métadonnées incluent:
| champ | valeur |
| last_lat | 48.8566 |
| last_lon | 2.3522 |
| computed_at | 2026-01-21T10:30:00Z |
| mode | voiture |
Et le TTL est de 15 minutes (900 secondes)
Scénario: Contenu géolocalisé remplace le contenu actuel (pas d'insertion en file)
Étant donné que j'écoute le contenu C2 de ma file [C1, C2, C3, C4, C5]
Et qu'une notification géolocalisée "Tour Eiffel" est déclenchée
Quand je valide la notification
Et que le décompte de 5s se termine
Alors le contenu "Tour Eiffel" remplace C2 et démarre
Et C2 est sauvegardé dans l'historique de navigation
Et la file reste [C3, C4, C5] (pas de contenu retiré)
Et quand "Tour Eiffel" se termine, C3 démarre
Scénario: Invalidation après déplacement exactement 10km
Étant donné que la file a été calculée à une position donnée
Quand je me déplace d'exactement 10.0 km
Alors la file d'attente n'est PAS invalidée (seuil strict >10km)
Et les contenus en cache restent valides
Quand je me déplace de 10.1 km supplémentaires (total 10.1km)
Alors la file d'attente est invalidée
Et une nouvelle file est calculée
Scénario: Rafraîchissement exactement après 10 minutes
Étant donné qu'une file a été calculée à 10:00:00
Quand l'heure actuelle est 10:10:00
Alors le timer de rafraîchissement expire
Et une nouvelle file de 5 contenus est recalculée
Et le timestamp "computed_at" est mis à jour
Scénario: Recalcul asynchrone non-bloquant
Étant donné qu'il reste 2 contenus dans la file
Et que j'appuie sur "Suivant"
Quand le recalcul asynchrone démarre
Alors la lecture du contenu actuel n'est pas interrompue
Et le recalcul se fait en arrière-plan
Et les nouveaux contenus sont ajoutés dès disponibles (< 500ms)
Et l'utilisateur ne perçoit aucune latence
Scénario: Notification basée sur ETA (pas distance fixe)
Étant donné qu'un contenu géolocalisé existe à un point GPS
Et que je roule à 130 km/h
Quand je suis à 252m du point (ETA = 7 secondes)
Alors une notification est envoyée
Quand je suis à 300m du point (ETA = 8 secondes)
Alors aucune notification n'est envoyée (ETA >7s)
Plan du Scénario: Différentes distances de déplacement et invalidation
Étant donné qu'une file a été calculée à une position donnée
Quand je me déplace de <distance> km
Alors la file est <action>
Exemples:
| distance | action |
| 5 | conservée |
| 9.9 | conservée |
| 10.0 | conservée |
| 10.1 | invalidée et recalculée |
| 15 | invalidée et recalculée |
| 50 | invalidée et recalculée |
Scénario: Quota de 6 contenus géolocalisés par heure
Étant donné que j'ai validé 6 notifications géolocalisées dans la dernière heure
Quand un 7ème contenu géolocalisé est détecté (ETA 7s)
Alors aucune notification n'est envoyée
Et le quota horaire est respecté
Scénario: Mode piéton - pas de notification avec compteur 7s
Étant donné que je suis en mode piéton (vitesse <5 km/h)
Et qu'un audio-guide géolocalisé existe à 150m
Quand je passe dans le rayon de 200m
Alors une notification push système est envoyée
Et aucun compteur 7s n'est affiché
Et je peux ouvrir l'app en tapant sur la notification

View File

@@ -0,0 +1,255 @@
# language: fr
Fonctionnalité: Lecture en boucle et enchaînement automatique
En tant qu'auditeur
Je veux que les contenus s'enchaînent automatiquement avec un délai paramétrable
Afin d'avoir une expérience fluide sans interruption
Contexte:
Étant donné que l'API RoadWave est disponible
Et qu'un utilisateur est connecté
Scénario: Passage automatique après 2 secondes (mode standard)
Étant donné que j'écoute un contenu "A" en mode standard
Quand la lecture se termine naturellement
Alors un timer de 2 secondes démarre
Et un overlay s'affiche: "Contenu suivant dans 2s..."
Et une barre de décompte visuelle s'affiche
Quand le timer atteint 0
Alors le contenu "B" démarre automatiquement
Et l'overlay disparaît
Scénario: Passage automatique après 1 seconde (mode Kids)
Étant donné que je suis en mode Kids
Et que j'écoute un contenu pour enfants
Quand la lecture se termine
Alors un timer de 1 seconde démarre
Et le message "Contenu suivant dans 1s..." s'affiche
Quand le timer expire
Alors le contenu suivant démarre automatiquement
Car l'attention des enfants est plus courte
Scénario: Passage immédiat après une radio live (0 seconde)
Étant donné que j'écoute une radio live
Quand le créateur arrête la diffusion
Alors le passage au contenu suivant est immédiat (0s de délai)
Et aucun overlay de décompte n'est affiché
Et la transition est fluide
Scénario: Annulation du passage automatique
Étant donné qu'un contenu se termine
Et que le timer de 2 secondes démarre
Quand je clique sur "Rester sur ce contenu" pendant le décompte
Alors le timer est annulé
Et le contenu actuel reste en pause à la fin
Et le contenu suivant n'est pas lancé
Scénario: Insertion de publicité pendant le délai de transition
Étant donné que j'ai écouté 4 contenus sans publicité
Et que le 5ème contenu se termine
Quand le délai de 2 secondes démarre
Alors une publicité s'insère dans la file d'attente
Et le message devient "Publicité (15s)"
Et la publicité démarre après les 2 secondes
Et elle ne coupe jamais un contenu en cours
Scénario: Fréquence de publicité paramétrable admin
Étant donné que la fréquence pub est configurée à "1/5 contenus"
Quand j'écoute 10 contenus
Alors 2 publicités sont insérées (après les contenus 5 et 10)
Étant donné que l'admin change la fréquence à "1/3 contenus"
Quand j'écoute 9 contenus
Alors 3 publicités sont insérées (après les contenus 3, 6 et 9)
Scénario: Publicité skippable après 5 secondes par défaut
Étant donné qu'une publicité de 30 secondes démarre
Et que le délai minimal de visionnage est configuré à 5 secondes
Quand j'écoute pendant 3 secondes
Alors le bouton "Passer" n'est pas encore visible
Quand j'atteins 5 secondes d'écoute
Alors le bouton "Passer" apparaît
Et je peux cliquer pour passer au contenu suivant
Scénario: Délai minimal de publicité paramétrable admin
Étant donné qu'une publicité démarre
Et que l'admin a configuré le délai à 10 secondes
Quand j'écoute pendant 9 secondes
Alors le bouton "Passer" n'est pas visible
Quand j'atteins 10 secondes
Alors le bouton "Passer" apparaît
Et je peux skipper la publicité
Scénario: Like et abonnement autorisés sur une publicité
Étant donné qu'une publicité est en lecture
Quand je clique sur le bouton cœur (véhicule arrêté)
Alors la publicité reçoit un like (+2% jauges tags pub)
Quand je clique sur "S'abonner" au créateur de la pub
Alors je suis abonné (+5% jauges tags créateur)
Et le créateur de pub bénéficie de l'engagement
Scénario: Métriques d'engagement publicité trackées
Étant donné qu'une publicité de 30s est diffusée à 100 auditeurs
Quand 40 auditeurs écoutent entièrement (30s)
Et que 50 auditeurs skippent après 10s
Et que 10 auditeurs skippent avant 5s
Alors les métriques sont:
| Métrique | Valeur |
| Taux d'écoute complète | 40% |
| Taux de skip après seuil | 50% |
| Taux de skip immédiat | 10% |
| Durée moyenne d'écoute | 18s |
Scénario: Message "Aucun contenu disponible" si file vide
Étant donné que la file d'attente est vide
Et qu'aucun contenu n'est disponible dans ma zone
Quand le contenu actuel se termine
Alors un message s'affiche: "Aucun contenu disponible dans cette zone"
Et une proposition apparaît: "Élargir la zone de recherche ?"
Et un bouton "Élargir" est disponible
Et la lecture se met en pause automatiquement
Scénario: Élargissement automatique de la zone de recherche
Étant donné que le message "Aucun contenu disponible" s'affiche
Quand je clique sur "Élargir la zone"
Alors l'algorithme relance une recherche avec rayon +50km
Et une notification "Recherche élargie à 50km" s'affiche
Et la file d'attente est recalculée
Et la lecture reprend automatiquement
Scénario: Refus d'élargissement laisse en pause
Étant donné que le message "Aucun contenu disponible" s'affiche
Quand je clique sur "Annuler"
Alors la lecture reste en pause
Et l'interface affiche "En attente de contenu"
Et je peux manuellement naviguer ou chercher du contenu
Scénario: Retry avec backoff exponentiel en cas d'échec réseau
Étant donné que le contenu suivant échoue au chargement
Quand la première tentative échoue
Alors le système retente après 1 seconde (backoff 1s)
Quand la 2ème tentative échoue
Alors le système retente après 2 secondes (backoff 2s)
Quand la 3ème tentative échoue
Alors le système retente après 4 secondes (backoff 4s)
Et après 3 échecs totaux, le système bascule en mode offline
Scénario: Basculement mode offline après 3 échecs réseau
Étant donné que j'ai eu 3 échecs de chargement consécutifs
Quand le 3ème échec se produit
Alors un message "Connexion instable, basculement mode offline" s'affiche
Et la lecture continue avec les contenus téléchargés uniquement
Et les contenus en ligne sont temporairement désactivés
Quand la connexion revient
Alors le mode en ligne est automatiquement rétabli
Scénario: Overlay de décompte avec barre visuelle
Étant donné qu'un contenu se termine
Quand le timer de 2 secondes démarre
Alors un overlay semi-transparent s'affiche en bas de l'écran
Et le texte "Contenu suivant dans 2s..." est visible
Et une barre de progression décroît de 100% à 0% en 2 secondes
Et la couleur de la barre passe de vert à orange
Et l'overlay disparaît automatiquement après le décompte
Scénario: Bouton "Rester sur ce contenu" pendant décompte
Étant donné que le décompte de 2 secondes est actif
Quand l'overlay s'affiche
Alors un bouton "Rester sur ce contenu" est visible
Et il est cliquable pendant les 2 secondes
Quand je clique dessus
Alors le timer est annulé immédiatement
Et l'overlay disparaît
Et le contenu actuel reste affiché en pause
Scénario: Pas d'interruption d'un contenu en cours
Étant donné que j'écoute un contenu de 10 minutes
Et que je suis à 5 minutes de lecture
Quand une publicité devrait s'insérer (fréquence 1/5)
Alors la publicité n'interrompt jamais le contenu en cours
Et elle attend la fin du contenu actuel
Et elle s'insère pendant le délai de transition (2s)
Scénario: Publicités uniquement pour utilisateurs gratuits
Étant donné que je suis un utilisateur gratuit
Quand j'écoute 5 contenus
Alors une publicité est insérée après le 5ème contenu
Étant donné que je passe en compte Premium
Quand j'écoute 100 contenus
Alors aucune publicité n'est insérée
Et l'enchaînement est direct (2s de transition seulement)
Scénario: Message clair pour l'utilisateur lors de la publicité
Étant donné qu'une publicité va démarrer
Quand le délai de transition démarre
Alors le message affiché est: "Publicité (15s)"
Et la durée totale de la pub est indiquée
Et l'utilisateur sait qu'il s'agit d'une pub
Et la transparence est maximale
Scénario: Transition fluide entre contenus sans coupure
Étant donné qu'un contenu se termine
Et que le suivant est pré-chargé en cache
Quand le timer de 2s expire
Alors la transition audio utilise un crossfade de 0.3s
Et il n'y a aucun blanc ou coupure
Et l'expérience est fluide
Scénario: Gestion des erreurs de chargement avec retry
Étant donné que le contenu suivant échoue au chargement
Quand la 1ère tentative échoue
Alors une notification "Chargement..." s'affiche
Et le système retente automatiquement
Quand la 2ème tentative réussit
Alors la lecture démarre normalement
Et aucune action utilisateur n'est requise
Scénario: Mode offline après échecs multiples
Étant donné que j'ai 50 contenus téléchargés en mode offline
Et que j'ai eu 3 échecs réseau consécutifs
Quand le mode offline s'active
Alors seuls les contenus téléchargés sont disponibles
Et un badge "Mode offline" s'affiche en haut de l'écran
Et la lecture continue sans interruption
Scénario: Compteur de contenus avant prochaine publicité
Étant donné que la fréquence pub est 1/5 contenus
Et que j'ai écouté 3 contenus depuis la dernière pub
Quand je consulte l'interface
Alors un indicateur discret affiche "2 contenus avant pub"
Et l'utilisateur sait quand attendre la prochaine publicité
Plan du Scénario: Délai de transition selon mode
Étant donné que je suis en mode <mode>
Quand un contenu se termine
Alors le délai de transition est <delai> secondes
Et le message affiché est <message>
Exemples:
| mode | delai | message |
| Standard | 2 | "Contenu suivant dans 2s..." |
| Kids | 1 | "Contenu suivant dans 1s..." |
| Live | 0 | (aucun message) |
Plan du Scénario: Fréquence d'insertion des publicités
Étant donné que la fréquence pub est configurée à <frequence>
Quand j'écoute <contenus> contenus
Alors <pubs> publicités sont insérées
Exemples:
| frequence | contenus | pubs |
| 1/3 | 6 | 2 |
| 1/3 | 9 | 3 |
| 1/5 | 10 | 2 |
| 1/5 | 15 | 3 |
| 1/7 | 14 | 2 |
| 1/7 | 21 | 3 |
Plan du Scénario: Backoff exponentiel retry
Étant donné que le chargement échoue
Quand je suis à la tentative <tentative>
Alors le délai de retry est <delai> secondes
Exemples:
| tentative | delai |
| 1 | 1 |
| 2 | 2 |
| 3 | 4 |

View File

@@ -0,0 +1,206 @@
# language: fr
Fonctionnalité: Partage de contenu
En tant qu'utilisateur de RoadWave
Je veux pouvoir partager du contenu audio
Afin de faire découvrir l'application à d'autres personnes
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté
# 15.1.1 - Bouton "Partager"
Scénario: Bouton partager disponible dans le player en lecture
Étant donné que le contenu "Balade à Paris" est en cours de lecture
Quand l'utilisateur consulte les contrôles du player
Alors le bouton "Partager" est visible
Scénario: Bouton partager disponible sur la page profil créateur
Étant donné que l'utilisateur consulte le profil de "@paris_stories"
Quand l'utilisateur consulte un contenu dans la liste
Alors le bouton "Partager" est disponible pour chaque contenu
Scénario: Bouton partager dans la liste de recherche
Étant donné que l'utilisateur effectue une recherche "voyage paris"
Quand l'utilisateur ouvre le menu contextuel d'un résultat
Alors l'option "Partager" est disponible
Scénario: Bouton partager dans l'historique personnel
Étant donné que l'utilisateur consulte son historique d'écoute
Quand l'utilisateur sélectionne un contenu de l'historique
Alors le bouton "Partager" est accessible
Plan du Scénario: Menu de partage avec options multiples
Étant donné que le contenu "<contenu>" est disponible
Quand l'utilisateur clique sur le bouton "Partager"
Alors le menu natif OS s'ouvre
Et les options suivantes sont disponibles:
| option |
| Copier le lien |
| WhatsApp |
| Email |
| SMS |
| Plus... |
Exemples:
| contenu |
| Balade à Paris |
| Secrets de Montmartre |
# 15.1.2 - Comportement du lien partagé
Scénario: Génération du lien de partage
Étant donné un contenu avec l'ID "content_12345"
Quand l'utilisateur copie le lien de partage
Alors le lien généré est "https://roadwave.fr/share/c/content_12345"
Scénario: Ouverture du lien partagé avec l'application installée (Deep link)
Étant donné que l'application RoadWave est installée sur l'appareil
Et qu'un lien "https://roadwave.fr/share/c/content_12345" est partagé
Quand l'utilisateur clique sur le lien
Alors l'application RoadWave s'ouvre automatiquement
Et le contenu "content_12345" commence à jouer
Scénario: Ouverture du lien partagé sans l'application installée (Web player)
Étant donné que l'application RoadWave n'est pas installée
Et qu'un lien "https://roadwave.fr/share/c/content_12345" est partagé
Quand l'utilisateur clique sur le lien
Alors une page web responsive s'affiche
Et le web player HTML5 est visible
Et les boutons de téléchargement App Store et Google Play sont affichés
Scénario: Contenu de la page web de partage
Étant donné un contenu public avec les métadonnées suivantes:
| champ | valeur |
| titre | Balade à Paris |
| créateur | @paris_stories |
| durée | 12 min |
| écoutes | 2300 |
| localisation | Paris 5e |
| type_geo | Ancré |
| tags | Voyage, Histoire |
Quand la page de partage est affichée
Alors la page contient:
| élément |
| Cover image 16:9 |
| Titre "Balade à Paris" |
| "@paris_stories" |
| "12 min · 🎧 2.3K" |
| "📍 Paris 5e · Ancré" |
| "🏷 #Voyage #Histoire" |
| Description |
| Player HTML5 |
| Bouton App Store |
| Bouton Google Play |
Scénario: Métadonnées Open Graph pour partage social
Étant donné un contenu "Balade à Paris" par "@paris_stories"
Quand la page de partage est générée
Alors les métadonnées Open Graph incluent:
| propriété | valeur |
| og:title | Balade à Paris - RoadWave |
| og:description | Écoutez ce contenu par @paris_stories |
| og:type | music.song |
| og:site_name | RoadWave |
| twitter:card | player |
Et l'aperçu s'affiche correctement sur WhatsApp
Et l'aperçu s'affiche correctement sur Facebook
Et l'aperçu s'affiche correctement sur Twitter
Plan du Scénario: Deep linking par plateforme
Étant donné que l'application RoadWave est installée sur <plateforme>
Et qu'un lien de partage est ouvert
Quand le système détecte l'application
Alors l'application s'ouvre via <mécanisme>
Exemples:
| plateforme | mécanisme |
| iOS | Universal Links |
| Android | App Links |
Scénario: Fallback URL scheme pour deep linking
Étant donné que les App Links ne fonctionnent pas
Quand le système tente d'ouvrir le contenu
Alors l'URL scheme "roadwave://content/content_12345" est utilisé
# 15.1.3 - Contenus Premium partagés
Scénario: Badge Premium visible sur le lien partagé
Étant donné un contenu Premium "Visite VIP Louvre"
Quand l'utilisateur non-premium clique sur le lien partagé
Alors la page web affiche le badge "👑 Contenu Premium"
Scénario: Preview 30 secondes d'un contenu Premium partagé
Étant donné un contenu Premium "Visite VIP Louvre" de 15 minutes
Et qu'un utilisateur non-premium ouvre le lien partagé
Quand le player démarre automatiquement
Alors l'audio joue pendant 30 secondes exactement
Et un fade out de 2 secondes est appliqué
Et un overlay "Contenu réservé Premium" s'affiche après 32 secondes
Scénario: Contenu de l'overlay paywall Premium
Étant donné qu'un contenu Premium a atteint la limite de 30 secondes
Quand l'overlay paywall s'affiche
Alors le texte suivant est visible:
"""
👑 Contenu réservé Premium
Profitez de ce contenu complet
et de milliers d'autres
sans publicité
[Passer Premium - 4.99/mois]
[Télécharger l'app]
"""
Scénario: Actions disponibles sur l'overlay Premium
Étant donné que l'overlay paywall Premium est affiché
Quand l'utilisateur consulte les options
Alors les actions suivantes sont disponibles:
| action | comportement |
| Passer Premium | Redirection vers paiement Mangopay web |
| Télécharger l'app | Redirection vers App Store/Google Play |
| Rejouer les 30 premières sec | Relecture illimitée du preview |
Scénario: Relecture illimitée du preview Premium
Étant donné un contenu Premium partagé
Et que l'utilisateur a écouté les 30 premières secondes
Quand l'utilisateur clique sur "Rejouer"
Alors les 30 premières secondes sont rejouées
Et cette action est possible de manière illimitée
Scénario: Tracking des partages Premium
Étant donné un créateur "@guide_louvre" avec un contenu Premium
Quand son contenu est partagé
Alors les métriques suivantes sont enregistrées:
| métrique | valeur |
| Partages Premium | +1 |
| Ouvertures lien | compteur |
| Conversions Premium | si souscription |
Scénario: Rémunération créateur sur conversion Premium via partage
Étant donné un contenu Premium partagé par "@guide_louvre"
Quand un utilisateur s'abonne via le lien partagé
Alors le créateur reçoit 70% des revenus de cet abonnement
Et la conversion est trackée dans son dashboard
# Cas d'erreur
Scénario: Partage d'un contenu supprimé
Étant donné qu'un lien de partage "https://roadwave.fr/share/c/deleted_content" est ouvert
Et que le contenu n'existe plus
Quand la page web se charge
Alors un message "Ce contenu n'est plus disponible" s'affiche
Et les boutons de téléchargement de l'app sont affichés
Scénario: Partage d'un contenu en attente de modération
Étant donné un contenu en cours de validation modération
Quand un lien de partage est ouvert
Alors le message "Ce contenu est en cours de validation" s'affiche
Scénario: Génération du lien hors connexion
Étant donné que l'utilisateur n'a pas de connexion réseau
Quand l'utilisateur tente de partager un contenu
Alors le lien est copié dans le presse-papiers
Et un message "Lien copié (nécessite connexion pour ouvrir)" s'affiche

View File

@@ -0,0 +1,293 @@
# language: fr
Fonctionnalité: Profil créateur
En tant qu'utilisateur de RoadWave
Je veux consulter les profils des créateurs
Afin de découvrir leur contenu et décider de m'abonner
Contexte:
Étant donné que l'application RoadWave est démarrée
# 15.2.1 - Structure de la page profil
Scénario: URL du profil créateur
Étant donné un créateur avec le pseudo "paris_stories"
Quand l'utilisateur accède au profil
Alors l'URL est "https://roadwave.fr/@paris_stories"
Scénario: Informations principales du profil
Étant donné un créateur "@paris_stories" avec les informations suivantes:
| champ | valeur |
| photo | avatar_120x120.jpg |
| pseudo | paris_stories |
| badge_vérifié | true |
| bio | Histoires et anecdotes de Paris |
| abonnés | 1200 |
| contenus | 42 |
| durée_totale | 18h |
| écoutes_totales | 54000 |
Quand le profil est affiché
Alors les éléments suivants sont visibles:
| élément | valeur affichée |
| Photo profil | 120×120 px |
| @pseudo | @paris_stories |
| Badge vérifié | |
| Bio | Histoires et... |
| Nombre abonnés | 1.2K abonnés |
| Nombre contenus | 42 contenus |
| Durée totale | 18h de contenu créé |
| Écoutes totales | 54K écoutes totales |
Plan du Scénario: Arrondi des statistiques publiques
Étant donné un créateur avec <valeur_exacte> <métrique>
Quand le profil est affiché
Alors la valeur affichée est "<valeur_affichée>"
Exemples:
| métrique | valeur_exacte | valeur_affichée |
| abonnés | 342 | 342 |
| abonnés | 1200 | 1.2K |
| abonnés | 54000 | 54K |
| abonnés | 1200000 | 1.2M |
| écoutes | 842 | 842 |
| écoutes | 5400 | 5.4K |
| écoutes | 142000 | 142K |
| écoutes | 2100000 | 2.1M |
| durée (heures) | 18 | 18h |
| durée (heures) | 142 | 142h |
Scénario: Bio avec markdown basique
Étant donné un créateur avec la bio suivante en markdown:
"""
**Histoires de Paris** - Découvrez la capitale
*Nouveau contenu chaque semaine*
https://paris-stories.fr
"""
Quand le profil est affiché
Alors le texte en gras "Histoires de Paris" est formaté
Et le texte en italique "Nouveau contenu chaque semaine" est formaté
Et le lien "https://paris-stories.fr" est cliquable
Scénario: Limitation de la bio à 300 caractères
Étant donné un créateur qui entre une bio de 350 caractères
Quand la bio est sauvegardée
Alors seuls les 300 premiers caractères sont conservés
Et un message "Maximum 300 caractères" s'affiche
Scénario: Boutons d'action principaux
Étant donné que l'utilisateur consulte un profil créateur
Quand la page est chargée
Alors les boutons suivants sont visibles:
| bouton | action |
| S'abonner | Abonnement au créateur |
| Partager profil | Menu de partage |
| | Menu contextuel |
Scénario: Menu contextuel du profil [•••]
Étant donné que l'utilisateur clique sur le bouton []
Quand le menu s'ouvre
Alors les options suivantes sont disponibles:
| option | description |
| Partager profil | Partager le lien du profil |
| Signaler profil | Signaler spam ou usurpation d'identité |
| Bloquer créateur | Masquer tous les contenus du créateur |
Scénario: Liste des contenus du créateur
Étant donné un créateur avec 3 contenus publiés
Quand le profil est affiché
Alors chaque contenu affiche:
| élément | exemple |
| Cover image | Image 16:9 |
| Titre | Balade à Paris |
| Durée et écoutes | 12 min · 🎧 2.3K |
| Localisation | 📍 Paris |
| Bouton lecture | |
Plan du Scénario: Options de tri des contenus
Étant donné un créateur avec 10 contenus publiés
Quand l'utilisateur sélectionne le tri "<option_tri>"
Alors les contenus sont triés par <critère>
Exemples:
| option_tri | critère |
| Plus récents | Date publication DESC (défaut) |
| Plus populaires | Écoutes × facteur temporel (90 jours) |
| Plus anciens | Date publication ASC |
Scénario: Filtrage des contenus par tag
Étant donné un créateur avec des contenus taggés "Voyage", "Histoire", "Gastronomie"
Quand l'utilisateur filtre par tags "Voyage, Histoire"
Alors seuls les contenus avec ces tags sont affichés
Et le nombre de résultats est indiqué "12 contenus"
Scénario: Recherche locale dans le profil
Étant donné que l'utilisateur consulte le profil de "@paris_stories"
Et que le créateur a publié 50 contenus
Quand l'utilisateur entre "Montmartre" dans la barre de recherche
Alors la recherche s'effectue sur les titres et descriptions
Et seuls les contenus correspondants sont affichés
Et le placeholder indique "Rechercher dans les contenus de @paris_stories"
Scénario: Chargement paginé des contenus
Étant donné un créateur avec 100 contenus publiés
Quand le profil est affiché
Alors 20 contenus sont chargés initialement
Et un bouton "Charger plus" est visible en bas de page
Quand l'utilisateur clique sur "Charger plus"
Alors 20 contenus supplémentaires sont chargés
# 15.2.2 - Statistiques publiques
Scénario: Informations publiques visibles par tous
Étant donné que l'utilisateur consulte un profil créateur
Alors les informations suivantes sont publiques:
| information | visible |
| Photo et pseudo | |
| Badge vérifié | |
| Bio | |
| Nombre abonnés | |
| Nombre contenus | |
| Durée totale créée | |
| Écoutes totales | |
Scénario: Informations privées non visibles
Étant donné que l'utilisateur consulte un profil créateur
Alors les informations suivantes sont privées:
| information | visible |
| Liste des abonnés | |
| Revenus | |
| Localisation précise | |
| Email | |
Scénario: Dashboard créateur avec métriques privées
Étant donné que le créateur "@paris_stories" consulte son propre dashboard
Quand la page statistiques est affichée
Alors les métriques suivantes sont accessibles:
| métrique | type |
| Taux complétion moyen | 78% |
| Évolution abonnés | Graphique |
| Écoutes par contenu | Tableau |
| Revenus | Dashboard |
| Taux conversion Premium | Pourcentage |
| Démographie (âge/zone) | Agrégée |
Scénario: Graphique d'évolution des abonnés
Étant donné que le créateur consulte son dashboard
Quand il sélectionne la période "30 jours"
Alors un graphique d'évolution des abonnés est affiché
Et les périodes disponibles sont:
| période |
| 30j |
| 90j |
| 1 an |
Scénario: Tableau détaillé des écoutes par contenu
Étant donné un créateur avec 10 contenus publiés
Quand il consulte le tableau des performances
Alors chaque contenu affiche:
| métrique | exemple |
| Titre | Balade |
| Écoutes totales | 2300 |
| Écoutes complètes >80% | 1840 |
| Taux complétion | 80% |
| Likes | 420 |
| Partages | 56 |
# 15.2.3 - Badge vérifié
Scénario: Affichage du badge vérifié
Étant donné un créateur vérifié "@paris_stories"
Quand son profil est affiché
Alors le badge bleu "" est accolé au pseudo
Et un tooltip "Compte vérifié" s'affiche au survol
Scénario: Badge vérifié visible partout
Étant donné un créateur vérifié "@paris_stories"
Alors le badge "" est affiché dans:
| emplacement |
| Page profil |
| Player en lecture |
| Résultats de recherche|
| Notifications |
Plan du Scénario: Attribution automatique du badge selon critères
Étant donné un créateur avec <critère>
Quand les conditions sont validées
Alors le badge vérifié est attribué <automatique>
Exemples:
| critère | automatique |
| KYC Mangopay validé | Oui |
| 10K abonnés + compte >6 mois | Oui |
| Célébrité / Média officiel | Manuel |
Scénario: Attribution automatique via KYC
Étant donné un créateur qui complète son KYC Mangopay
Quand les documents sont validés
Alors le badge vérifié est attribué automatiquement
Et une notification "Votre compte est maintenant vérifié " est envoyée
Scénario: Attribution automatique à 10K abonnés
Étant donné un créateur avec 9999 abonnés et un compte de 7 mois
Quand il atteint 10000 abonnés
Alors le badge vérifié est attribué automatiquement
Et une notification de félicitations est envoyée
Scénario: Demande manuelle de vérification (célébrité)
Étant donné un créateur reconnu publiquement
Quand il soumet le formulaire de demande de vérification
Alors une requête est créée pour l'équipe RoadWave
Et l'équipe vérifie l'identité sous 48-72h
Et le badge est attribué si validation réussie
Scénario: Retrait du badge en cas de suspension
Étant donné un créateur vérifié avec le badge ""
Quand sa monétisation est suspendue
Alors le badge vérifié est retiré temporairement
Et le badge est restauré après levée de la suspension
Scénario: Retrait définitif du badge pour strikes multiples
Étant donné un créateur vérifié avec 3 strikes actifs
Quand un 4ème strike est appliqué (ban)
Alors le badge vérifié est retiré définitivement
Et le compte est banni
Scénario: Retrait du badge pour usurpation d'identité
Étant donné un créateur vérifié qui usurpe l'identité d'une célébrité
Quand la fraude est détectée
Alors le badge est retiré immédiatement
Et le compte est banni
Et une enquête est ouverte
# Cas d'erreur et limites
Scénario: Profil créateur supprimé
Étant donné qu'un utilisateur tente d'accéder à "@deleted_user"
Quand la page est chargée
Alors un message "Ce profil n'existe pas ou a été supprimé" s'affiche
Scénario: Blocage d'un créateur
Étant donné que l'utilisateur bloque le créateur "@spam_account"
Quand l'utilisateur consulte son flux de recommandations
Alors aucun contenu de "@spam_account" n'est affiché
Et le créateur n'apparaît plus dans les recherches
Scénario: Déblocage d'un créateur
Étant donné que l'utilisateur a bloqué "@paris_stories"
Quand il accède à ses paramètres "Comptes bloqués"
Et qu'il débloque "@paris_stories"
Alors les contenus du créateur réapparaissent dans les recommandations
Scénario: Signalement d'un profil pour spam
Étant donné que l'utilisateur signale le profil "@spam_account"
Quand il sélectionne la raison "Spam"
Alors le signalement est envoyé à la modération
Et un message de confirmation s'affiche
Et le profil reste visible jusqu'à décision de modération
Scénario: Signalement pour usurpation d'identité
Étant donné que l'utilisateur signale le profil "@fake_celebrity"
Quand il sélectionne "Usurpation d'identité"
Et qu'il fournit une preuve
Alors le signalement est priorisé (priorité HAUTE)
Et l'équipe modération traite sous 24h

View File

@@ -0,0 +1,472 @@
# language: fr
Fonctionnalité: Recherche de contenu
En tant qu'utilisateur de RoadWave
Je veux rechercher des contenus audio par mots-clés, localisation et filtres
Afin de trouver facilement le contenu qui m'intéresse
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté
# 15.3.1 - Recherche par mot-clé
Scénario: Recherche full-text basique
Étant donné que la base contient les contenus suivants:
| titre | description | créateur |
| Balade à Paris | Visite du quartier Latin | @paris_stories |
| Secrets de Montmartre | Histoire de la butte | @explore_paris |
| Voyage en Normandie | Découverte des plages | @voyages_fr |
Quand l'utilisateur recherche "paris"
Alors 2 résultats sont retournés
Et les résultats incluent "Balade à Paris"
Et les résultats incluent "Secrets de Montmartre"
Scénario: Recherche avec stemming français
Étant donné un contenu avec le titre "Voyage en Bretagne"
Quand l'utilisateur recherche "voyages"
Alors le contenu "Voyage en Bretagne" est trouvé
Et le stemming a transformé "voyages" en racine "voyag"
Plan du Scénario: Stemming français sur différentes formes
Étant donné un contenu avec le mot "<mot_original>"
Quand l'utilisateur recherche "<recherche>"
Alors le contenu est trouvé grâce au stemming français
Exemples:
| mot_original | recherche |
| voyage | voyages |
| voyager | voyage |
| balades | balade |
| historique | histoire |
Scénario: Recherche avec accents ignorés
Étant donné un contenu avec le titre "Découverte de l'Élysée"
Quand l'utilisateur recherche "decouverte elysee"
Alors le contenu est trouvé
Et les accents sont normalisés automatiquement
Scénario: Champs indexés avec pondération
Étant donné les contenus suivants:
| titre | description | créateur | tags |
| Voyage Paris | Balade sympa | @user1 | Tourisme |
| Balade Lyon | Voyage en ville | @paris_guide | Voyage |
Quand l'utilisateur recherche "paris"
Alors "Voyage Paris" est en première position
Parce que le titre a un poids × 3
Et "@paris_guide" apparaît en second
Parce que le créateur a un poids × 2
Scénario: Ranking par pertinence et popularité
Étant donné les contenus suivants:
| titre | écoutes | rang_texte |
| Balade Paris | 50000 | 0.8 |
| Paris la nuit | 1000 | 0.9 |
Quand l'utilisateur recherche "paris"
Alors le score final combine rang_texte × (1 + log(écoutes + 1))
Et "Balade Paris" est mieux classé grâce à sa popularité
Scénario: Autocomplete pendant la frappe
Étant donné que l'utilisateur commence à taper "par"
Quand 3 caractères sont saisis
Alors des suggestions apparaissent:
| suggestion |
| paris |
| parc naturel |
| parvis notre-dame |
Et le top 5 des suggestions est affiché
Scénario: Historique des 10 dernières recherches
Étant donné que l'utilisateur a effectué les recherches suivantes:
| recherche | date |
| voyage paris | 2026-01-20 |
| audio-guide louvre | 2026-01-19 |
| podcast automobile | 2026-01-18 |
Quand l'utilisateur ouvre la barre de recherche
Alors les 10 dernières recherches sont affichées
Et elles sont triées par date décroissante
Scénario: Correction automatique si aucun résultat
Étant donné que l'utilisateur recherche "ballade paris" (faute d'orthographe)
Et qu'aucun résultat n'est trouvé
Quand la page de résultats s'affiche
Alors une suggestion "Essayez plutôt : balade paris" est affichée
Scénario: Recherches populaires suggérées
Étant donné qu'aucun résultat n'est trouvé pour une recherche
Quand la page s'affiche
Alors des suggestions populaires sont affichées:
| suggestion |
| balade paris |
| audio-guide louvre |
| visite montmartre |
# 15.3.2 - Recherche géographique
Scénario: Saisie d'un lieu avec autocomplete
Étant donné que l'utilisateur ouvre le filtre "Lieu"
Quand il tape "Louv"
Alors Nominatim retourne des suggestions:
| suggestion | type |
| Musée du Louvre, Paris | monument |
| Louvres, Val-d'Oise | commune |
Scénario: Sélection d'un lieu et définition du rayon
Étant donné que l'utilisateur sélectionne "Paris, France"
Et que les coordonnées sont (48.8566, 2.3522)
Quand il définit un rayon de 50 km
Alors la recherche PostGIS utilise ST_DWithin avec 50000 mètres
Plan du Scénario: Recherche géographique avec différents rayons
Étant donné un contenu à 30 km de Paris
Quand l'utilisateur recherche autour de Paris avec un rayon de <rayon>
Alors le contenu est <résultat>
Exemples:
| rayon | résultat |
| 20 km | non trouvé |
| 50 km | trouvé |
| 100 km | trouvé |
Scénario: Utilisation de "Autour de moi" (GPS actuel)
Étant donné que l'utilisateur active le GPS
Et que sa position est (48.8566, 2.3522)
Quand il sélectionne "Autour de moi"
Alors la recherche utilise ses coordonnées GPS actuelles
Et un rayon par défaut de 10 km est appliqué
Scénario: Curseur de rayon avec limites
Étant donné que l'utilisateur ouvre le curseur de rayon
Quand il ajuste le curseur
Alors les valeurs disponibles vont de 5 km à 500 km
Et la valeur s'affiche en temps réel "50 km"
Scénario: Affichage de la distance dans les résultats
Étant donné une recherche géographique autour de Paris
Et un contenu à 2.3 km de distance
Quand les résultats sont affichés
Alors la distance "À 2.3 km" est indiquée pour chaque résultat
Plan du Scénario: Tri par proximité géographique
Étant donné des contenus à différentes distances de Paris:
| contenu | distance |
| Louvre Guide | 0.5 km |
| Tour Eiffel | 2.0 km |
| Versailles | 20 km |
Quand l'utilisateur trie par "Proximité"
Alors les résultats sont affichés dans l'ordre:
| position | contenu |
| 1 | Louvre Guide |
| 2 | Tour Eiffel |
| 3 | Versailles |
Scénario: Géocodage avec Nominatim (MVP)
Étant donné que l'application est en phase MVP
Quand une requête de géocodage est effectuée
Alors l'API publique Nominatim est utilisée
Et le rate limit de 1 req/s est respecté
Scénario: Géocodage avec fallback Mapbox
Étant donné que Nominatim ne retourne aucun résultat
Quand l'application tente un fallback
Alors l'API Mapbox Geocoding est utilisée
Et le coût de 0.50 / 1000 requêtes est appliqué
# 15.3.3 - Filtres avancés
Scénario: Ouverture du panneau de filtres
Étant donné que l'utilisateur est sur la page de recherche
Quand il clique sur "Filtres"
Alors un panneau latéral s'ouvre
Et 7 catégories de filtres sont affichées:
| catégorie |
| Type de contenu |
| Durée |
| Classification âge |
| Géo-pertinence |
| Tags |
| Date de publication |
| Abonnement |
Scénario: Filtre par type de contenu (multi-sélection)
Étant donné que l'utilisateur ouvre les filtres
Quand il sélectionne:
| type |
| Contenu court |
| Audio-guide |
Alors seuls ces types de contenus sont recherchés
Et les podcasts et radios live sont exclus
Plan du Scénario: Filtre par durée
Étant donné un contenu de <durée> minutes
Quand l'utilisateur filtre par "<tranche>"
Alors le contenu est <résultat>
Exemples:
| durée | tranche | résultat |
| 3 | <5 min | trouvé |
| 3 | 5-15 min | non trouvé |
| 10 | 5-15 min | trouvé |
| 20 | 15-30 min | trouvé |
| 45 | >30 min | trouvé |
Scénario: Filtre par classification âge
Étant donné des contenus avec différentes classifications:
| contenu | classification |
| Conte enfants | Tout public |
| Podcast news | 13+ |
| Débat politique | 16+ |
Quand l'utilisateur filtre "Tout public"
Alors seul "Conte enfants" est affiché
Scénario: Filtre par géo-pertinence
Étant donné des contenus avec différents types géo:
| contenu | type_geo |
| Guide Louvre | Ancré |
| Podcast Paris | Contextuel |
| News nationales | Neutre |
Quand l'utilisateur filtre "Ancré, Contextuel"
Alors "Guide Louvre" et "Podcast Paris" sont affichés
Et "News nationales" est exclu
Scénario: Filtre par tags (multi-sélection)
Étant donné des contenus taggés:
| contenu | tags |
| Voyage en Italie | Voyage, Gastronomie |
| Histoire de Rome | Voyage, Histoire |
| Économie italienne | Économie |
Quand l'utilisateur sélectionne les tags "Voyage, Histoire"
Alors "Histoire de Rome" est en priorité (2 tags correspondants)
Et "Voyage en Italie" est affiché (1 tag correspondant)
Et "Économie italienne" est exclu
Plan du Scénario: Filtre par date de publication
Étant donné un contenu publié il y a <délai>
Quand l'utilisateur filtre par "<période>"
Alors le contenu est <résultat>
Exemples:
| délai | période | résultat |
| 12 heures | Dernières 24h | trouvé |
| 3 jours | Cette semaine | trouvé |
| 15 jours | Ce mois | trouvé |
| 8 mois | Cette année | trouvé |
| 2 ans | Toutes dates | trouvé |
| 2 ans | Cette année | non trouvé |
Scénario: Filtre par type d'abonnement
Étant donné des contenus gratuits et Premium:
| contenu | type |
| Balade Paris | Gratuit |
| Visite VIP Louvre | Premium |
Quand l'utilisateur filtre "Premium uniquement 👑"
Alors seul "Visite VIP Louvre" est affiché
Scénario: Combinaison de filtres multiples (AND logic)
Étant donné que l'utilisateur applique les filtres:
| filtre | valeur |
| Type | Audio-guide |
| Durée | 5-15 min |
| Tags | Voyage |
| Classification | Tout public |
Quand la recherche est lancée
Alors seuls les contenus respectant TOUS les critères sont affichés
Scénario: Réinitialisation des filtres
Étant donné que l'utilisateur a appliqué 5 filtres différents
Quand il clique sur "Réinitialiser"
Alors tous les filtres sont désactivés
Et la recherche affiche tous les résultats
Scénario: Sauvegarde d'une recherche
Étant donné que l'utilisateur a appliqué plusieurs filtres
Quand il clique sur "💾 Sauvegarder cette recherche"
Et qu'il entre le nom "Podcasts voyage Paris"
Alors la recherche est sauvegardée
Et elle apparaît dans l'onglet "Recherches sauvegardées"
Scénario: Limite de 5 recherches sauvegardées
Étant donné que l'utilisateur a déjà 5 recherches sauvegardées
Quand il tente de sauvegarder une 6ème recherche
Alors un message d'erreur s'affiche
Et il doit supprimer une recherche existante avant d'en ajouter une nouvelle
Scénario: Notifications pour recherches sauvegardées
Étant donné une recherche sauvegardée "Podcasts voyage Paris"
Et que l'utilisateur a activé les notifications
Quand 3 nouveaux contenus correspondants sont publiés
Alors une notification "3 nouveaux contenus dans 'Podcasts voyage Paris'" est envoyée
Plan du Scénario: Options de tri des résultats
Étant donné une recherche avec plusieurs résultats
Quand l'utilisateur sélectionne le tri "<option>"
Alors les résultats sont triés selon <algorithme>
Exemples:
| option | algorithme |
| Pertinence | Score recherche × (1 + log(écoutes + 1)) |
| Popularité | Écoutes complètes derniers 30j DESC |
| Récent | Date publication DESC |
| Proximité | Distance GPS ASC (si recherche géo) |
| Durée | Durée audio ASC ou DESC |
# 15.3.4 - Page de résultats
Scénario: Structure d'un résultat de recherche
Étant donné un résultat de recherche
Quand la page est affichée
Alors chaque résultat contient:
| élément | exemple |
| Cover image | 120×68 px (16:9) |
| Titre | Balade à Paris (2 lignes max) |
| Créateur | @paris_stories |
| Durée | 12 min |
| Écoutes | 🎧 2.3K |
| Localisation | 📍 Paris 5e · Ancré |
| Tags | 🏷 #Voyage #Histoire |
| Badge Premium | 👑 (si applicable) |
| Distance | À 2.3 km (si recherche géo) |
| Bouton lecture | Écouter |
| Menu contextuel | |
Scénario: Lazy loading des images
Étant donné une page avec 20 résultats de recherche
Quand la page se charge
Alors seules les 5 premières images sont chargées
Et les images suivantes se chargent au scroll
Scénario: Troncature du titre sur 2 lignes maximum
Étant donné un contenu avec un titre de 120 caractères
Quand le résultat est affiché
Alors le titre est tronqué après 2 lignes
Et "..." est ajouté à la fin
Scénario: Lien cliquable vers le profil créateur
Étant donné un résultat de recherche pour "@paris_stories"
Quand l'utilisateur clique sur "@paris_stories"
Alors il est redirigé vers "https://roadwave.fr/@paris_stories"
Scénario: Menu contextuel d'un résultat [⋮]
Étant donné que l'utilisateur clique sur [] pour un résultat
Quand le menu s'ouvre
Alors les actions suivantes sont disponibles:
| action |
| Partager |
| Ajouter à une playlist |
| Télécharger (offline) |
| Signaler |
Scénario: Pagination avec 20 résultats par page
Étant donné une recherche retournant 100 résultats
Quand la page est affichée
Alors 20 résultats sont chargés initialement
Et un indicateur "1-20 sur 100 résultats" est visible
Scénario: Infinite scroll automatique
Étant donné que l'utilisateur scroll dans les résultats
Quand il atteint 80% de la page
Alors les 20 résultats suivants sont chargés automatiquement
Et un loader est affiché pendant le chargement
Scénario: Bouton fallback "Charger 20 suivants"
Étant donné que l'infinite scroll est désactivé (paramètres)
Quand l'utilisateur atteint la fin de la page
Alors un bouton "Charger 20 suivants" est affiché
Et les résultats se chargent au clic
# Vue carte
Scénario: Basculement entre vue liste et vue carte
Étant donné que l'utilisateur est sur la page de résultats
Quand il clique sur le toggle "Liste / Carte"
Alors la vue carte Leaflet s'affiche
Et les résultats sont affichés comme markers sur la carte
Scénario: Affichage de la carte Leaflet
Étant donné que la vue carte est activée
Quand la carte se charge
Alors la carte utilise les tuiles OpenStreetMap
Et le centre est la position de recherche (ou GPS utilisateur)
Et le zoom initial montre tous les résultats
Scénario: Markers cliquables sur la carte
Étant donné que 10 résultats sont affichés sur la carte
Quand l'utilisateur clique sur un marker
Alors une popup s'affiche avec:
| élément |
| Titre |
| Créateur |
| Durée |
| Distance |
| Bouton Écouter|
Scénario: Clustering des markers proches
Étant donné que 50 résultats sont très proches géographiquement
Quand la carte est affichée
Alors les markers proches sont groupés en clusters
Et le nombre de contenus est affiché sur le cluster
Et le cluster se décompose au zoom
Scénario: Synchronisation liste / carte
Étant donné que l'utilisateur est en vue carte
Quand il clique sur un marker et écoute le contenu
Et qu'il rebascule en vue liste
Alors le contenu écouté est marqué dans la liste
Et la position de scroll est maintenue
# Performances et index
Scénario: Index PostgreSQL full-text pour performances
Étant donné que la base contient 100K contenus
Quand une recherche full-text est effectuée
Alors l'index GIN sur to_tsvector est utilisé
Et la requête retourne en moins de 100ms
Scénario: Index PostGIS GIST pour recherche géo
Étant donné une recherche géographique avec rayon 50 km
Quand la requête PostGIS ST_DWithin est exécutée
Alors l'index GIST sur la colonne location est utilisé
Et la requête retourne en moins de 50ms
Scénario: Index composites pour filtres
Étant donné une recherche avec filtres multiples
Quand les filtres type, durée, âge, géo, date sont appliqués
Alors l'index composite idx_content_filters est utilisé
Et les performances restent optimales
Scénario: Index GIN pour recherche par tags
Étant donné une recherche filtrée par tags "Voyage, Histoire"
Quand la requête est exécutée
Alors l'index GIN sur la colonne tags est utilisé
Et la recherche est performante même avec 500K contenus
# Cas d'erreur
Scénario: Aucun résultat trouvé
Étant donné que l'utilisateur recherche "xyzabc123"
Quand aucun résultat n'est trouvé
Alors un message "Aucun résultat pour 'xyzabc123'" s'affiche
Et des suggestions de recherches populaires sont proposées
Scénario: Recherche vide
Étant donné que l'utilisateur clique sur "Rechercher" sans saisir de texte
Quand la recherche est lancée
Alors un message "Veuillez entrer au moins 2 caractères" s'affiche
Scénario: Erreur de géocodage Nominatim
Étant donné que l'API Nominatim est indisponible
Quand l'utilisateur tente une recherche géographique
Alors un message "Service de localisation temporairement indisponible" s'affiche
Et la recherche continue sans filtre géographique
Scénario: GPS désactivé pour "Autour de moi"
Étant donné que l'utilisateur a désactivé le GPS
Quand il sélectionne "Autour de moi"
Alors un message "Veuillez activer la localisation" s'affiche
Et un bouton "Activer" ouvre les paramètres système
Scénario: Timeout de recherche après 10 secondes
Étant donné qu'une recherche complexe est lancée
Quand la requête dépasse 10 secondes
Alors la recherche est annulée
Et un message "La recherche a pris trop de temps, veuillez réessayer" s'affiche