refactor(docs): réorganiser la documentation selon principes DDD

Réorganise la documentation du projet selon les principes du Domain-Driven Design (DDD) pour améliorer la cohésion, la maintenabilité et l'alignement avec l'architecture modulaire du backend.

**Structure cible:**
```
docs/domains/
├── README.md (Context Map)
├── _shared/ (Core Domain)
├── recommendation/ (Supporting Subdomain)
├── content/ (Supporting Subdomain)
├── moderation/ (Supporting Subdomain)
├── advertising/ (Generic Subdomain)
├── premium/ (Generic Subdomain)
└── monetization/ (Generic Subdomain)
```

**Changements effectués:**

Phase 1: Création de l'arborescence des 7 bounded contexts
Phase 2: Déplacement des règles métier (01-19) vers domains/*/rules/
Phase 3: Déplacement des diagrammes d'entités vers domains/*/entities/
Phase 4: Déplacement des diagrammes flux/états/séquences vers domains/*/
Phase 5: Création des README.md pour chaque domaine
Phase 6: Déplacement des features Gherkin vers domains/*/features/
Phase 7: Création du Context Map (domains/README.md)
Phase 8: Mise à jour de mkdocs.yml pour la nouvelle navigation
Phase 9: Correction automatique des liens internes (script fix-markdown-links.sh)
Phase 10: Nettoyage de l'ancienne structure (regles-metier/, diagrammes/, features/)

**Configuration des tests:**
- Makefile: godog run docs/domains/*/features/
- scripts/generate-bdd-docs.py: features_dir → docs/domains

**Avantages:**
 Cohésion forte: toute la doc d'un domaine au même endroit
 Couplage faible: domaines indépendants, dépendances explicites
 Navigabilité améliorée: README par domaine = entrée claire
 Alignement code/docs: miroir de backend/internal/
 Onboarding facilité: exploration domaine par domaine
 Tests BDD intégrés: features au plus près des règles métier

Voir docs/REFACTOR-DDD.md pour le plan complet.
This commit is contained in:
jpgiannetti
2026-02-07 17:15:02 +01:00
parent 78422bb2c0
commit 5e5fcf4714
227 changed files with 1413 additions and 1967 deletions

View File

@@ -0,0 +1,263 @@
# language: fr
Fonctionnalité: Audio-guides multi-séquences pour piétons
En tant qu'auditeur à pied
Je veux profiter d'audio-guides structurés lors de mes visites
Afin de découvrir des lieux de manière autonome et à mon rythme
Contexte:
Étant donné que l'API RoadWave est disponible
Et que je suis connecté en tant qu'auditeur
Et que je suis en mode piéton (vitesse <5 km/h)
Scénario: Détection d'audio-guide à proximité
Étant donné que je me trouve à 80 mètres du Musée du Louvre
Et que 3 audio-guides sont disponibles pour ce lieu
Quand le système détecte ma position
Alors je reçois une notification push:
"""
📍 Audio-guide disponible : Musée du Louvre
Choisissez parmi 3 guides pour Musée du Louvre
Tap pour explorer
"""
Scénario: Rayon de détection de 100m
Étant donné qu'un audio-guide est centré aux coordonnées GPS du Louvre
Quand je suis à exactement 100m du centre
Alors la notification est déclenchée
Et quand je suis à 101m, aucune notification n'est envoyée
Scénario: Page de sélection des audio-guides
Étant donné que j'ai tapé sur la notification audio-guide
Quand la page de sélection s'affiche
Alors je vois une liste de guides disponibles:
| titre | créateur | nb_sequences | durée | note | écoutes |
| Visite complète | Créateur A | 12 | 45 min | 4.8 | 1.2K |
| Œuvres majeures | Créateur B | 5 | 20 min | 4.9 | 3.5K |
| Visite famille | Créateur C | 8 | 30 min | 4.7 | 850 |
Scénario: Sélection d'un audio-guide
Étant donné que je suis sur la page de sélection
Quand je tape sur "Visite complète (45 min)"
Alors l'interface de lecture d'audio-guide s'ouvre
Et la séquence 1 commence automatiquement
Et je vois la liste complète des 12 séquences
Scénario: Interface de lecture audio-guide
Étant donné que j'ai sélectionné un audio-guide de 12 séquences
Quand l'interface s'affiche
Alors je vois:
| élément | exemple |
| Titre guide | 🎨 Visite complète Musée du Louvre |
| Piste actuelle | Piste 2/12 |
| Titre séquence | "La Joconde - Histoire et mystères" |
| Barre de progression | 3:24 / 6:50 |
| Liste séquences | 1. Intro, 2. Joconde, 3. Vénus... |
| Boutons navigation | Précédent, Play/Pause, Suivant |
Scénario: Navigation vers séquence suivante
Étant donné que j'écoute la séquence 2
Quand je tape sur "Suivant"
Alors la séquence 3 commence immédiatement
Et le titre de la séquence s'affiche: "Vénus de Milo"
Et la barre de progression se réinitialise
Scénario: Navigation vers séquence précédente
Étant donné que j'écoute la séquence 5
Quand je tape sur "Précédent"
Alors la séquence 4 recommence depuis le début
Et je peux réécouter cette séquence
Scénario: Saut direct à une séquence spécifique
Étant donné que j'écoute la séquence 2
Et que la liste des séquences est affichée
Quand je tape sur "7. Peintures Renaissance"
Alors la séquence 7 démarre immédiatement
Et je passe directement de la séquence 2 à la 7
Scénario: Commande vocale "Suivant"
Étant donné que j'écoute la séquence 3
Quand je dis "Suivant" via la commande vocale
Alors la séquence 4 démarre
Et la commande vocale fonctionne même si l'écran est verrouillé
Scénario: Commande vocale "Précédent"
Étant donné que j'écoute la séquence 6
Quand je dis "Précédent" via la commande vocale
Alors la séquence 5 démarre depuis le début
Scénario: Pause et reprise à la position exacte
Étant donné que j'écoute la séquence 4 à la position 2:30
Quand je mets en pause
Et que j'attends 5 minutes
Et que je reprends la lecture
Alors la séquence reprend exactement à 2:30
Et aucune donnée n'est perdue
Scénario: Guidage vocal automatique entre séquences
Étant donné que la séquence 2 se termine
Quand la transition vers la séquence 3 se produit
Alors j'entends un message vocal:
"""
Vous avez terminé la séquence 2. Dirigez-vous vers la Vénus de Milo pour la séquence 3.
"""
Et la séquence 3 ne démarre pas automatiquement (navigation manuelle)
Scénario: Avertissement si éloignement du point d'intérêt
Étant donné que je suis dans le guide du Louvre
Et que je devrais être devant la Vénus de Milo (séquence 3)
Quand je m'éloigne de plus de 50m de ce point
Alors j'entends un message vocal:
"""
Vous vous éloignez de la prochaine étape. Consultez le plan.
"""
Et un bouton "Voir le plan" apparaît dans l'interface
Scénario: Sauvegarde automatique de la progression
Étant donné que j'écoute la séquence 5 à la position 1:45
Quand je ferme l'application brutalement
Et que je la rouvre 10 minutes plus tard
Alors je vois une popup "Reprendre la visite du Musée du Louvre ?"
Et si je choisis "Reprendre", je retourne à la séquence 5 à 1:45
Scénario: Option de recommencer depuis le début
Étant donné que j'ai une progression sauvegardée à la séquence 7
Quand je rouvre le guide
Alors je vois 2 options:
| option | action |
| Reprendre à la séquence 7 | Reprend à la position exacte |
| Recommencer depuis le début | Retourne à la séquence 1 |
Scénario: Expiration de la sauvegarde après 30 jours
Étant donné que j'ai une progression sauvegardée depuis 30 jours
Quand j'essaie de reprendre le guide
Alors la sauvegarde est considérée comme expirée
Et je recommence depuis la séquence 1
Et je vois le message "Votre précédente visite date de plus de 30 jours. Recommençons depuis le début."
Scénario: Synchronisation multi-device de la progression
Étant donné que j'écoute un guide sur mon iPhone à la séquence 4
Quand je ferme l'app et ouvre sur mon iPad
Alors je vois la progression synchronisée
Et je peux reprendre à la séquence 4 sur l'iPad
Scénario: Marquage "Terminé" après toutes les séquences
Étant donné que j'écoute la dernière séquence (12/12)
Quand cette séquence se termine
Alors le guide est marqué " Terminé" dans mon historique
Et je vois un message de félicitation:
"""
Félicitations ! Vous avez terminé la visite complète du Musée du Louvre.
"""
Et le créateur gagne les statistiques d'écoute complète
Scénario: Création d'audio-guide par un créateur
Étant donné que je suis un créateur
Quand je crée un nouvel audio-guide
Alors je dois:
| étape | détail |
| Uploader plusieurs fichiers | 1 fichier MP3 par séquence |
| Numéroter les séquences | Séquence 1, Séquence 2, etc. |
| Titrer chaque séquence | "Introduction", "La Joconde", etc. |
| Définir point GPS unique | Centre du lieu (ex: Louvre) |
| Définir rayon de détection | Par défaut 100m |
Et la durée totale est calculée automatiquement
Scénario: Structure JSON de stockage audio-guide
Étant donné qu'un créateur publie un audio-guide du Louvre
Quand les métadonnées sont stockées en base
Alors le format JSON contient:
```json
{
"guide_id": "abc123",
"title": "Visite complète Musée du Louvre",
"location": {"lat": 48.8606, "lon": 2.3376, "radius": 200},
"sequences": [
{
"sequence_number": 1,
"title": "Introduction et architecture",
"audio_url": "https://cdn.../seq1.mp3",
"duration_seconds": 180
},
{
"sequence_number": 2,
"title": "La Joconde",
"audio_url": "https://cdn.../seq2.mp3",
"duration_seconds": 410
}
],
"total_duration_seconds": 2700,
"creator_id": "creator_xyz"
}
```
Scénario: Limitation du nombre de séquences
Étant donné que je crée un audio-guide
Quand j'essaie d'ajouter plus de 50 séquences
Alors je vois le message "Maximum 50 séquences par audio-guide"
Et je dois structurer mon contenu différemment ou créer plusieurs guides
Scénario: Quitter le guide et sauvegarder
Étant donné que j'écoute la séquence 6
Quand je tape sur le bouton "×" (fermer)
Alors je vois une confirmation:
"""
Voulez-vous quitter ce guide ?
Votre progression sera sauvegardée.
"""
Et si je confirme, la progression est enregistrée
Et je retourne à l'écran principal
Scénario: Statistiques créateur pour audio-guides
Étant donné que je suis créateur d'un audio-guide
Quand je consulte mes statistiques
Alors je vois:
| métrique | exemple valeur |
| Nombre de démarrages | 1250 |
| Nombre de complétions (100%) | 387 (31%) |
| Séquence la plus skippée | Séquence 8 |
| Durée moyenne d'écoute | 28 min (sur 45) |
Scénario: Audio-guide multilingue (post-MVP)
Étant donné qu'un créateur peut publier plusieurs versions linguistiques
Quand un touriste anglophone visite le Louvre
Alors il voit les guides disponibles en anglais
Et peut choisir parmi les guides traduits
Mais cette fonctionnalité n'est pas disponible en MVP
Scénario: Publicité entre séquences d'audio-guide
Étant donné que je suis un utilisateur gratuit
Et que j'écoute un audio-guide
Quand je passe de la séquence 5 à la séquence 6
Alors une publicité peut être insérée (1 pub toutes les 5 séquences)
Et la publicité est skippable après 5 secondes
Et les utilisateurs Premium ne voient pas de publicité
Scénario: Audio-guide en mode offline
Étant donné que j'ai téléchargé un audio-guide complet
Quand je visite le lieu sans connexion internet
Alors toutes les séquences sont disponibles hors ligne
Et la navigation fonctionne normalement
Et seule la sauvegarde cloud est différée jusqu'à reconnexion
Scénario: Notation d'un audio-guide après écoute
Étant donné que j'ai terminé un audio-guide
Quand je ferme l'interface
Alors je vois une popup "Notez cette visite"
Et je peux donner une note de 1 à 5 étoiles
Et cette note contribue à la note globale visible par les autres utilisateurs
Scénario: Filtrage par langue dans la page de sélection
Étant donné que plusieurs audio-guides sont disponibles en différentes langues
Quand j'accède à la page de sélection
Alors je peux filtrer par langue
Et par défaut, les guides dans ma langue système sont affichés en premier
Scénario: Réutilisation de l'infrastructure existante
Étant donné qu'un audio-guide est techniquement un contenu structuré
Alors il réutilise:
| composant | usage |
| OVH Object Storage | Hébergement fichiers MP3 séquences |
| Streaming HLS | Diffusion audio adaptative |
| Cache Redis | Métadonnées guides + progressions |
| PostgreSQL | Stockage structure JSON guides |
Et aucune infrastructure dédiée n'est nécessaire

View File

@@ -0,0 +1,146 @@
# language: fr
Fonctionnalité: Impact des abonnements sur l'algorithme
En tant qu'auditeur
Je veux que les contenus de mes créateurs suivis soient favorisés
Afin de ne pas rater leurs publications tout en découvrant de nouveaux contenus
Contexte:
Étant donné que l'API RoadWave est disponible
Et que je suis connecté en tant qu'auditeur
Et que je suis abonné au créateur "JeanDupont"
Scénario: Boost de +30% appliqué au score final
Étant donné un contenu du créateur "JeanDupont" avec:
| score_geo | 0.5 |
| score_interet | 0.6 |
| score_engage | 0.5 |
Quand le score final est calculé
Alors le score de base est 0.53
Et le boost abonnement de +30% est appliqué
Et le score final avec boost est 0.69
Scénario: Contenu non-suivi peut battre contenu suivi
Étant donné que je suis à Paris
Et que 2 contenus sont disponibles:
| contenu | createur_suivi | score_geo | score_interet | score_engage | score_final_base | score_avec_boost |
| Contenu A | Non | 0.9 | 0.8 | 0.7 | 0.80 | 0.80 |
| Contenu B | Oui | 0.5 | 0.6 | 0.5 | 0.53 | 0.69 |
Quand l'algorithme sélectionne le prochain contenu
Alors le Contenu A est proposé en premier
Car son score (0.80) est supérieur au Contenu B avec boost (0.69)
Scénario: Contenu suivi remporte grâce au boost
Étant donné que je suis à Paris
Et que 2 contenus sont disponibles:
| contenu | createur_suivi | score_final_base | score_avec_boost |
| Contenu A | Non | 0.70 | 0.70 |
| Contenu B | Oui | 0.60 | 0.78 |
Quand l'algorithme sélectionne le prochain contenu
Alors le Contenu B est proposé en premier
Car le boost fait pencher la balance (0.78 > 0.70)
Scénario: Contenu suivi avec faible engagement ne domine pas
Étant donné que je suis abonné au créateur "CreateurMoyen"
Et qu'il publie un contenu avec très faible engagement (score 0.30)
Et qu'un contenu viral d'un créateur non-suivi a un score de 0.85
Quand l'algorithme sélectionne le prochain contenu
Alors le contenu viral est proposé en premier (0.85)
Car même avec boost, le contenu suivi obtient seulement 0.39 (0.30 × 1.3)
Scénario: Pas de file dédiée aux abonnements
Étant donné que je suis abonné à 50 créateurs
Quand l'algorithme génère ma file d'attente de 5 contenus
Alors les contenus suivis et non-suivis sont mélangés
Et tous entrent en compétition selon leurs scores (avec boost si abonnement)
Et il n'y a pas de section séparée "Contenus de vos abonnements"
Scénario: Vérification du calcul du boost
Étant donné un contenu d'un créateur suivi
Et que le score final de base est calculé à 0.65
Quand le boost abonnement est appliqué
Alors le multiplicateur utilisé est exactement 1.3
Et le score final avec boost est 0.845 (0.65 × 1.3)
Et le résultat est arrondi à 2 décimales: 0.85
Scénario: Boost appliqué à tous les contenus du créateur suivi
Étant donné que je suis abonné au créateur "JeanDupont"
Et qu'il a publié 10 contenus différents
Quand l'algorithme évalue chacun de ces contenus
Alors le boost de +30% est appliqué à tous les 10 contenus
Et chaque contenu bénéficie du même multiplicateur 1.3
Scénario: Plusieurs créateurs suivis en compétition
Étant donné que je suis abonné à "Créateur A" et "Créateur B"
Et que les 2 ont des contenus disponibles dans ma zone:
| createur | score_base | score_avec_boost |
| Créateur A | 0.70 | 0.91 |
| Créateur B | 0.65 | 0.85 |
Quand l'algorithme sélectionne le prochain contenu
Alors le contenu du Créateur A est proposé en premier (0.91 > 0.85)
Et les 2 bénéficient du boost, mais le meilleur score gagne
Scénario: Contenu national d'un créateur suivi
Étant donné que je suis abonné à "MediaNational"
Et qu'il publie un contenu de type "National" (score_geo 0.2)
Quand le score est calculé avec:
| score_geo | score_interet | score_engage |
| 0.2 | 0.7 | 0.6 |
Alors le score de base est environ 0.50
Et avec le boost abonnement, le score devient 0.65
Et le contenu peut être proposé malgré son score géo faible
Scénario: Transparence du boost dans les paramètres
Quand j'accède aux paramètres de l'algorithme de recommandation
Alors je vois l'information: "Les contenus de vos créateurs suivis bénéficient d'un boost de +30%"
Et je comprends que ce n'est pas une priorité absolue
Et que la découverte de nouveaux contenus reste possible
Scénario: Boost désactivé si désabonnement
Étant donné que je suis abonné au créateur "JeanDupont"
Et qu'un de ses contenus bénéficiait du boost +30%
Quand je me désabonne de "JeanDupont"
Alors ses contenus n'ont plus le boost
Et leur score revient au score de base sans multiplicateur
Scénario: Contenu d'un créateur nouvellement suivi
Étant donné que je viens de m'abonner à "NouveauCreateur"
Et qu'il a publié un contenu il y a 2 jours
Quand l'algorithme recalcule les scores
Alors le boost de +30% est immédiatement appliqué à ce contenu
Et il peut apparaître dans ma prochaine file d'attente
Scénario: Impact sur le taux de contenu suivi dans le feed
Étant donné que je suis abonné à 30 créateurs
Et que j'écoute 100 contenus sur une semaine
Quand j'analyse la répartition
Alors environ 40-50% des contenus proviennent de créateurs suivis
Et 50-60% proviennent de créateurs non-suivis (découverte)
Car le boost favorise sans dominer
Scénario: Contenu suivi hors zone géographique
Étant donné que je suis à Paris
Et que je suis abonné à un créateur de Marseille
Et qu'il publie un contenu ancré à Marseille (hors de portée)
Quand l'algorithme évalue ce contenu
Alors le score géo est quasi nul (0.05)
Et même avec boost +30%, le score reste très faible
Et le contenu n'est probablement pas proposé
Scénario: Performance de calcul du boost
Étant donné que je suis abonné à 100 créateurs
Et que l'algorithme évalue 1000 contenus potentiels
Quand le calcul des scores avec boost est effectué
Alors le temps de calcul reste inférieur à 50ms
Car le boost est une simple multiplication (opération O(1))
Et la requête SQL utilise un JOIN sur la table abonnements
Scénario: Boost combiné avec d'autres facteurs
Étant donné un contenu d'un créateur suivi
Et que le contenu bénéficie aussi de:
| facteur | impact |
| Score d'engagement élevé | +20% |
| Contenu récent (<24h) | +10% |
| Boost abonnement | +30% |
Quand le score final est calculé
Alors le boost abonnement s'applique au score final (après tous les autres calculs)
Et les boosts ne s'additionnent pas, le boost abonnement est un multiplicateur final

View File

@@ -0,0 +1,244 @@
# language: fr
Fonctionnalité: Limites d'abonnements et désabonnement
En tant qu'auditeur
Je veux gérer mes abonnements de manière équilibrée
Afin de suivre mes créateurs préférés sans être submergé
Contexte:
Étant donné que l'API RoadWave est disponible
Et que je suis connecté en tant qu'auditeur
Scénario: Limite maximale de 200 abonnements
Étant donné que je suis abonné à 199 créateurs
Quand j'essaie de m'abonner à un 200ème créateur
Alors l'abonnement réussit
Et je suis maintenant abonné à 200 créateurs
Scénario: Impossible de dépasser 200 abonnements
Étant donné que je suis déjà abonné à 200 créateurs
Quand j'essaie de m'abonner à un nouveau créateur
Alors l'action échoue
Et je vois le message:
"""
Vous suivez déjà 200 créateurs. Désabonnez-vous d'un créateur pour en suivre un nouveau.
"""
Scénario: Suggestion de désabonnement de créateurs inactifs
Étant donné que je suis abonné à 200 créateurs
Et que j'essaie de m'abonner à un nouveau créateur
Quand je vois le message de limite atteinte
Alors je vois aussi une suggestion:
"""
Vous n'avez pas écouté [Créateur X] depuis 6 mois, le désabonner ?
"""
Et un bouton "Désabonner" est proposé pour ce créateur
Scénario: Liste triable des abonnements
Étant donné que je suis abonné à 150 créateurs
Quand j'accède à ma liste d'abonnements
Alors je peux trier par:
| critère | ordre |
| Date d'abonnement | Plus récent / Plus ancien |
| Nombre de contenus écoutés| Plus écoutés / Moins écoutés |
| Dernière activité créateur| Plus récent / Plus ancien |
| Ordre alphabétique | A-Z / Z-A |
Scénario: Abonnement initial augmente les jauges de +5%
Étant donné que mes jauges d'intérêt sont:
| catégorie | valeur initiale |
| Automobile | 60% |
| Voyage | 55% |
Et qu'un créateur tague ses contenus "Automobile" et "Voyage"
Quand je m'abonne à ce créateur
Alors mes jauges évoluent:
| catégorie | nouvelle valeur |
| Automobile | 65% (+5%) |
| Voyage | 60% (+5%) |
Scénario: Abonnement avec créateur ayant 3 tags
Étant donné qu'un créateur tague ses contenus:
| tags |
| Automobile, Voyage, Technologie |
Et que mes jauges sont toutes à 50%
Quand je m'abonne à ce créateur
Alors les 3 jauges augmentent de +5%:
| catégorie | nouvelle valeur |
| Automobile | 55% |
| Voyage | 55% |
| Technologie | 55% |
Scénario: Désabonnement diminue les jauges de -5%
Étant donné que je suis abonné à un créateur avec tags "Politique" et "Économie"
Et que mes jauges sont:
| catégorie | valeur actuelle |
| Politique | 70% |
| Économie | 65% |
Quand je me désabonne de ce créateur
Alors mes jauges évoluent:
| catégorie | nouvelle valeur |
| Politique | 65% (-5%) |
| Économie | 60% (-5%) |
Scénario: Désabonnement sans confirmation
Étant donné que je consulte le profil d'un créateur suivi
Quand je clique sur "Se désabonner"
Alors le désabonnement est immédiat
Et aucune popup de confirmation n'apparaît
Car l'action est réversible (je peux me réabonner)
Scénario: Réabonnement possible immédiatement
Étant donné que je viens de me désabonner d'un créateur
Quand je consulte à nouveau son profil
Alors le bouton "S'abonner" est affiché
Et je peux me réabonner immédiatement
Et mes jauges augmentent à nouveau de +5%
Scénario: Effet symétrique abonnement/désabonnement
Étant donné qu'un créateur a les tags "Musique" et "Culture"
Et que ma jauge Musique est à 50%
Quand je m'abonne puis me désabonne immédiatement
Alors ma jauge revient exactement à 50%
Et il n'y a pas de perte ou gain net
Scénario: Abonnement ne dépasse pas 100% de jauge
Étant donné que ma jauge Automobile est à 97%
Et qu'un créateur tague ses contenus "Automobile"
Quand je m'abonne à ce créateur
Alors ma jauge Automobile passe à 100% (limite max)
Et l'augmentation effective est de +3% seulement
Scénario: Désabonnement ne descend pas sous 0%
Étant donné que ma jauge Politique est à 3%
Et que je suis abonné à un créateur avec tag "Politique"
Quand je me désabonne de ce créateur
Alors ma jauge Politique passe à 0% (limite min)
Et la diminution effective est de -3% seulement
Scénario: Créateur ne voit pas qui est abonné (privacy)
Étant donné que je suis abonné au créateur "JeanDupont"
Quand "JeanDupont" consulte ses statistiques
Alors il voit le nombre total d'abonnés (ex: "1,247 abonnés")
Mais il ne voit pas la liste des utilisateurs abonnés
Et mon identité reste privée
Scénario: Créateur voit uniquement le nombre total d'abonnés
Étant donné que je suis créateur
Et que j'ai 523 abonnés
Quand je consulte mes statistiques
Alors je vois "523 abonnés"
Mais je ne peux pas:
| action interdite |
| Voir la liste des abonnés |
| Contacter mes abonnés individuellement|
| Voir leurs profils |
Scénario: Pas d'abonnement mutuel visible
Étant donné que je suis abonné au créateur "Alice"
Et qu'"Alice" est abonnée à mon compte créateur
Quand je consulte le profil d'"Alice"
Alors je ne vois pas d'indication qu'elle est abonnée à moi
Et il n'y a pas de badge "Abonné mutuellement"
Scénario: Performance avec 200 abonnements
Étant donné que je suis abonné à 200 créateurs
Quand l'algorithme calcule ma recommandation
Alors la requête SQL utilise un JOIN sur la table abonnements
Et la table est indexée sur user_id et creator_id
Et le temps de calcul reste inférieur à 50ms
Scénario: Impact sur la recommandation avec beaucoup d'abonnements
Étant donné que je suis abonné à 150 créateurs très actifs
Et qu'ils publient collectivement 100 contenus par jour
Quand l'algorithme génère ma file de 5 contenus
Alors environ 60-70% des contenus proviennent de créateurs suivis (grâce au boost +30%)
Mais 30-40% proviennent de nouveaux créateurs (découverte)
Scénario: Notification de désabonnement au créateur (non implémenté)
Étant donné que je me désabonne d'un créateur
Alors le créateur ne reçoit aucune notification
Et il ne peut pas savoir qui s'est désabonné
Car cela préserve la privacy et évite le harcèlement
Scénario: Statistiques d'abonnements pour l'utilisateur
Étant donné que je suis abonné à 87 créateurs
Quand j'accède à mes statistiques d'abonnements
Alors je vois:
| métrique | exemple valeur |
| Nombre total d'abonnements | 87 / 200 |
| Créateurs les plus écoutés | Top 10 avec % écoute |
| Créateurs non écoutés depuis 6 mois | 12 créateurs |
| Nouveaux contenus non écoutés | 23 contenus |
Scénario: Recherche dans la liste d'abonnements
Étant donné que je suis abonné à 120 créateurs
Quand j'accède à ma liste d'abonnements
Alors je peux chercher par nom de créateur
Et les résultats sont filtrés en temps réel
Et je trouve rapidement un créateur spécifique
Scénario: Export de la liste d'abonnements (RGPD)
Étant donné que je demande l'export de mes données
Quand l'export est généré
Alors la liste de mes abonnements est incluse:
```json
{
"subscriptions": [
{
"creator_name": "JeanDupont",
"creator_id": "abc123",
"subscribed_at": "2025-06-15T10:30:00Z"
},
...
]
}
```
Scénario: Suppression compte utilisateur et impact sur abonnements
Étant donné que je suis abonné à 50 créateurs
Quand je supprime définitivement mon compte
Alors tous mes abonnements sont supprimés
Et le compteur d'abonnés de chaque créateur est décrémenté de -1
Et les jauges n'existent plus (données supprimées)
Scénario: Suppression compte créateur et impact sur abonnés
Étant donné que je suis abonné au créateur "Bob"
Quand "Bob" supprime son compte créateur
Alors je suis automatiquement désabonné
Et mes jauges diminuent de -5% pour les tags de "Bob"
Et je ne vois plus "Bob" dans ma liste d'abonnements
Scénario: Limite 200 justifiée par usage réaliste
Étant donné que la moyenne d'abonnements sur YouTube est de ~50-100 chaînes
Et que Twitter limite à 5000 follows (mais moyenne ~150)
Quand RoadWave fixe la limite à 200
Alors cela couvre largement 99% des utilisateurs
Et évite les abus (comptes spam suivant tout le monde)
Scénario: Table PostgreSQL optimisée pour abonnements
Étant donné la structure de table subscriptions:
```sql
CREATE TABLE subscriptions (
user_id UUID,
creator_id UUID,
subscribed_at TIMESTAMP,
PRIMARY KEY (user_id, creator_id)
);
CREATE INDEX idx_user_subscriptions ON subscriptions(user_id);
CREATE INDEX idx_creator_subscribers ON subscriptions(creator_id);
```
Alors les requêtes d'abonnements sont O(1) avec index
Et le count d'abonnés par créateur est rapide
Et la vérification "est abonné ?" est instantanée
Scénario: Détection d'abonnements abusifs
Étant donné qu'un utilisateur s'abonne à 200 créateurs en moins de 5 minutes
Quand le système détecte cette activité suspecte
Alors un rate limiting est appliqué (max 10 abonnements/minute)
Et l'utilisateur voit "Trop d'actions rapides. Veuillez réessayer dans 1 minute"
Et cela prévient les bots de spam
Scénario: Badge créateur vérifié visible dans abonnements
Étant donné que je suis abonné à 3 créateurs dont 1 vérifié
Quand je consulte ma liste d'abonnements
Alors le créateur vérifié a un badge bleu
Et les créateurs non vérifiés n'ont pas de badge

View File

@@ -0,0 +1,239 @@
# language: fr
Fonctionnalité: Notifications contextuelles selon le mode de déplacement
En tant qu'auditeur
Je veux recevoir des notifications adaptées à mon contexte
Afin d'être informé sans être distrait en conduisant
Contexte:
Étant donné que l'API RoadWave est disponible
Et que je suis connecté en tant qu'auditeur
Et que j'ai activé les notifications
Scénario: Détection automatique du contexte en voiture
Étant donné que ma vitesse GPS est de 50 km/h
Quand le système détecte mon contexte
Alors je suis identifié comme "En voiture"
Et les notifications push sont désactivées
Et seules les notifications in-app sont actives
Scénario: Détection automatique du contexte à pied
Étant donné que ma vitesse GPS est de 3 km/h
Quand le système détecte mon contexte
Alors je suis identifié comme "À pied"
Et les notifications push sont activées
Et l'interface tactile et vocale sont disponibles
Scénario: Zone de transition 5-10 km/h
Étant donné que ma vitesse GPS varie entre 5 et 10 km/h
Quand le système détecte mon contexte
Alors un algorithme de lissage est appliqué sur 30 secondes
Et le mode est déterminé selon la vitesse moyenne
Et les changements de mode ne sont pas trop fréquents
Scénario: Nouveau contenu créateur suivi - Mode voiture
Étant donné que je suis en voiture (vitesse >10 km/h)
Et que je suis abonné au créateur "JeanDupont"
Quand "JeanDupont" publie un nouveau contenu dans ma zone
Alors je ne reçois pas de notification push
Mais je vois un badge compteur in-app
Et le contenu apparaît dans ma file avec boost +30%
Scénario: Nouveau contenu créateur suivi - Mode piéton
Étant donné que je suis à pied (vitesse <5 km/h)
Et que je suis abonné au créateur "JeanDupont"
Et que je suis situé en Île-de-France
Quand "JeanDupont" publie un contenu géolocalisé en Île-de-France
Alors je reçois une notification push:
"""
🎧 JeanDupont a publié : "Titre du contenu"
Tap pour écouter
"""
Scénario: Live créateur suivi - Mode voiture
Étant donné que je suis en voiture
Et que je suis abonné au créateur "RadioLive"
Quand "RadioLive" démarre un live dans ma zone
Alors je ne reçois pas de notification push
Mais je vois un badge compteur in-app
Et le live peut apparaître dans ma recommandation automatiquement
Scénario: Live créateur suivi - Mode piéton
Étant donné que je suis à pied
Et que je suis abonné au créateur "RadioLive"
Et que je suis situé dans la zone du live
Quand "RadioLive" démarre un live
Alors je reçois une notification push:
"""
🔴 RadioLive est en direct : "Titre du live"
Tap pour rejoindre
"""
Scénario: Audio-guide disponible à proximité - Mode piéton
Étant donné que je suis à pied
Quand je passe à moins de 100m d'un lieu avec audio-guides
Alors je reçois une notification push:
"""
📍 Audio-guide disponible : Musée du Louvre
Choisissez parmi 3 guides pour Musée du Louvre
Tap pour explorer
"""
Scénario: Audio-guide disponible à proximité - Mode voiture
Étant donné que je suis en voiture
Quand je passe à moins de 100m d'un lieu avec audio-guides
Alors je reçois une notification audio (bip)
Et une annonce vocale: "Audio-guide disponible"
Mais pas de notification push (sécurité)
Scénario: Filtrage géographique des notifications
Étant donné que je suis abonné au créateur "CreateurMarseille"
Et que je suis situé à Paris
Quand "CreateurMarseille" publie un contenu ancré à Marseille
Alors je ne reçois pas de notification
Car le contenu est hors de ma zone géographique
Et cela évite la frustration de contenus non écoutables
Scénario: Contenu national notifie tous les abonnés
Étant donné que je suis abonné au créateur "MediaNational"
Et que je suis situé n'importe où en France
Quand "MediaNational" publie un contenu de type "National"
Alors je reçois une notification (si mode piéton)
Car les contenus nationaux ne sont pas filtrés géographiquement
Scénario: Limite de 10 notifications push par jour
Étant donné que je suis abonné à 50 créateurs actifs
Et que j'ai déjà reçu 10 notifications push aujourd'hui
Quand un 11ème contenu est publié
Alors je ne reçois pas de notification push individuelle
Mais une notification groupée: "🎧 3 nouveaux contenus de créateurs suivis"
Scénario: Paramétrage de la limite quotidienne
Étant donné que la limite par défaut est de 10 notifications/jour
Quand j'accède aux paramètres de notifications
Alors je peux modifier la limite entre 5 et 20
Et si je choisis 15, je recevrai jusqu'à 15 notifications/jour
Scénario: Mode silencieux nocturne par défaut
Étant donné que le mode silencieux est activé de 22h à 8h par défaut
Et qu'il est 23h30
Quand un créateur suivi publie un contenu
Alors je ne reçois pas de notification push
Mais les notifications sont empilées
Et je les vois le lendemain matin à 8h01
Scénario: Exception du mode silencieux pour les lives
Étant donné que le mode silencieux est activé (22h-8h)
Et qu'il est 23h00
Et que j'ai activé "Notifications importantes uniquement" (lives uniquement)
Quand un créateur suivi démarre un live
Alors je reçois quand même la notification push du live
Car les lives sont des événements temps réel prioritaires
Scénario: Désactivation complète des notifications
Étant donné que j'accède aux paramètres de notifications
Quand je désactive toutes les notifications
Alors je ne reçois plus aucune notification push
Et les badges in-app sont également désactivés
Et seule la recommandation algorithmique reste active
Scénario: Notification "Nouveaux contenus" activée par défaut
Étant donné que je crée un nouveau compte
Et que je m'abonne à mon premier créateur
Quand je consulte les préférences de notifications
Alors "Nouveaux contenus" est activé par défaut
Et "Lives" est activé par défaut
Et "Audio-guides proximité" est activé par défaut
Scénario: Désactivation sélective par type de notification
Étant donné que j'ai activé toutes les notifications
Quand je désactive uniquement "Nouveaux contenus"
Alors je ne reçois plus de notifications pour nouveaux contenus
Mais je reçois toujours les notifications de lives
Et les notifications d'audio-guides restent actives
Scénario: Notification groupée après limite dépassée
Étant donné que j'ai reçu 10 notifications push aujourd'hui
Et que 5 nouveaux contenus sont publiés dans l'heure suivante
Quand la 11ème notification devrait être envoyée
Alors les 5 contenus sont regroupés en une seule notification:
"""
🎧 5 nouveaux contenus de créateurs suivis
Tap pour voir la liste
"""
Scénario: Détail de la notification groupée
Étant donné que j'ai reçu une notification groupée "3 nouveaux contenus"
Quand je tape sur la notification
Alors l'app s'ouvre sur une liste des 3 contenus:
| créateur | titre |
| JeanDupont | "Actualité du jour" |
| MarieDurand | "Podcast économie" |
| PaulMartin | "Anecdote historique" |
Et je peux choisir lequel écouter en premier
Scénario: Personnalisation des plages horaires du mode silencieux
Étant donné que le mode silencieux est 22h-8h par défaut
Quand j'accède aux paramètres
Alors je peux modifier les heures: par exemple 23h-7h
Et le mode silencieux s'applique dans la nouvelle plage horaire
Scénario: Format notification nouveau contenu complet
Étant donné que je suis à pied
Et qu'un créateur suivi publie un contenu
Quand je reçois la notification push
Alors elle contient:
| élément | exemple |
| Emoji | 🎧 |
| Créateur | JeanDupont |
| Action | a publié |
| Titre | "Les secrets du Louvre" |
| CTA | Tap pour écouter |
Scénario: Format notification live complet
Étant donné que je suis à pied
Et qu'un créateur suivi démarre un live
Quand je reçois la notification push
Alors elle contient:
| élément | exemple |
| Emoji | 🔴 |
| Créateur | RadioLive |
| Action | est en direct |
| Titre | "Débat politique ce soir" |
| CTA | Tap pour rejoindre |
Scénario: Notification disparaît si contenu supprimé
Étant donné que j'ai reçu une notification pour un contenu
Et que je n'ai pas encore tapé dessus
Quand le créateur supprime le contenu
Alors la notification est automatiquement retirée de mon centre de notifications
Et si je tape dessus par erreur, je vois "Contenu non disponible"
Scénario: Badge compteur in-app en mode voiture
Étant donné que je suis en voiture
Et que 5 créateurs suivis publient des contenus
Quand j'ouvre l'application
Alors je vois un badge "5" sur l'onglet "Nouveautés"
Et en consultant l'onglet, je vois les 5 nouveaux contenus
Et le badge disparaît après consultation
Scénario: Coût des notifications push Firebase
Étant donné que je reçois 10 notifications push par jour
Et que je suis actif 365 jours par an
Quand le système calcule le coût
Alors 3650 notifications/an sont envoyées
Et Firebase Cloud Messaging est gratuit jusqu'à plusieurs millions de notifications
Et le coût reste 0 pour le volume MVP/Growth
Scénario: Deep link depuis notification push
Étant donné que je reçois une notification push pour un contenu
Quand je tape sur la notification
Alors l'app s'ouvre directement sur le contenu
Et la lecture démarre automatiquement (si j'étais à pied)
Ou le contenu est ajouté en première position dans la file (si je suis en voiture)
Scénario: Notification refusée si permissions désactivées au niveau OS
Étant donné que j'ai désactivé les notifications dans les paramètres iOS/Android
Quand un créateur suivi publie un contenu
Alors aucune notification push n'est envoyée
Et l'app propose de réactiver les permissions dans les paramètres
Mais les badges in-app continuent de fonctionner

View File

@@ -0,0 +1,242 @@
# language: fr
Fonctionnalité: Gestion des contenus supprimés pendant offline
En tant qu'utilisateur offline
Je veux être informé des contenus supprimés à la reconnexion
Afin de comprendre pourquoi certains contenus ont disparu
Contexte:
Étant donné qu'un utilisateur a téléchargé 50 contenus offline
Et que l'utilisateur part offline pendant 7 jours
# Processus de synchronisation
Scénario: Reconnexion WiFi - Validation des contenus locaux
Étant donné que l'utilisateur se reconnecte au WiFi
Quand l'application détecte la connexion internet
Alors une requête API est envoyée: GET /offline/validate
Et l'API retourne:
"""json
{
"valid_ids": [1, 2, 3, 5, 7, 8, ...],
"deleted_ids": [4, 6, 10],
"metadata_updates": [
{"id": 9, "new_title": "Nouveau titre", "creator": "CreateurA"}
]
}
"""
Scénario: Traitement réponse validation - Suppression fichiers locaux
Étant donné que l'API retourne deleted_ids = [4, 6, 10]
Quand l'application traite la réponse
Alors les fichiers locaux des contenus 4, 6, 10 sont supprimés immédiatement
Et les métadonnées locales SQLite sont mises à jour
Et un compteur de suppressions est incrémenté
Scénario: Renouvellement validité contenus valides
Étant donné que l'API retourne valid_ids = [1, 2, 3, 5, 7, 8, ...]
Quand l'application traite la réponse
Alors la validité de ces contenus est renouvelée pour 30 jours
Et la date d'expiration est mise à jour: today + 30 jours
Scénario: Mise à jour métadonnées modifiées
Étant donné que l'API retourne metadata_updates avec un nouveau titre
Quand l'application traite la réponse
Alors la base SQLite locale est mise à jour:
| content_id | nouveau_titre | nouveau_creator |
| 9 | Nouveau titre | CreateurA |
Et les fichiers audio restent inchangés
# Gestion contenu en cours d'écoute
Scénario: Contenu supprimé en cours de lecture - Arrêt immédiat
Étant donné que l'utilisateur écoute le contenu 4 (à 1:30 sur 3:00)
Et que l'API retourne deleted_ids = [4]
Quand la synchronisation détecte que contenu 4 est supprimé
Alors la lecture s'arrête immédiatement
Et une modal s'affiche:
"""
Contenu supprimé
Ce contenu n'est plus disponible
et a été retiré par le créateur.
Passage au contenu suivant...
[OK]
"""
Et le fichier audio est supprimé localement
Et après 2 secondes, le contenu suivant démarre
Scénario: Contenu supprimé PAS en cours - Suppression silencieuse
Étant donné que l'utilisateur écoute le contenu 1
Et que l'API retourne deleted_ids = [4, 6, 10]
Quand la synchronisation traite les suppressions
Alors les fichiers 4, 6, 10 sont supprimés silencieusement
Et aucune interruption de lecture ne se produit
Et un toast récapitulatif est affiché à la fin de la lecture actuelle
# Message récapitulatif
Scénario: Plusieurs contenus supprimés - Popup récapitulative
Étant donné que 3 contenus ont été supprimés (4, 6, 10)
Quand la synchronisation est terminée
Alors une popup s'affiche:
"""
Contenus supprimés
3 contenus téléchargés ne sont plus
disponibles et ont été retirés.
Les créateurs peuvent supprimer ou
modifier leurs contenus à tout moment.
[Voir la liste] [OK]
"""
Scénario: Bouton "Voir la liste" - Affichage détails
Étant donné que la popup récapitulative est affichée
Quand l'utilisateur clique sur [Voir la liste]
Alors une nouvelle vue s'affiche avec:
| Contenu | Créateur | Raison |
| Podcast Auto Episode 4| CreateurA | Retiré créateur |
| Musique Classique | CreateurB | Modération |
| Audio-guide Paris | CreateurC | Retiré créateur |
Et l'historique est conservé 7 jours puis purgé
Scénario: Bouton "OK" - Fermeture popup
Étant donné que la popup récapitulative est affichée
Quand l'utilisateur clique sur [OK]
Alors la popup se ferme
Et l'utilisateur continue normalement
Et l'historique des suppressions reste accessible pendant 7 jours
# Toast notification
Scénario: Toast simple si 1 seul contenu supprimé
Étant donné qu'un seul contenu a été supprimé (4)
Quand la synchronisation est terminée
Alors un toast s'affiche pendant 5 secondes:
"""
1 contenu supprimé a été retiré
"""
Et aucune popup n'est affichée (toast suffit)
Scénario: Popup si 2+ contenus supprimés
Étant donné que 2 ou plus contenus ont été supprimés
Quand la synchronisation est terminée
Alors une popup complète est affichée (pas juste un toast)
Car l'utilisateur doit être informé clairement
# Motifs de suppression
Scénario: Contenu supprimé par créateur volontairement
Étant donné que le créateur a supprimé le contenu 4 volontairement
Quand l'API retourne deleted_ids = [4] avec motif "creator_deleted"
Alors le contenu est retiré immédiatement (pas de grace period MVP)
Et la raison affichée est: "Retiré par le créateur"
Scénario: Contenu supprimé par modération (violation CGU)
Étant donné que le contenu 6 a été modéré pour violation CGU
Quand l'API retourne deleted_ids = [6] avec motif "moderation"
Alors le contenu est retiré immédiatement
Et la raison affichée est: "Retiré pour modération"
Scénario: Contenu passé en Premium (créateur change statut)
Étant donné que le contenu 10 est passé en "Premium exclusif"
Et que l'utilisateur est gratuit
Quand l'API retourne deleted_ids = [10] avec motif "premium_only"
Alors le contenu est retiré immédiatement
Et la raison affichée est: "Réservé aux abonnés Premium"
# Justification KISS
Scénario: Comparaison avec grace period - Simplicité MVP
Étant donné qu'on compare la suppression immédiate vs grace period
Quand on évalue les avantages KISS
Alors les avantages suppression immédiate sont:
| avantage | description |
| Simplicité technique | Pas de gestion états intermédiaires complexes |
| Respect créateur | Volonté immédiate respectée |
| Conformité légale | Contenu illégal retiré immédiatement |
| Cas rare | Peu de créateurs suppriment contenus après publi |
# Post-MVP : Grace period (si feedback négatifs)
Scénario: Post-MVP - Grace period 7 jours pour suppression créateur volontaire
Étant donné qu'on implémente la grace period post-MVP
Et que le contenu 4 est supprimé volontairement par le créateur
Quand l'API retourne deleted_ids = [4] avec motif "creator_deleted"
Alors le contenu est marqué "Bientôt retiré" avec badge
Et l'utilisateur peut encore l'écouter pendant 7 jours
Et après 7 jours, le contenu est supprimé
Scénario: Post-MVP - Pas de grace period pour modération
Étant donné qu'on implémente la grace period post-MVP
Et que le contenu 6 est modéré (illégal, violation CGU)
Quand l'API retourne deleted_ids = [6] avec motif "moderation"
Alors le contenu est supprimé immédiatement (pas de grace period)
Car sécurité/légalité prime
Scénario: Post-MVP - Grace period si user Premium et contenu devient Premium
Étant donné qu'on implémente la grace period post-MVP
Et que le contenu 10 passe en "Premium exclusif"
Et que l'utilisateur a un abonnement Premium
Quand l'API retourne deleted_ids = [10] avec motif "premium_only"
Alors l'utilisateur Premium conserve l'accès (pas supprimé)
Mais l'utilisateur gratuit perd l'accès après 7 jours
# Cas limites
Scénario: Tous les contenus offline supprimés - Message spécifique
Étant donné que l'utilisateur avait 5 contenus offline
Et que les 5 contenus ont été supprimés pendant offline
Quand la synchronisation retourne deleted_ids = [1, 2, 3, 4, 5]
Alors une popup spécifique s'affiche:
"""
Tous vos contenus offline ont été supprimés
Les 5 contenus téléchargés ne sont plus disponibles.
Téléchargez de nouveaux contenus pour écouter offline.
[Parcourir contenus] [OK]
"""
Scénario: Contenu supprimé puis utilisateur re-télécharge
Étant donné que le contenu 4 a été supprimé pendant offline
Et que l'utilisateur a vu la notification
Quand l'utilisateur se reconnecte et browse les contenus
Alors le contenu 4 n'apparaît plus dans la liste
Et l'utilisateur ne peut pas le re-télécharger (supprimé définitivement)
Scénario: Historique suppressions conservé 7 jours
Étant donné que 3 contenus ont été supprimés le 1er février
Quand l'utilisateur consulte l'historique des suppressions le 7 février
Alors l'historique est toujours visible
Quand l'utilisateur consulte l'historique le 9 février (7+ jours)
Alors l'historique a été purgé automatiquement
# Statistiques côté API
Scénario: API - Comptabilisation contenus supprimés pendant offline
Étant donné que 1000 utilisateurs étaient offline pendant 7 jours
Et que 150 contenus ont été supprimés pendant cette période
Quand l'API génère les métriques de synchronisation
Alors les statistiques affichent:
| métrique | valeur |
| Utilisateurs reconnectés | 1000 |
| Contenus supprimés détectés | 4500 |
| Moyenne contenus supprimés/user | 4.5 |
| Users affectés (1 suppression) | 850 |
# Performance
Scénario: Requête GET /offline/validate - Performance optimisée
Étant donné qu'un utilisateur a 50 contenus offline
Quand l'API reçoit GET /offline/validate avec la liste des 50 IDs
Alors la requête SQL vérifie l'existence des contenus en batch:
"""sql
SELECT id FROM contents WHERE id IN (1,2,3,...,50) AND deleted_at IS NULL
"""
Et la réponse est générée en <200ms
Et Redis cache les résultats pour éviter requêtes répétées

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,362 @@
# language: fr
Fonctionnalité: Avantages Premium
En tant qu'abonné Premium
Je veux bénéficier d'avantages exclusifs
Afin de profiter d'une expérience audio améliorée sans publicité
Contexte:
Étant donné que je suis connecté à l'application RoadWave
# ===== PUBLICITÉS =====
Scénario: Utilisateur gratuit voit 1 publicité tous les 5 contenus
Étant donné que je suis un utilisateur gratuit
Quand j'écoute ma file de contenus
Alors je vois une publicité tous les 5 contenus
Et la publicité dure 30 secondes en moyenne
Et je ne peux pas la skip
Scénario: Utilisateur Premium ne voit aucune publicité
Étant donné que je suis un utilisateur Premium
Quand j'écoute mes contenus
Alors aucune publicité n'est diffusée
Et je passe directement d'un contenu à l'autre
Et l'expérience d'écoute est fluide et ininterrompue
Scénario: Badge "0 publicité" sur page Premium
Étant donné que je consulte la page des avantages Premium
Quand je lis la liste des avantages
Alors je vois en premier:
"""
🚫 0 publicité
Profitez d'une écoute sans interruption
"""
Et c'est l'argument principal mis en avant
# ===== CONTENUS EXCLUSIFS =====
Scénario: Utilisateur gratuit voit contenus Premium bloqués
Étant donné que je suis un utilisateur gratuit
Quand je consulte les contenus d'un créateur
Alors je vois les contenus marqués Premium avec badge 👑
Mais je ne peux pas les lire (overlay bloquant)
Scénario: Utilisateur Premium accède à tous les contenus exclusifs
Étant donné que je suis un utilisateur Premium
Quand je consulte les contenus d'un créateur
Alors tous les contenus Premium sont accessibles
Et je peux les lire sans restriction
Et j'ai accès à 100% du catalogue (gratuit + Premium)
Scénario: Nombre de contenus Premium disponibles
Étant donné que je suis Premium
Quand je consulte les statistiques
Alors je vois combien de contenus Premium sont disponibles sur la plateforme
Et par exemple: "8,547 contenus Premium exclusifs disponibles"
Et cela justifie la valeur de l'abonnement
# ===== QUALITÉ AUDIO =====
Scénario: Utilisateur gratuit écoute en 48 kbps Opus
Étant donné que je suis un utilisateur gratuit
Quand je lance un contenu
Alors l'audio est streamé en 48 kbps Opus
Et cela consomme environ 20 MB/heure
Et la qualité est très correcte pour de la voix
Scénario: Utilisateur Premium écoute en 64 kbps Opus
Étant donné que je suis un utilisateur Premium
Quand je lance un contenu
Alors l'audio est streamé en 64 kbps Opus
Et cela consomme environ 30 MB/heure
Et la qualité est excellente (détails audio supérieurs)
Scénario: Comparaison qualité 48 kbps vs 64 kbps
Étant donné que je consulte la page Premium
Quand je lis la section qualité audio
Alors je vois l'explication:
"""
📻 Qualité audio supérieure
Gratuit: 48 kbps Opus (~20 MB/h)
Premium: 64 kbps Opus (~30 MB/h)
Profitez d'une qualité audio exceptionnelle avec plus de détails
et une meilleure restitution des voix et ambiances.
"""
Scénario: Justification 48 kbps suffisant pour gratuit
Étant donné que le contenu RoadWave est principalement de la voix
Quand la qualité est fixée à 48 kbps pour gratuit
Alors c'est largement suffisant pour comprendre clairement
Et équivalent à la qualité radio FM
Et les utilisateurs gratuits ne sont pas frustrés
Scénario: Justification 64 kbps avantage tangible Premium
Étant donné que les audiophiles et créateurs audio sont exigeants
Quand la qualité Premium est à 64 kbps
Alors la différence est perceptible à l'oreille
Et les ambiances, musiques de fond, nuances de voix sont mieux rendues
Et cela justifie l'abonnement Premium
Scénario: Switch automatique qualité selon abonnement
Étant donné que je suis gratuit et j'écoute en 48 kbps
Quand je souscris à Premium
Alors dès le contenu suivant, je passe automatiquement en 64 kbps
Et je peux entendre la différence de qualité immédiatement
Scénario: Consommation data Premium vs Gratuit
Étant donné que je roule 1 heure par jour
Quand je calcule la consommation mensuelle
Alors en gratuit: 20 MB/h × 1h × 22 jours = 440 MB/mois
Et en Premium: 30 MB/h × 1h × 22 jours = 660 MB/mois
Et la différence est de 220 MB/mois (acceptable pour 4G/5G illimitée)
# ===== MODE OFFLINE =====
Scénario: Utilisateur gratuit limité à 50 contenus téléchargés
Étant donné que je suis un utilisateur gratuit
Quand j'accède au mode offline
Alors je peux télécharger jusqu'à 50 contenus maximum
Et si j'essaie de télécharger un 51ème, je vois:
"""
Limite atteinte (50 contenus max en gratuit).
Passez Premium pour des téléchargements illimités.
"""
Scénario: Utilisateur Premium téléchargements illimités
Étant donné que je suis un utilisateur Premium
Quand j'accède au mode offline
Alors je peux télécharger autant de contenus que je veux
Et la seule limite est l'espace de stockage de mon device
Et par exemple 500 contenus × 10 MB = 5 GB
Scénario: Justification limite 50 contenus gratuit
Étant donné que 50 contenus de 10 minutes = ~8 heures d'écoute
Quand un utilisateur gratuit prépare un road trip
Alors 8 heures couvrent largement une journée de trajet
Et cela permet un usage offline raisonnable sans abuser
Scénario: Justification illimité Premium pour longs road trips
Étant donné qu'un road trip de plusieurs jours nécessite 20-50h de contenu
Quand un utilisateur Premium télécharge 200 contenus
Alors il peut partir serein sans connexion internet pendant 1 semaine
Et cela justifie pleinement l'abonnement Premium
Scénario: Affichage compteur téléchargements gratuit
Étant donné que je suis gratuit et j'ai téléchargé 37 contenus
Quand j'accède à la page Téléchargements
Alors je vois:
"""
📥 Téléchargements offline
37 / 50 contenus téléchargés
Passez Premium pour des téléchargements illimités
[Découvrir Premium]
```
Scénario: Pas de compteur pour Premium
Étant donné que je suis Premium et j'ai téléchargé 187 contenus
Quand j'accède à la page Téléchargements
Alors je vois simplement:
"""
📥 Téléchargements offline
187 contenus téléchargés (illimité)
Espace utilisé: 1.8 GB
```
Et aucune limite n'est affichée
# ===== HISTORIQUE ÉCOUTE =====
Scénario: Utilisateur gratuit historique limité à 100 derniers
Étant donné que je suis un utilisateur gratuit
Quand j'accède à mon historique d'écoute
Alors je vois les 100 derniers contenus écoutés
Et les contenus plus anciens ne sont pas affichés
Et je vois un message "Historique limité à 100 contenus. Passez Premium pour un historique illimité."
Scénario: Utilisateur Premium historique illimité
Étant donné que je suis un utilisateur Premium
Quand j'accède à mon historique d'écoute
Alors je vois tous les contenus que j'ai écoutés depuis mon inscription
Et je peux scroller jusqu'au premier contenu jamais écouté
Et l'historique est complet et permanent
Scénario: Recherche dans historique Premium
Étant donné que je suis Premium et j'ai 2 000 contenus dans mon historique
Quand je recherche "Tesla" dans mon historique
Alors tous les contenus écoutés contenant "Tesla" sont affichés
Et je peux retrouver facilement un contenu écouté il y a 6 mois
Scénario: Justification limite 100 gratuit suffisante
Étant donné que 100 contenus de 10 min = ~16 heures d'écoute
Quand un utilisateur gratuit écoute 1h/jour
Alors l'historique couvre les 16 derniers jours
Et cela suffit pour retrouver un contenu récent
Scénario: Justification illimité Premium pour power users
Étant donné qu'un power user écoute 3h/jour depuis 2 ans
Quand il veut retrouver un contenu spécifique écouté il y a 1 an
Alors l'historique illimité Premium lui permet de retrouver ce contenu
Et cela apporte une vraie valeur ajoutée
Scénario: Export historique complet (Premium uniquement)
Étant donné que je suis Premium
Quand je demande l'export de mes données
Alors l'historique complet est inclus dans l'export:
```json
{
"listen_history": [
{
"content_title": "Mon épisode préféré",
"creator_name": "JeanDupont",
"listened_at": "2025-06-15T14:30:00Z",
"completion_rate": 0.95
},
...
],
"total_listens": 2847
}
```
# ===== TABLEAU COMPARATIF =====
Scénario: Affichage tableau comparatif Gratuit vs Premium
Étant donné que je consulte la page Premium
Quand je vois le tableau comparatif
Alors il affiche:
```
Avantage Gratuit Premium
Publicités 1/5 contenus 0 (aucune)
Contenus exclusifs Bloqués Accès
Qualité audio 48 kbps Opus 64 kbps Opus
Mode offline 50 max Illimité
Historique écoute 100 derniers Illimité
Prix Gratuit 4.99/mois
```
# ===== JUSTIFICATIONS GÉNÉRALES =====
Scénario: Justification 0 pub = argument principal
Étant donné qu'une publicité de 30s tous les 5 contenus = 6 min/h de pub
Quand un utilisateur écoute 1h/jour
Alors il subit 180 min de pub/mois (3 heures !)
Et payer 4.99 pour éviter 3h de pub/mois est très rentable
Et c'est l'argument de conversion n°1
Scénario: Justification qualité audio avantage tangible
Étant donné que la différence 48 kbps 64 kbps est audible
Quand un audiophile compare les deux
Alors il entend clairement la différence sur un bon système audio voiture
Et cela justifie l'abonnement pour les exigeants
Scénario: Justification offline illimité pour road trips
Étant donné qu'un road trip de 2 semaines nécessite 50-100h de contenu
Quand un utilisateur Premium télécharge 300 contenus avant de partir
Alors il peut partir en zone sans réseau sereinement
Et cela apporte une vraie valeur pratique
Scénario: Justification pas d'over-engineering
Étant donné que RoadWave se concentre sur l'essentiel
Quand les avantages Premium sont définis
Alors il n'y a pas de:
| fonctionnalité superflue | raison exclusion |
| Badges cosmétiques | Pas de valeur réelle |
| Avatar Premium exclusif | Inutile pour audio |
| Fonctionnalités sociales avancées | Pas prioritaire MVP |
| Early access nouveaux contenus | Complexité > bénéfice |
Et cela réduit la complexité et le coût de développement
# ===== CONVERSION ET INCITATION =====
Scénario: CTA Premium après 5ème publicité
Étant donné que je suis gratuit et je viens d'entendre ma 5ème pub
Quand la publicité se termine
Alors je vois un message:
"""
😫 Marre des pubs ?
Passez Premium pour seulement 4.99/mois :
0 publicité
Qualité audio supérieure
Téléchargements illimités
[Essayer Premium] [Plus tard]
```
Scénario: CTA Premium quand limite 50 téléchargements atteinte
Étant donné que je suis gratuit et j'ai atteint 50 téléchargements
Quand j'essaie de télécharger un 51ème contenu
Alors je vois une popup:
"""
📥 Limite atteinte
Vous avez atteint la limite de 50 téléchargements.
Avec Premium (4.99/mois), téléchargez autant de contenus que vous voulez
pour vos longs road trips !
[Passer Premium] [Gérer mes téléchargements]
```
Scénario: CTA Premium quand contenu exclusif bloqué
Étant donné que je suis gratuit et je clique sur un contenu Premium
Quand l'overlay bloquant apparaît
Alors je vois:
"""
👑 Contenu Premium
Ce contenu est réservé aux abonnés Premium.
Débloquez 8,547 contenus Premium exclusifs pour 4.99/mois !
[Passer Premium] [Découvrir d'autres contenus]
```
Scénario: Statistiques conversion - Quel avantage convertit le mieux ?
Étant donné qu'un admin consulte les statistiques de conversion
Quand il analyse les sources de conversion
Alors il voit:
| source de conversion | % conversions |
| CTA après 5ème pub | 42% |
| CTA contenu Premium bloqué | 28% |
| CTA limite 50 téléchargements | 18% |
| Page Premium directe | 12% |
Et cela aide à optimiser le placement des CTA
Scénario: A/B test message CTA
Étant donné que RoadWave veut optimiser les conversions
Quand un A/B test est lancé sur le CTA après pub
Alors groupe A voit "Marre des pubs ?" (focus négatif)
Et groupe B voit "Profitez de 0 publicité" (focus positif)
Et le taux de conversion est mesuré
Et le message le plus performant est déployé
Scénario: Notification Premium après 30 jours d'utilisation gratuite
Étant donné que je suis utilisateur gratuit depuis 30 jours
Et que j'écoute régulièrement (15h cumulées)
Quand le 30ème jour arrive
Alors je reçois une notification:
"""
🎉 Vous avez écouté 15h sur RoadWave !
Profitez encore plus avec Premium :
0 publicité
Qualité supérieure
Téléchargements illimités
Offre découverte : -20% sur le premier mois (3.99)
[Découvrir Premium]
```
Scénario: Trial gratuit refusé mais onboarding amélioré
Étant donné qu'il n'y a pas de trial gratuit
Quand un nouvel utilisateur s'inscrit
Alors un onboarding explique clairement les avantages Premium
Et il peut comparer gratuit vs Premium dès le premier lancement
Et cela l'aide à décider rapidement s'il veut payer

View File

@@ -0,0 +1,457 @@
# language: fr
Fonctionnalité: Gestion abonnement Premium
En tant qu'utilisateur
Je veux gérer facilement mon abonnement Premium
Afin de souscrire, renouveler ou annuler en toute transparence
Contexte:
Étant donné que je suis connecté à l'application RoadWave
# ===== SOUSCRIPTION =====
Scénario: Souscription via Web (desktop/mobile) avec Mangopay
Étant donné que je consulte la page Premium sur le site web
Quand je clique sur "S'abonner - Mensuel 4.99"
Alors je suis redirigé vers le formulaire de paiement Mangopay
Et je saisis mes informations de carte bancaire
Et le paiement de 4.99 est prélevé immédiatement
Et la commission Mangopay est de 1.8% + 0.18 = 0.27
Et RoadWave reçoit 4.72 net
Scénario: Calcul commission Mangopay
Étant donné qu'un utilisateur paie 4.99 via Mangopay
Quand la commission est calculée
Alors la commission est : 4.99 × 1.8% + 0.18 = 0.09 + 0.18 = 0.27
Et RoadWave reçoit : 4.99 - 0.27 = 4.72
Et la commission représente 5.4% du prix
Scénario: Souscription via iOS App avec Apple IAP
Étant donné que j'utilise l'app iOS
Quand je clique sur "S'abonner - Mensuel 5.99"
Alors je suis redirigé vers l'interface Apple In-App Purchase
Et le prix affiché est 5.99 (majoré de 20%)
Et le paiement est effectué via mon compte Apple
Et Apple prend 30% de commission = 1.80
Et RoadWave reçoit 4.19 net
Scénario: Souscription via Android App avec Google Play Billing
Étant donné que j'utilise l'app Android
Quand je clique sur "S'abonner - Mensuel 5.99"
Alors je suis redirigé vers l'interface Google Play Billing
Et le prix affiché est 5.99 (majoré de 20%)
Et le paiement est effectué via mon compte Google
Et Google prend 30% de commission = 1.80
Et RoadWave reçoit 4.19 net
Scénario: Majoration 20% sur mobile pour compenser commission 30%
Étant donné que Apple/Google prennent 30% de commission
Quand RoadWave fixe le prix mobile
Alors le prix web est 4.99 (commission Mangopay 5.4%)
Et le prix mobile est 5.99 (commission Apple/Google 30%)
Et la majoration est de 1 (+20%)
Et cela compense partiellement la commission excessive
Scénario: Email incitation souscription web moins chère
Étant donné que je consulte Premium depuis l'app mobile
Quand je vois le prix 5.99
Alors je vois aussi un message:
"""
💡 Astuce : Abonnez-vous sur roadwave.com pour seulement 4.99/mois
Économisez 12/an en évitant les frais Apple/Google !
"""
Et un lien vers le site web est fourni
Scénario: Calcul économie souscription web vs mobile
Étant donné que le prix web est 4.99/mois
Et que le prix mobile est 5.99/mois
Quand je calcule l'économie annuelle
Alors web : 4.99 × 12 = 59.88/an
Et mobile : 5.99 × 12 = 71.88/an
Et économie : 12/an (soit 20% d'économie)
Scénario: Activation immédiate après paiement réussi
Étant donné que je viens de payer mon abonnement Premium
Quand le paiement est confirmé
Alors mon statut passe immédiatement à "Premium"
Et je peux accéder aux avantages Premium dès maintenant
Et je reçois un email de confirmation
Scénario: Email confirmation souscription
Étant donné que j'ai souscrit à Premium
Quand la souscription est confirmée
Alors je reçois un email:
"""
🎉 Bienvenue Premium !
Votre abonnement Premium est actif.
Formule: Mensuel 4.99/mois
Prochain renouvellement: 15 juillet 2025
Vos avantages:
0 publicité
Contenus exclusifs
Qualité audio 64 kbps
Téléchargements illimités
Historique illimité
Profitez pleinement de RoadWave !
Gérer mon abonnement: [Lien]
"""
# ===== RENOUVELLEMENT AUTOMATIQUE =====
Scénario: Email rappel 7 jours avant renouvellement
Étant donné que mon abonnement mensuel se renouvelle le 15 juillet
Quand le 8 juillet arrive (7 jours avant)
Alors je reçois un email de rappel:
"""
📅 Votre abonnement Premium se renouvelle dans 7 jours
Formule: Mensuel 4.99/mois
Date de renouvellement: 15 juillet 2025
Montant: 4.99
Votre carte bancaire sera débitée automatiquement.
Vous souhaitez annuler ? [Gérer mon abonnement]
"""
Scénario: Renouvellement automatique réussi
Étant donné que mon abonnement mensuel arrive à échéance le 15 juillet
Quand le 15 juillet arrive
Alors Mangopay/Apple/Google prélève automatiquement 4.99 (ou 5.99)
Et mon abonnement est renouvelé pour 1 mois supplémentaire
Et je reçois un email de confirmation
Scénario: Email confirmation renouvellement
Étant donné que mon abonnement vient d'être renouvelé
Quand le paiement est confirmé
Alors je reçois un email:
"""
Abonnement Premium renouvelé
Votre abonnement a été renouvelé avec succès.
Montant débité: 4.99
Prochaine échéance: 15 août 2025
Merci de continuer à soutenir RoadWave et ses créateurs !
Gérer mon abonnement: [Lien]
"""
Scénario: Échec paiement renouvellement - Tentative 1
Étant donné que mon abonnement doit se renouveler le 15 juillet
Mais que ma carte bancaire est expirée ou sans fonds
Quand le prélèvement échoue
Alors je reçois un email:
"""
Échec renouvellement abonnement Premium
Le paiement de votre abonnement a échoué.
Raison: Carte bancaire expirée
Nous allons réessayer automatiquement dans 3 jours.
Veuillez mettre à jour vos informations de paiement: [Lien]
Votre accès Premium reste actif jusqu'au 22 juillet (7 jours).
"""
Scénario: Retry automatique paiement après 3 jours
Étant donné que le paiement a échoué le 15 juillet
Quand le 18 juillet arrive (J+3)
Alors Mangopay/Apple/Google tente automatiquement un nouveau prélèvement
Et si le paiement réussit, l'abonnement est renouvelé normalement
Et si le paiement échoue encore, un 2ème retry est programmé
Scénario: Retry automatique paiement après 7 jours
Étant donné que 2 tentatives ont échoué (15 juillet et 18 juillet)
Quand le 22 juillet arrive (J+7)
Alors une 3ème et dernière tentative est effectuée
Et si le paiement réussit, l'abonnement est sauvé
Et si le paiement échoue, l'abonnement est annulé automatiquement
Scénario: Annulation automatique après 3 échecs paiement
Étant donné que les 3 tentatives de renouvellement ont échoué (J+0, J+3, J+7)
Quand la 3ème tentative échoue
Alors mon abonnement Premium est annulé automatiquement
Et mon statut repasse à "Gratuit"
Et je perds accès aux avantages Premium
Et je reçois un email d'annulation
Scénario: Email annulation automatique pour impayé
Étant donné que mon abonnement a été annulé pour échec paiement
Quand l'annulation devient effective
Alors je reçois un email:
"""
Abonnement Premium annulé
Votre abonnement a été annulé suite à 3 échecs de paiement.
Vous repassez en mode gratuit et perdez l'accès à:
Contenus Premium exclusifs
Qualité audio supérieure
Téléchargements illimités
Pour réactiver Premium, mettez à jour vos informations de paiement: [Lien]
"""
# ===== ANNULATION =====
Scénario: Annulation self-service dans Settings
Étant donné que je veux annuler mon abonnement
Quand j'accède à "Paramètres > Abonnement"
Alors je vois un bouton "Annuler l'abonnement"
Et je peux annuler en 2 clics sans contacter le support
Scénario: Confirmation avant annulation
Étant donné que je clique sur "Annuler l'abonnement"
Quand une popup de confirmation apparaît
Alors je vois:
"""
😢 Vous allez annuler votre abonnement Premium
Vous perdrez l'accès à:
0 publicité
Contenus Premium exclusifs
Qualité audio supérieure
Téléchargements illimités
Accès maintenu jusqu'au: 15 juillet 2025 (fin période payée)
Pas de remboursement prorata.
[Confirmer l'annulation] [Rester Premium]
```
Scénario: Accès Premium maintenu jusqu'à fin période payée
Étant donné que j'ai annulé mon abonnement le 1er juillet
Et que mon abonnement mensuel était valable jusqu'au 15 juillet
Quand l'annulation est confirmée
Alors je garde l'accès Premium jusqu'au 15 juillet
Et à partir du 16 juillet, je repasse en gratuit
Et je ne suis pas remboursé pour les 14 jours restants
Scénario: Justification pas de remboursement prorata
Étant donné que l'industrie (Spotify, Netflix, YouTube) ne rembourse pas prorata
Quand RoadWave applique la même règle
Alors c'est le standard accepté par les utilisateurs
Et cela simplifie la gestion comptable
Et évite les abus (souscription puis annulation immédiate pour remboursement)
Scénario: Email confirmation annulation
Étant donné que j'ai annulé mon abonnement
Quand l'annulation est enregistrée
Alors je reçois un email:
"""
Annulation confirmée
Votre abonnement Premium a été annulé.
Accès Premium maintenu jusqu'au: 15 juillet 2025
Après cette date, vous repasserez en mode gratuit.
Pas de remboursement pour la période restante (standard industrie).
Vous pouvez vous réabonner à tout moment !
Nous espérons vous revoir bientôt.
Réabonner: [Lien]
"""
Scénario: Pas de renouvellement après annulation
Étant donné que j'ai annulé mon abonnement le 1er juillet
Quand le 15 juillet arrive (date de renouvellement prévue)
Alors aucun prélèvement n'est effectué
Et mon statut passe automatiquement à "Gratuit"
Et je ne reçois pas d'email de renouvellement
# ===== RÉABONNEMENT =====
Scénario: Réabonnement possible immédiatement
Étant donné que j'ai annulé mon abonnement il y a 5 jours
Quand j'accède à la page Premium
Alors je peux me réabonner immédiatement
Et le processus de paiement est le même que la première fois
Scénario: Pas de nouvelle période d'essai au réabonnement
Étant donné que j'ai annulé mon abonnement il y a 3 mois
Quand je me réabonne
Alors je paie immédiatement 4.99 (pas d'essai gratuit)
Car RoadWave ne propose jamais d'essai gratuit (ni première fois ni réabonnement)
Scénario: Offre win-back pour utilisateurs ayant annulé
Étant donné que j'ai annulé mon abonnement il y a 1 mois
Quand je reçois un email de win-back
Alors je vois une offre spéciale:
"""
🎁 On vous a manqué ?
Revenez en Premium avec une offre exclusive:
-30% sur les 3 premiers mois (3.49/mois au lieu de 4.99)
Offre valable jusqu'au 31 juillet.
[Réactiver Premium]
```
# ===== ARCHITECTURE DONNÉES =====
Scénario: Table subscriptions en base PostgreSQL
Étant donné qu'un utilisateur souscrit à Premium
Quand les données sont enregistrées
Alors la table subscriptions contient:
```sql
CREATE TABLE subscriptions (
id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users(id) UNIQUE,
mangopay_recurring_payin_id VARCHAR(255), -- Null si IAP
mangopay_user_id VARCHAR(255), -- Null si IAP
apple_transaction_id VARCHAR(255), -- Null si Mangopay
google_purchase_token VARCHAR(255), -- Null si Mangopay
status VARCHAR(50) NOT NULL, -- 'active', 'cancelled', 'expired', 'past_due'
plan VARCHAR(50) NOT NULL, -- 'monthly', 'yearly'
current_period_start TIMESTAMP NOT NULL,
current_period_end TIMESTAMP NOT NULL,
cancelled_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
```
Scénario: Statuts possibles dans subscription.status
Étant donné qu'un abonnement peut avoir différents statuts
Quand le statut est stocké en base
Alors les valeurs possibles sont:
| statut | description |
| active | Abonnement actif et payé |
| cancelled | Annulé par utilisateur (accès jusqu'à fin période) |
| expired | Période terminée, pas renouvelé |
| past_due | Échec paiement, en retry automatique |
Scénario: Cache Redis pour vérification Premium temps réel
Étant donné qu'un utilisateur lance un contenu
Quand l'app vérifie s'il est Premium
Alors une clé Redis est consultée:
```
Key: premium:{user_id}
Value: true/false
TTL: 1 heure
```
Et si la clé n'existe pas, elle est recalculée depuis PostgreSQL
Et cela garantit des performances <10ms
Scénario: Refresh cache Redis via webhooks
Étant donné qu'un paiement est confirmé par Mangopay/Apple/Google
Quand un webhook est reçu par RoadWave
Alors le cache Redis premium:{user_id} est mis à jour immédiatement
Et l'utilisateur voit son statut Premium activé sans délai
Scénario: Webhooks Mangopay - PAYIN_NORMAL_SUCCEEDED
Étant donné qu'un paiement Mangopay réussit
Quand Mangopay envoie le webhook PAYIN_NORMAL_SUCCEEDED
Alors RoadWave met à jour subscriptions.status = 'active'
Et met à jour current_period_end = NOW() + 1 mois
Et refresh le cache Redis premium:{user_id} = true
Scénario: Webhooks Mangopay - PAYIN_NORMAL_FAILED
Étant donné qu'un paiement Mangopay échoue
Quand Mangopay envoie le webhook PAYIN_NORMAL_FAILED
Alors RoadWave met à jour subscriptions.status = 'past_due'
Et programme un retry automatique dans 3 jours
Et envoie un email à l'utilisateur
Scénario: Webhooks Apple - App Store Server Notifications
Étant donné qu'un paiement Apple IAP change de statut
Quand Apple envoie une notification serveur
Alors RoadWave parse la notification (JSON)
Et met à jour la subscription en conséquence
Et refresh le cache Redis
Scénario: Webhooks Google - Real-time Developer Notifications
Étant donné qu'un paiement Google Play change de statut
Quand Google envoie une notification temps réel
Alors RoadWave parse la notification (JSON)
Et met à jour la subscription en conséquence
Et refresh le cache Redis
# ===== STATISTIQUES ET MONITORING =====
Scénario: Dashboard admin - Métriques abonnements
Étant donné qu'un admin consulte les métriques Premium
Quand il accède au dashboard
Alors il voit:
| métrique | valeur |
| Abonnés actifs | 12,547 |
| Nouveaux abonnements ce mois | 1,234 |
| Annulations ce mois | 287 (2.3%) |
| Churn rate mensuel | 2.3% |
| MRR (Revenus mensuels récurrents) | 58,890€ |
| Taux conversion gratuit → Premium | 8.5% |
Scénario: Calcul churn rate mensuel
Étant donné que 287 utilisateurs ont annulé ce mois
Et qu'il y avait 12,547 abonnés au début du mois
Quand le churn rate est calculé
Alors churn = 287 / 12,547 = 2.3%
Et un churn <5% est considéré comme excellent
Et RoadWave surveille cette métrique de près
Scénario: Alerte si churn rate >5%
Étant donné que le churn rate mensuel dépasse 5%
Quand le système détecte cette anomalie
Alors une alerte est envoyée à l'équipe:
"""
Churn rate anormal: 6.2%
Nombre annulations ce mois: 778
Causes principales:
- Prix jugé trop élevé: 42%
- Utilisation faible: 28%
- Concurrent moins cher: 18%
- Autre: 12%
Action recommandée: Enquête satisfaction + offres win-back
"""
Scénario: Enquête satisfaction à l'annulation
Étant donné que je viens d'annuler mon abonnement
Quand l'annulation est confirmée
Alors je vois un questionnaire rapide:
"""
Pourquoi annulez-vous Premium ?
Prix trop élevé
Je n'utilise pas assez RoadWave
Pas assez de contenus exclusifs
Problèmes techniques
J'ai trouvé une alternative moins chère
Autre: [Texte libre]
[Envoyer] [Ignorer]
```
Et les réponses aident à améliorer l'offre Premium
Scénario: Répartition canaux souscription
Étant donné qu'un admin analyse les canaux de souscription
Quand il consulte les statistiques
Alors il voit:
| canal | abonnés | % total | revenus/mois |
| Web (Mangopay) | 8,234 | 65.6% | 41,088 |
| iOS (Apple) | 2,845 | 22.7% | 17,042 |
| Android (Google)| 1,468 | 11.7% | 8,793 |
Et cela aide à orienter les efforts marketing (inciter web = moins de commission)
Scénario: Performance vérification Premium <10ms
Étant donné que 100 000 utilisateurs consultent des contenus simultanément
Quand chaque requête vérifie le statut Premium via Redis
Alors le temps de réponse moyen est <10ms
Et Redis gère facilement 100 000 requêtes/seconde
Et l'expérience utilisateur est fluide
Scénario: Backup données abonnements
Étant donné que les données d'abonnements sont critiques
Quand un backup est effectué
Alors PostgreSQL est répliqué en temps réel sur un replica
Et un snapshot quotidien est stocké sur S3
Et en cas de crash, les données peuvent être restaurées <5 minutes

View File

@@ -0,0 +1,233 @@
# language: fr
Fonctionnalité: Multi-devices Premium - Dernier device prend priorité (KISS)
En tant que système Premium
Je veux appliquer une règle simple: le dernier device à démarrer prend toujours la priorité
Afin d'éviter la complexité des exceptions temporelles et géographiques
Contexte:
Étant donné qu'un utilisateur "UserA" a un abonnement Premium actif
Et que l'utilisateur possède 2 devices:
| device | type |
| iPhone_123 | iOS |
| iPad_456 | iOS |
# Règle KISS : Dernier device = Priorité
Scénario: Transition normale - Device 2 démarre après Device 1
Étant donné que "UserA" écoute un contenu sur iPhone_123
Et que le heartbeat iPhone_123 est actif (toutes les 30s)
Quand "UserA" démarre la lecture sur iPad_456 (5 secondes après)
Alors le serveur détecte une session active (iPhone_123)
Et envoie un WebSocket close à iPhone_123
Et iPhone_123 affiche le message:
"""
Lecture interrompue
Votre compte est utilisé sur un autre appareil.
Un seul appareil peut écouter à la fois avec votre compte Premium.
Le partage de compte viole nos CGU et peut entraîner une suspension.
[Reprendre ici] [Sécuriser mon compte]
"""
Et Redis met à jour active_streams:UserA = {device_id: "iPad_456", started_at: timestamp}
Et la lecture démarre sur iPad_456
# Offline connecté internet
Scénario: Device 1 offline MAIS connecté WiFi - Device 2 coupe Device 1
Étant donné que "UserA" écoute un contenu téléchargé en offline sur iPhone_123
Mais que iPhone_123 est connecté au WiFi
Et que le heartbeat iPhone_123 est envoyé toutes les 30s
Quand "UserA" démarre la lecture online sur iPad_456
Alors le serveur détecte une session active (iPhone_123, mode offline)
Et envoie un WebSocket close à iPhone_123
Et iPhone_123 affiche le même message d'interruption
Et iPad_456 démarre normalement
# Offline déconnecté internet (exception technique)
Scénario: Device 1 vraiment offline (mode avion) - Pas de détection possible
Étant donné que "UserA" écoute un contenu offline sur iPhone_123 en mode avion
Et qu'aucun heartbeat n'est envoyé (pas de connexion internet)
Quand "UserA" démarre la lecture online sur iPad_456
Alors le serveur ne détecte AUCUNE session active
Car aucun heartbeat depuis iPhone_123
Et iPad_456 démarre normalement
Et iPhone_123 continue en offline (pas de coupure possible)
Et c'est une exception technique acceptable ("Tant pis")
Scénario: Device 1 offline reconnecte - Heartbeat reprend
Étant donné que "UserA" écoutait offline sur iPhone_123 en mode avion
Quand iPhone_123 sort du mode avion et se connecte au WiFi
Alors le heartbeat reprend immédiatement
Et Redis crée/met à jour active_streams:UserA = {device_id: "iPhone_123", started_at: timestamp}
Et si iPad_456 démarre, iPhone_123 sera coupé normalement
# Heartbeat et TTL Redis
Scénario: Heartbeat toutes les 30s maintient la session active
Étant donné que "UserA" écoute sur iPhone_123
Quand iPhone_123 envoie un heartbeat toutes les 30 secondes
Alors Redis met à jour active_streams:UserA avec TTL 5 minutes
Et la session reste active tant que les heartbeats arrivent
Scénario: Pas de heartbeat pendant 5 min - Session expirée
Étant donné que "UserA" écoutait sur iPhone_123
Mais que iPhone_123 a été éteint brutalement (pas de heartbeat final)
Quand 5 minutes se sont écoulées sans heartbeat
Alors Redis expire automatiquement l'entrée active_streams:UserA (TTL)
Et iPad_456 peut démarrer sans détecter de conflit
# Boutons message coupure
Scénario: Bouton "Reprendre ici" sur device coupé
Étant donné que iPhone_123 a été coupé par iPad_456
Et que le message d'interruption est affiché sur iPhone_123
Quand "UserA" clique sur [Reprendre ici] sur iPhone_123
Alors le serveur détecte que iPad_456 est actif
Et envoie un WebSocket close à iPad_456
Et iPhone_123 reprend la lecture
Et iPad_456 affiche le même message d'interruption
Scénario: Bouton "Sécuriser mon compte" sur device coupé
Étant donné que iPhone_123 a été coupé par iPad_456
Et que le message d'interruption est affiché sur iPhone_123
Quand "UserA" clique sur [Sécuriser mon compte]
Alors l'utilisateur est redirigé vers:
- Page changement mot de passe
- Option "Déconnecter tous les appareils"
Et un email de sécurité est envoyé
Et un log de sécurité est créé (suspicion piratage)
# Limite offline 30 jours
Scénario: Contenus offline expirés >30j - Force reconnexion
Étant donné que "UserA" a téléchargé 50 contenus il y a 32 jours
Et qu'il n'a pas reconnecté son device depuis 32 jours
Quand "UserA" essaie d'écouter un contenu offline
Alors un message s'affiche:
"""
Contenus expirés (>30 jours)
Reconnectez-vous au WiFi pour renouveler vos contenus.
"""
Et la lecture est bloquée
Et cela force la reconnexion (détection multi-devices redevient possible)
Scénario: Notification J-3 avant expiration contenus offline
Étant donné que "UserA" a des contenus offline qui expirent dans 3 jours
Quand le système détecte l'approche de l'expiration
Alors une notification push est envoyée:
"""
10 contenus offline expirent dans 3 jours
Reconnectez-vous au WiFi pour renouveler automatiquement.
"""
# Détection abus post-MVP
Scénario: Monitoring patterns suspects - Flag modération manuelle
Étant donné que le système monitore les patterns d'utilisation
Quand "UserA" change de device >10 fois par jour pendant 7 jours
Alors un flag de suspicion est créé dans le dashboard modération
Et un email d'investigation est envoyé à "UserA":
"""
Activité suspecte détectée sur votre compte
Nous avons détecté des changements fréquents d'appareil.
Si ce n'est pas vous, sécurisez votre compte immédiatement.
[Sécuriser mon compte] [Contacter le support]
"""
Mais aucune action automatique n'est prise (évite faux positifs)
Scénario: Connexions alternées villes éloignées - Flag modération
Étant donné que le système monitore les localisations approximatives
Quand "UserA" se connecte à Paris à 10h puis Marseille à 10h30 (même jour)
Et que ce pattern se répète 5 fois en 1 semaine
Alors un flag de suspicion est créé
Et un modérateur humain enquête manuellement
Mais aucun blocage automatique (évite faux positifs: TGV légitime)
Scénario: Partage de compte confirmé - Suspension 7 jours
Étant donné qu'un modérateur confirme manuellement un partage de compte
Et que les preuves sont claires (signalements users + patterns suspects)
Quand le modérateur applique la sanction
Alors le compte est suspendu 7 jours
Et un email de warning est envoyé:
"""
Suspension de compte (7 jours)
Le partage de compte Premium viole nos CGU.
Votre compte est suspendu jusqu'au [Date].
En cas de récidive, le compte sera définitivement fermé.
"""
Scénario: Récidive partage de compte - Ban définitif
Étant donné qu'un compte a déjà été suspendu 7 jours pour partage
Et que le partage continue après la réactivation
Quand le modérateur confirme la récidive
Alors le compte est définitivement fermé (ban)
Et un email de fermeture définitive est envoyé
Et le remboursement pro-rata de l'abonnement est effectué
# Cas d'usage réels
Scénario: User change de pièce (voiture → maison) rapidement
Étant donné que "UserA" écoute dans sa voiture sur iPhone_123
Quand il arrive chez lui et passe sur iPad_456 après 30 secondes
Alors iPhone_123 est coupé immédiatement
Et iPad_456 prend le relai
Et l'utilisateur comprend le message (device unique)
Scénario: User oublie son téléphone en lecture et part
Étant donné que "UserA" écoute sur iPhone_123
Et qu'il part en laissant iPhone_123 en lecture
Quand il démarre la lecture sur iPad_456 au travail
Alors iPhone_123 est coupé à distance (WebSocket close)
Et iPad_456 prend la priorité
Scénario: TGV Paris → Lyon (légitime) - Pas de faux positif
Étant donné que "UserA" prend le TGV Paris Lyon
Et qu'il écoute pendant tout le trajet sur iPhone_123
Quand le système détecte un changement de localisation (GPS)
Alors aucun flag de suspicion n'est créé
Car c'est un déplacement continu légitime
Et aucune action automatique n'est prise
# Implémentation technique Redis
Scénario: Stockage Redis - Structure active_streams
Étant donné que "UserA" démarre la lecture sur iPhone_123
Quand le heartbeat est envoyé au serveur
Alors Redis stocke:
| clé | valeur |
| active_streams:UserA | {"device_id": "iPhone_123", "started_at": 1707311400} |
Et un TTL de 5 minutes (300 secondes) est défini
Et la clé expire automatiquement si pas de heartbeat
Scénario: Mise à jour Redis à chaque heartbeat
Étant donné qu'une session active existe sur iPhone_123
Quand un heartbeat est envoyé toutes les 30 secondes
Alors Redis met à jour le TTL à 5 minutes
Et started_at reste inchangé (timestamp initial)
Et la session reste active indéfiniment tant que les heartbeats arrivent
# Justification KISS
Scénario: Comparaison avec règle complexe - Avantages KISS
Étant donné qu'on compare la règle KISS avec une règle complexe (exceptions temporelles, GPS, etc.)
Quand on évalue les avantages
Alors les avantages KISS sont:
| avantage | description |
| Simplicité technique | Pas de tracking GPS précis, pas de calcul distances |
| Pas de faux positifs | TGV légitime ne déclenche pas d'alerte automatique |
| Assume bonne foi | Majorité users honnêtes, gestion réactive suffit |
| Message dissuasif clair | Avertissement CGU dans message, option sécurité |
| Protection revenus créateurs | 1 abonnement = 1 personne = 1 écoute active |
| UX claire | User comprend immédiatement le comportement |

View File

@@ -0,0 +1,279 @@
# language: fr
Fonctionnalité: Multi-devices et détection simultanée
En tant qu'abonné Premium
Je veux utiliser mon compte sur plusieurs appareils
Mais limité à 1 seul stream actif à la fois pour éviter le partage abusif
Contexte:
Étant donné que je suis un utilisateur Premium actif
Et que mon compte est valide
# ===== LIMITE 1 STREAM ACTIF =====
Scénario: 1 seul stream actif autorisé par compte
Étant donné que je n'écoute rien actuellement
Quand je lance un contenu sur mon iPhone
Alors le stream démarre normalement
Et Redis enregistre: active_streams:{user_id} = {device_id: "iPhone", started_at: timestamp}
Scénario: Détection connexion simultanée - Arrêt premier device
Étant donné que j'écoute un contenu sur mon iPhone
Quand je lance un contenu sur mon iPad
Alors le système détecte une session active sur iPhone
Et la lecture sur iPhone est arrêtée immédiatement (WebSocket close)
Et je vois sur iPhone: "Lecture interrompue : votre compte est utilisé sur un autre appareil"
Et la lecture démarre sur iPad normalement
Scénario: Message explicite sur device interrompu
Étant donné que ma lecture sur iPhone vient d'être interrompue
Quand je regarde l'écran de mon iPhone
Alors je vois une overlay avec le message:
"""
🔴 Lecture interrompue
Votre compte Premium est utilisé sur un autre appareil.
Un seul stream actif est autorisé à la fois pour protéger
les revenus des créateurs et éviter le partage de compte.
"""
Et un bouton "Reprendre ici" est disponible
Scénario: Reprendre lecture sur device interrompu
Étant donné que ma lecture sur iPhone a été interrompue
Et que je veux reprendre sur iPhone
Quand je clique sur "Reprendre ici"
Alors la lecture démarre sur iPhone
Et l'iPad est à son tour interrompu avec le même message
Et le "ping-pong" entre devices est possible (mais pénible)
# ===== IMPLÉMENTATION TECHNIQUE REDIS =====
Scénario: Enregistrement session active dans Redis
Étant donné que je lance un contenu sur mon iPhone
Quand la lecture démarre
Alors une entrée Redis est créée:
```
Key: active_streams:{user_id}
Value: {
"device_id": "iPhone-ABC123",
"started_at": "2025-06-15T14:30:00Z",
"content_id": "xyz789"
}
TTL: 300 secondes (5 minutes)
```
Scénario: Heartbeat toutes les 30 secondes pour maintenir session
Étant donné que j'écoute un contenu sur mon iPhone
Quand 30 secondes s'écoulent
Alors l'app envoie un heartbeat au serveur
Et le serveur refresh le TTL Redis à 300 secondes
Et la session reste active
Scénario: Session considérée morte après 5 minutes sans heartbeat
Étant donné que j'écoute un contenu sur mon iPhone
Mais que l'app crash ou que le réseau coupe
Quand 5 minutes s'écoulent sans heartbeat
Alors l'entrée Redis expire automatiquement (TTL atteint)
Et je peux relancer sur n'importe quel device sans conflit
Scénario: Vérification session avant démarrage lecture
Étant donné que je veux lancer un contenu sur mon iPad
Quand j'appuie sur Play
Alors le serveur vérifie Redis: active_streams:{user_id}
Et si une session existe sur un autre device, elle est tuée
Et la nouvelle session iPad est enregistrée dans Redis
Scénario: Gestion multi-utilisateurs simultanés
Étant donné que 100 000 utilisateurs Premium écoutent simultanément
Quand Redis stocke 100 000 entrées active_streams
Alors chaque entrée a un TTL de 5 minutes
Et Redis gère facilement cette charge (~10 MB de RAM)
Et les vérifications sont quasi-instantanées (O(1))
# ===== EXCEPTIONS ET CAS PARTICULIERS =====
Scénario: Contenus téléchargés (offline) ne comptent pas comme stream
Étant donné que j'ai téléchargé 20 contenus en mode offline
Quand j'écoute un contenu téléchargé sur mon iPhone sans réseau
Alors aucune session active n'est enregistrée dans Redis
Et je peux écouter offline pendant qu'un autre device stream online
Car le contenu offline ne consomme pas de bande passante serveur
Scénario: Transition rapide device <10s tolérée
Étant donné que j'écoute dans ma voiture sur mon iPhone
Et que j'arrive chez moi
Quand je lance la lecture sur mon iPad dans les 10 secondes
Alors la transition est considérée comme un changement de device légitime
Et aucun message d'erreur n'est affiché sur iPhone
Et la lecture reprend exactement où j'étais sur iPad
Scénario: Détection transition rapide via timestamps
Étant donné que la session iPhone a started_at = 14:30:00
Quand je lance sur iPad à 14:30:05 (5 secondes après)
Alors le serveur détecte: diff = 5s < 10s
Et applique une "graceful transition" (pas de message d'erreur iPhone)
Et Redis met à jour: active_streams:{user_id} = {device_id: "iPad", ...}
Scénario: Plusieurs devices disponibles mais 1 seul actif
Étant donné que je possède:
| device | status |
| iPhone | Installé |
| iPad | Installé |
| MacBook (web) | Connecté |
| Android (conjoint)| Installé |
Quand je lance un stream sur n'importe quel device
Alors seulement 1 peut être actif à la fois
Et les autres devices sont en "standby"
# ===== JUSTIFICATIONS =====
Scénario: Justification anti-partage compte
Étant donné qu'un utilisateur Premium partage son compte avec un ami
Quand les 2 personnes essaient d'écouter simultanément
Alors la lecture est constamment interrompue sur l'un ou l'autre
Et l'expérience devient inutilisable
Et cela décourage fortement le partage de compte
Scénario: Justification protection revenus créateurs
Étant donné que 1 abonnement Premium = 4.99€/mois
Quand 70% sont reversés aux créateurs (3.49€)
Alors les créateurs sont rémunérés pour 1 personne
Et si 2 personnes utilisent le même compte simultanément, c'est injuste
Et la limite 1 stream protège l'équité du système
Scénario: Justification UX claire
Étant donné qu'un stream est interrompu sur un device
Quand l'utilisateur voit le message explicite
Alors il comprend immédiatement pourquoi (autre device actif)
Et il peut choisir de reprendre sur le device actuel ou l'autre
Et il n'y a pas de confusion ou frustration
Scénario: Comparaison avec Spotify (limite 1 stream)
Étant donné que Spotify Premium limite aussi à 1 stream actif
Quand RoadWave applique la même règle
Alors les utilisateurs connaissent déjà ce comportement
Et cela paraît normal et accepté par l'industrie
Scénario: Comparaison avec Netflix (plusieurs streams selon formule)
Étant donné que Netflix permet 1-4 streams selon la formule
Quand RoadWave limite à 1 stream pour tous
Alors c'est plus strict que Netflix
Mais Netflix cible le foyer familial (TV partagée)
Alors que RoadWave cible l'individu conducteur (usage personnel)
# ===== MONITORING ET DÉTECTION ABUS =====
Scénario: Détection pattern suspect - Changements devices fréquents
Étant donné qu'un utilisateur change de device 50 fois en 1 heure
Quand le système détecte ce pattern anormal
Alors une alerte est générée pour l'équipe modération
Et le compte peut être marqué pour surveillance
Et si abus confirmé, suspension possible
Scénario: Logs des changements de device
Étant donné que je change de device plusieurs fois par jour
Quand les changements sont loggés
Alors chaque événement est enregistré:
| timestamp | from_device | to_device | content_id |
| 2025-06-15 08:30:00 | null | iPhone | abc123 |
| 2025-06-15 09:15:00 | iPhone | iPad | def456 |
| 2025-06-15 18:30:00 | iPad | iPhone | ghi789 |
Et ces logs aident à détecter les partages de compte
Scénario: Métriques admin - Changements devices par utilisateur
Étant donné qu'un admin consulte les métriques de streaming
Quand il accède au dashboard
Alors il voit:
| métrique | valeur |
| Utilisateurs Premium actifs | 12,547 |
| Changements de device/jour (médiane) | 2 |
| Utilisateurs >10 changements/jour | 47 (0.4%) |
| Comptes suspects (>20 changements/j) | 3 |
Scénario: Email d'avertissement si changements excessifs
Étant donné que je change de device 30 fois par jour pendant 3 jours
Quand le système détecte ce pattern
Alors je reçois un email d'avertissement:
"""
Activité inhabituelle détectée sur votre compte
Nous avons détecté un nombre anormalement élevé de changements de device (30/jour).
Rappel: Le partage de compte Premium est interdit selon nos CGU.
Un seul stream actif est autorisé à la fois.
Si cette activité continue, votre compte pourra être suspendu.
"""
Scénario: Suspension compte après avertissement ignoré
Étant donné que j'ai reçu un email d'avertissement il y a 7 jours
Mais que je continue à changer de device 30 fois par jour
Quand l'équipe modération examine le compte
Alors mon compte Premium peut être suspendu pour partage abusif
Et je reçois un email de suspension avec justification
# ===== SUPPORT UTILISATEUR =====
Scénario: FAQ - Pourquoi ma lecture s'arrête quand j'utilise un autre device ?
Étant donné que je consulte la FAQ Premium
Quand je cherche "lecture interrompue"
Alors je trouve la réponse:
"""
Q: Pourquoi ma lecture s'arrête quand j'utilise un autre appareil ?
R: Votre abonnement Premium autorise 1 seul stream actif à la fois.
Si vous lancez la lecture sur un autre appareil, le premier est automatiquement arrêté.
Pourquoi cette limite ?
- Protéger les revenus des créateurs (1 abonnement = 1 personne)
- Éviter le partage de compte abusif
Vous pouvez utiliser autant d'appareils que vous voulez, mais un seul peut lire à la fois.
"""
Scénario: Support - Utilisateur pense être piraté
Étant donné qu'un utilisateur voit constamment "Lecture interrompue"
Et qu'il pense que son compte est piraté
Quand il contacte le support
Alors le support vérifie les logs de changements de device
Et peut identifier les devices (iPhone, iPad perso vs iPhone inconnu)
Et conseille de changer le mot de passe si device inconnu détecté
Scénario: Changement mot de passe déconnecte tous les devices
Étant donné que je pense que mon compte est compromis
Quand je change mon mot de passe
Alors tous mes devices sont déconnectés immédiatement
Et les sessions actives dans Redis sont supprimées
Et je dois me reconnecter sur chaque device
Et cela sécurise mon compte
# ===== TESTS TECHNIQUES =====
Scénario: Test charge - 100 000 vérifications/seconde
Étant donné que 100 000 utilisateurs Premium lancent des contenus
Quand chaque lancement vérifie Redis (GET active_streams:{user_id})
Alors Redis peut gérer facilement 100 000 requêtes/seconde
Et le temps de réponse moyen est <1ms
Et aucun ralentissement n'est constaté
Scénario: Test failover Redis
Étant donné que le serveur Redis principal tombe en panne
Quand le failover automatique vers le replica Redis s'active
Alors les sessions actives peuvent être perdues temporairement (max 5 min)
Mais les utilisateurs peuvent relancer immédiatement
Et l'impact est minimal (pas de perte de données critiques)
Scénario: Test concurrence - Lancement simultané 2 devices
Étant donné que je lance exactement au même instant sur iPhone et iPad
Quand les 2 requêtes arrivent en parallèle au serveur
Alors Redis utilise un lock (SETNX) pour atomicité
Et 1 seul device gagne (par exemple iPhone)
Et l'autre device (iPad) reçoit immédiatement une erreur
Et l'utilisateur peut retry sur iPad si souhaité
Scénario: Nettoyage automatique sessions expirées
Étant donné que 1000 sessions Redis ont expiré (TTL atteint)
Quand Redis supprime automatiquement ces entrées
Alors la mémoire est libérée
Et les nouveaux streams peuvent démarrer sans conflit
Et aucune intervention manuelle n'est nécessaire

View File

@@ -0,0 +1,254 @@
# language: fr
Fonctionnalité: Offre et tarification Premium
En tant qu'utilisateur
Je veux pouvoir souscrire à un abonnement Premium
Afin de profiter d'une expérience sans publicité avec des avantages exclusifs
Contexte:
Étant donné que l'API RoadWave est disponible
Et que je suis connecté en tant qu'utilisateur
# ===== FORMULES DISPONIBLES =====
Scénario: Formule mensuelle à 4.99€/mois
Étant donné que je consulte les offres Premium
Quand je vois la formule mensuelle
Alors le prix affiché est 4.99/mois
Et il n'y a aucune réduction
Et le prix effectif par mois est 4.99
Scénario: Formule annuelle à 49.99€/an (2 mois offerts)
Étant donné que je consulte les offres Premium
Quand je vois la formule annuelle
Alors le prix affiché est 49.99/an
Et l'économie affichée est "2 mois offerts"
Et le prix effectif par mois est 4.16
Et je vois le badge "Meilleure offre"
Scénario: Calcul économie formule annuelle
Étant donné que la formule mensuelle coûte 4.99/mois
Quand je calcule le coût annuel en mensuel
Alors 12 mois × 4.99 = 59.88/an
Et la formule annuelle coûte 49.99
Et l'économie est de 9.89 ( 2 mois gratuits)
Et la réduction est de 16.5%
Scénario: Pas d'essai gratuit disponible
Étant donné que je consulte les offres Premium
Quand je recherche une option "Essai gratuit"
Alors aucune option d'essai gratuit n'est proposée
Et je dois payer dès le premier jour pour accéder au Premium
Scénario: Justification absence essai gratuit - Anti-abus vacances
Étant donné que RoadWave ne propose pas d'essai gratuit
Quand un utilisateur envisage un road trip de 14 jours
Alors il ne peut pas s'abonner pour l'essai gratuit puis annuler
Et cela évite les inscriptions opportunistes
Et protège les revenus des créateurs
Scénario: Justification absence essai gratuit - Protection revenus créateurs
Étant donné qu'un utilisateur Premium écoute des contenus
Quand il génère des écoutes dès le jour 1
Alors les créateurs sont rémunérés immédiatement (70% de 4.99)
Et il n'y a pas de "période gratuite" sans rémunération créateurs
Scénario: Justification absence essai gratuit - Simplicité
Étant donné que RoadWave gère les abonnements
Quand il n'y a pas d'essai gratuit
Alors pas de gestion complexe de période trial
Et pas de workflow de conversion trial payant
Et cela réduit la complexité technique
Scénario: Justification absence essai gratuit - Engagement
Étant donné qu'un utilisateur paie dès le début
Quand il souscrit à Premium
Alors il est plus engagé qu'un utilisateur en essai gratuit
Et le taux de churn est généralement plus faible
Et la lifetime value (LTV) est plus élevée
Scénario: Pas de partage familial au MVP
Étant donné que je consulte les offres Premium
Quand je recherche une option "Famille" ou "Partage"
Alors aucune option de partage familial n'est disponible
Et seuls les abonnements individuels sont proposés
Scénario: Justification absence partage familial - Complexité technique
Étant donné que le partage familial nécessite:
| fonctionnalité | complexité |
| Gestion invitations | Moyenne |
| Validation liens famille | Moyenne |
| Limite devices par membre | Élevée |
| Dashboard admin famille | Élevée |
Quand RoadWave évalue le ROI
Alors le coût dev/support est trop élevé pour le MVP
Et la fonctionnalité est reportée post-MVP
Scénario: Justification absence partage familial - Risque abus
Étant donné qu'une offre famille permet 5-6 membres
Quand il n'y a pas de vérification stricte de lien familial
Alors des "familles" de 6 inconnus pourraient se former
Et cela réduirait fortement les revenus (6 personnes pour 1 abonnement)
Scénario: Justification absence partage familial - Cible individuelle
Étant donné que RoadWave cible principalement les conducteurs
Quand chaque conducteur utilise l'app individuellement en voiture
Alors le besoin de partage familial est limité
Et la plupart des utilisateurs sont des individus (pas des familles)
Scénario: Post-MVP - Offre Famille à 9.99€/mois pour 5 comptes
Étant donné que RoadWave envisage une offre Famille post-MVP
Quand la fonctionnalité est spécifiée
Alors le prix serait 9.99/mois pour 5 comptes
Et cela représente 2/mois/personne
Mais cette offre n'est pas disponible au MVP
Scénario: Comparaison tarif - Spotify à 10.99€/mois
Étant donné que Spotify Premium coûte 10.99/mois
Quand RoadWave fixe son prix à 4.99/mois
Alors RoadWave est 54.5% moins cher que Spotify
Et cela positionne RoadWave comme très accessible
Scénario: Comparaison tarif - YouTube Premium à 11.99€/mois
Étant donné que YouTube Premium coûte 11.99/mois
Quand RoadWave fixe son prix à 4.99/mois
Alors RoadWave est 58.4% moins cher que YouTube Premium
Et cela est un argument commercial fort
Scénario: Comparaison tarif - Apple Music à 10.99€/mois
Étant donné qu'Apple Music coûte 10.99/mois
Quand RoadWave fixe son prix à 4.99/mois
Alors RoadWave est 54.5% moins cher qu'Apple Music
Et cela attire les utilisateurs sensibles au prix
Scénario: Justification tarif bas - Cible conducteurs quotidiens
Étant donné que RoadWave cible les trajets quotidiens domicile-travail
Quand le prix est fixé à 4.99/mois
Alors c'est un budget raisonnable pour un conducteur
Et équivalent à ~1-2 cafés/mois
Et psychologiquement acceptable pour un usage quotidien
Scénario: Justification formule annuelle - Engagement long terme
Étant donné que la formule annuelle offre 2 mois gratuits
Quand un utilisateur souscrit pour 1 an
Alors il s'engage sur le long terme
Et RoadWave sécurise 49.99 de revenus immédiatement
Et le cash flow est amélioré
Scénario: Justification formule annuelle - Réduction churn
Étant donné qu'un utilisateur paie 49.99 pour l'année
Quand il envisage d'arrêter après 3 mois
Alors il a déjà payé pour 12 mois
Et il continuera probablement à utiliser l'app
Et le taux de churn est réduit significativement
Scénario: Affichage comparatif des deux formules
Étant donné que je consulte la page Premium
Quand je vois les deux formules côte à côte
Alors je vois:
```
MENSUEL ANNUEL Meilleure offre
4.99/mois 49.99/an
soit 4.16/mois
Engagement 1 mois
💰 2 mois offerts !
Économie: 9.89/an
[S'abonner] [S'abonner]
```
Scénario: Mise en avant formule annuelle
Étant donné que je consulte la page Premium
Quand je vois les deux formules
Alors la formule annuelle a un badge "Meilleure offre"
Et elle est visuellement mise en avant (bordure colorée, taille plus grande)
Et l'économie de 2 mois est affichée en gros
Et cela incite à choisir la formule annuelle
Scénario: Lien "Pourquoi pas d'essai gratuit ?" en FAQ
Étant donné que je consulte la page Premium
Quand je clique sur "FAQ"
Alors je vois une question "Pourquoi pas d'essai gratuit ?"
Et la réponse explique:
"""
RoadWave ne propose pas d'essai gratuit pour 3 raisons:
1. Protection des créateurs: Vos écoutes rémunèrent les créateurs dès le premier jour.
2. Engagement: Un abonnement payant dès le début garantit une meilleure expérience.
3. Anti-abus: Éviter les inscriptions opportunistes (essai avant vacances puis annulation).
Le prix de 4.99/mois reste très accessible (moitié prix de Spotify/YouTube Premium).
"""
Scénario: A/B test formule annuelle (post-MVP)
Étant donné que RoadWave veut optimiser la conversion annuelle
Quand un A/B test est lancé
Alors groupe A voit "2 mois offerts" (économie en durée)
Et groupe B voit "Économisez 9.89" (économie en argent)
Et les taux de souscription sont mesurés
Et le message le plus performant est déployé
Scénario: Promo temporaire exceptionnelle (Black Friday, etc.)
Étant donné que c'est le Black Friday
Quand une promo temporaire est activée
Alors la formule annuelle peut passer à 39.99/an (au lieu de 49.99)
Et l'économie affichée est "4 mois offerts !"
Et la promo dure 3 jours uniquement
Et cela génère un pic de souscriptions
Scénario: Code promo partenariat influenceur
Étant donné qu'un influenceur promeut RoadWave
Quand il partage un code promo "INFLUENCEUR20"
Alors les utilisateurs obtiennent -20% sur le premier mois (3.99 au lieu de 4.99)
Et le code est valable 1 mois
Et les conversions sont trackées par code promo
Scénario: Statistiques admin - Répartition formules
Étant donné qu'un admin consulte les métriques d'abonnements
Quand il accède au dashboard
Alors il voit:
| métrique | valeur |
| Abonnés Premium total | 12,547 |
| Abonnés mensuels | 7,234 (58%)|
| Abonnés annuels | 5,313 (42%)|
| Revenus mensuels récurrents | 58,890 |
Et ces données aident à piloter la stratégie tarifaire
Scénario: Calcul revenus mensuels récurrents (MRR)
Étant donné que RoadWave a:
| formule | nombre abonnés | prix |
| Mensuel | 7,234 | 4.99/mois|
| Annuel | 5,313 | 49.99/an |
Quand le MRR est calculé
Alors MRR mensuel = 7,234 × 4.99 = 36,098
Et MRR annuel ramené au mois = 5,313 × 49.99 / 12 = 22,139
Et MRR total = 58,237/mois
Scénario: Projection revenus annuels (ARR)
Étant donné que le MRR est de 58,237
Quand l'ARR est calculé
Alors ARR = 58,237 × 12 = 698,844/an
Et cela aide à évaluer la valorisation de l'entreprise
Scénario: Affichage prix TTC (TVA incluse)
Étant donné que RoadWave est une plateforme française
Quand les prix sont affichés
Alors tous les prix sont TTC (TVA 20% incluse)
Et le prix 4.99 inclut déjà la TVA
Et cela respecte la réglementation française
Scénario: Performance page Premium avec cache
Étant donné que la page Premium est consultée fréquemment
Quand un utilisateur charge la page
Alors les prix et avantages sont servis depuis un cache CDN
Et le temps de chargement est <200ms
Et cela garantit une expérience fluide
Scénario: Localisation prix selon pays (post-MVP)
Étant donné que RoadWave se lance à l'international post-MVP
Quand un utilisateur se connecte depuis l'Allemagne
Alors les prix peuvent être ajustés (ex: 4.99 en France, 4.49 en Pologne)
Et cela respecte le pouvoir d'achat local
Mais cette fonctionnalité n'est pas au MVP (France uniquement)

View File

@@ -0,0 +1,223 @@
# language: fr
@api @premium @multi-device @mvp
Fonctionnalité: Détection et gestion des conflits de streaming multi-device Premium
En tant qu'abonné Premium
Je peux écouter sur plusieurs appareils (iPhone, iPad, Android, Web)
Mais un seul stream audio peut être actif à la fois
Afin de respecter les conditions d'abonnement Premium individuel
Contexte:
Étant donné un utilisateur avec abonnement Premium actif
Et plusieurs appareils enregistrés sur le compte :
| device_id | platform | nom | last_seen |
| iphone-123 | iOS | iPhone de Jean | 2026-02-03 14:00:00 |
| ipad-456 | iOS | iPad Pro | 2026-02-03 12:30:00 |
| android-789 | Android | Samsung Galaxy | 2026-02-02 18:00:00 |
| web-abc | Web | Chrome MacBook | 2026-02-03 10:00:00 |
# ============================================================================
# DÉTECTION STREAM ACTIF ET CONFLIT
# ============================================================================
Scénario: Premier stream - aucun conflit
Étant donné aucun stream n'est actuellement actif sur le compte
Quand l'utilisateur démarre la lecture sur "iPhone de Jean"
Alors le stream doit démarrer normalement
Et une session active doit être enregistrée dans Redis :
| user_id | user-123 |
| device_id | iphone-123 |
| started_at | 2026-02-03 14:00:00|
| content_id | content-xyz |
| stream_token | token-abc123 |
Et le TTL Redis doit être de 2 heures
Scénario: Tentative de stream simultané sur second appareil
Étant donné un stream actif sur "iPhone de Jean" depuis 10 minutes
Quand l'utilisateur tente de démarrer la lecture sur "iPad Pro"
Alors une réponse HTTP 409 Conflict doit être retournée
Et le message doit indiquer :
"""
Un contenu est déjà en cours de lecture sur "iPhone de Jean".
Voulez-vous arrêter la lecture sur cet appareil et continuer ici ?
"""
Et les options proposées doivent être :
| option | action |
| Continuer ici | kill_previous_session |
| Annuler | cancel_new_session |
Scénario: Utilisateur choisit "Continuer ici" - kill de l'ancienne session
Étant donné un stream actif sur "iPhone de Jean"
Et l'utilisateur tente de lire sur "iPad Pro"
Et le conflit est détecté
Quand l'utilisateur choisit "Continuer ici"
Alors l'ancienne session sur "iPhone de Jean" doit être terminée immédiatement
Et un message WebSocket doit être envoyé à "iPhone de Jean" :
"""
{"type": "stream_stopped", "reason": "playback_started_on_other_device", "device": "iPad Pro"}
"""
Et la lecture sur "iPhone de Jean" doit s'arrêter avec notification :
"""
Lecture arrêtée : un autre appareil utilise votre compte Premium.
"""
Et le nouveau stream sur "iPad Pro" doit démarrer normalement
Scénario: Utilisateur choisit "Annuler" - maintien de la session actuelle
Étant donné un stream actif sur "iPhone de Jean"
Et l'utilisateur tente de lire sur "iPad Pro"
Et le conflit est détecté
Quand l'utilisateur choisit "Annuler"
Alors la session sur "iPhone de Jean" doit continuer normalement
Et aucun stream ne doit démarrer sur "iPad Pro"
Et l'utilisateur doit être redirigé vers l'écran d'accueil sur "iPad Pro"
# ============================================================================
# GESTION AUTOMATIQUE DES SESSIONS EXPIRÉES
# ============================================================================
Scénario: Session expirée automatiquement après pause prolongée
Étant donné un stream actif sur "iPhone de Jean"
Et l'utilisateur met en pause à 14:00:00
Et le TTL Redis est configuré pour expirer après 30 minutes de pause
Quand il est 14:35:00 (>30 min de pause)
Alors la session Redis doit expirer automatiquement
Et l'utilisateur peut démarrer un nouveau stream sur n'importe quel appareil
Et aucun conflit ne doit être détecté
Scénario: Heartbeat maintient la session active pendant la lecture
Étant donné un stream actif sur "iPhone de Jean"
Quand l'application envoie un heartbeat toutes les 30 secondes
Alors le TTL Redis doit être renouvelé à 2 heures à chaque heartbeat
Et la session doit rester active tant que les heartbeats continuent
Et si 3 heartbeats consécutifs échouent, la session doit expirer
Scénario: Fermeture propre de l'application libère la session
Étant donné un stream actif sur "iPhone de Jean"
Quand l'utilisateur ferme proprement l'application (swipe kill ou logout)
Alors une requête "end_session" doit être envoyée à l'API
Et la session Redis doit être immédiatement supprimée
Et l'utilisateur peut démarrer un stream sur un autre appareil sans délai
# ============================================================================
# CAS LIMITES ET EDGE CASES
# ============================================================================
Scénario: Crash application sans fermeture propre
Étant donné un stream actif sur "iPhone de Jean"
Quand l'application crash sans envoyer "end_session"
Alors la session Redis reste active avec son TTL
Et après 2 minutes sans heartbeat, la session doit être marquée "stale"
Et un nouveau stream peut être démarré après 2 minutes sans heartbeat
Ou l'utilisateur peut forcer le kill via le conflit modal
Scénario: Connexion réseau perdue pendant stream
Étant donné un stream actif sur "iPhone de Jean"
Quand la connexion réseau est perdue pendant 5 minutes
Alors les heartbeats échouent mais l'app continue en buffer
Et après 3 heartbeats manqués (90 secondes), la session est considérée "stale"
Et un autre appareil peut démarrer un stream après 90 secondes
Scénario: Deux appareils tentent de démarrer simultanément (race condition)
Étant donné aucun stream actif
Quand "iPhone de Jean" et "iPad Pro" tentent de démarrer un stream à la même milliseconde
Alors le premier à obtenir le lock Redis doit réussir
Et le second doit recevoir un conflit 409
Et un mécanisme de lock distribué (Redis SET NX) doit être utilisé
Scénario: Utilisateur bascule rapidement entre appareils (<10s)
Étant donné un stream sur "iPhone de Jean"
Quand l'utilisateur kill la session et démarre sur "iPad Pro"
Et tente de redémarrer sur "iPhone de Jean" 5 secondes après
Alors le système doit détecter le conflit avec "iPad Pro"
Et proposer à nouveau de kill la session iPad
Et un délai de grâce de 5 secondes doit être respecté pour éviter les boucles
# ============================================================================
# GESTION UTILISATEUR GRATUIT (pas de multi-device)
# ============================================================================
Scénario: Utilisateur gratuit tente de streamer sur 2 appareils
Étant donné un utilisateur avec abonnement gratuit (pas Premium)
Et un stream actif sur "iPhone de Jean"
Quand l'utilisateur tente de lire sur "iPad Pro"
Alors une réponse HTTP 403 Forbidden doit être retournée
Et le message doit indiquer :
"""
Le multi-device nécessite un abonnement Premium.
Actuellement en lecture sur "iPhone de Jean".
Passez à Premium pour écouter sur plusieurs appareils.
"""
Et un bouton "Passer à Premium" doit être proposé
# ============================================================================
# INTERFACE ADMIN & GESTION DES CONFLITS
# ============================================================================
Scénario: Dashboard admin - voir sessions actives par utilisateur
Étant donné un administrateur connecté au dashboard
Quand l'admin recherche l'utilisateur "user-123"
Alors les sessions actives doivent être affichées :
| device_id | platform | started_at | content_id | duration |
| iphone-123 | iOS | 2026-02-03 14:00:00 | content-xyz | 15:30 |
Et l'admin doit pouvoir "Terminer la session" manuellement
Scénario: Support client - résolution conflit bloqué
Étant donné un utilisateur signale ne pas pouvoir lire sur aucun appareil
Et une session "fantôme" existe dans Redis (crash + heartbeat bloqué)
Quand le support client force la suppression de la session Redis
Alors la clé Redis "active_stream:user-123" doit être supprimée
Et l'utilisateur doit pouvoir redémarrer immédiatement
# ============================================================================
# MÉTRIQUES & MONITORING
# ============================================================================
Scénario: Logging des conflits pour analytics
Étant donné un conflit est détecté entre "iPhone de Jean" et "iPad Pro"
Quand le conflit est résolu par kill de session
Alors un événement analytics doit être loggé :
| event_type | stream_conflict_resolved |
| user_id | user-123 |
| previous_device | iphone-123 |
| new_device | ipad-456 |
| resolution | kill_previous |
| previous_session_duration | 900 |
| timestamp | 2026-02-03 14:15:00 |
Et ces métriques doivent être disponibles dans le dashboard
Scénario: Alerte monitoring - taux de conflits élevé
Étant donné le système monitore les conflits de stream
Quand le taux de conflits dépasse 15% des nouvelles sessions sur 1 heure
Alors une alerte Slack doit être envoyée à l'équipe technique
Et le message doit indiquer :
"""
Taux de conflits stream élevé : 18% (seuil : 15%)
Sessions impactées : 234 sur 1300
Action recommandée : vérifier expiration Redis et heartbeats
"""
# ============================================================================
# COMPATIBILITÉ AVEC D'AUTRES FEATURES
# ============================================================================
Scénario: Radio live avec conflit de stream
Étant donné un utilisateur écoute une radio live sur "iPhone de Jean"
Quand l'utilisateur démarre un stream sur "iPad Pro" et kill la session iPhone
Alors la radio live doit s'arrêter sur iPhone
Et le nouveau stream sur iPad peut être une radio live ou contenu normal
Et la progression audio-guide (si applicable) doit être sauvegardée
Scénario: Mode offline ne déclenche PAS de conflit
Étant donné un stream actif en ligne sur "iPhone de Jean"
Quand l'utilisateur écoute un contenu téléchargé en mode offline sur "iPad Pro"
Alors aucun conflit ne doit être détecté
Car le mode offline ne consomme pas de stream en ligne
Et les deux lectures peuvent coexister
Scénario: Multi-device avec partage familial (post-MVP)
Étant donné la fonctionnalité de partage familial est activée (post-MVP)
Et le compte principal a 3 profils famille
Quand chaque profil démarre un stream sur son appareil
Alors jusqu'à 3 streams simultanés doivent être autorisés
Et la détection de conflit doit s'appliquer par profil (1 stream/profil)

View File

@@ -0,0 +1,57 @@
# language: fr
@api @premium @pricing @mvp
Fonctionnalité: Tarification différenciée multi-canal
En tant que plateforme
Je veux différencier les tarifs selon le canal d'acquisition
Afin d'optimiser la monétisation et les marges
Scénario: Tarif standard sur le web
Étant donné un utilisateur sur roadwave.fr (web)
Quand il consulte les tarifs Premium
Alors il voit:
| Offre | Prix mensuel | Prix annuel |
| Premium | 4.99 | 49.90 |
Et aucun frais de plateforme
Et un événement "PRICING_WEB_DISPLAYED" est enregistré
Scénario: Tarif majoré sur iOS (In-App Purchase)
Étant donné un utilisateur sur l'app iOS
Quand il consulte les tarifs Premium
Alors il voit:
| Offre | Prix mensuel | Prix annuel |
| Premium | 5.99 | 59.99 |
Et la majoration compense la commission Apple (30%)
Et un événement "PRICING_IOS_DISPLAYED" est enregistré
Scénario: Tarif majoré sur Android (Google Play)
Étant donné un utilisateur sur l'app Android
Alors il voit:
| Offre | Prix mensuel | Prix annuel |
| Premium | 5.99 | 59.99 |
Et la majoration compense la commission Google (15-30%)
Et un événement "PRICING_ANDROID_DISPLAYED" est enregistré
Scénario: Redirection vers le web pour optimiser le coût
Étant donné un utilisateur sur mobile
Quand il clique sur "S'abonner"
Alors un message suggère: "Économisez 1 en vous abonnant sur notre site web"
Et un lien direct vers roadwave.fr/premium
Et un événement "WEB_SUBSCRIPTION_SUGGESTED" est enregistré
Scénario: Gestion des abonnements multi-plateformes
Étant donné un utilisateur abonné via iOS
Quand il se connecte sur Android
Alors son abonnement est reconnu et actif
Et synchronisé automatiquement
Et un événement "CROSS_PLATFORM_SUBSCRIPTION_SYNCED" est enregistré
Scénario: Métriques de conversion par canal
Étant donné que 1000 abonnements ont été souscrits
Alors la répartition par canal est:
| Canal | Abonnements | Taux conversion | Revenu moyen |
| Web | 450 (45%) | 8% | 49.90 |
| iOS | 350 (35%) | 6% | 59.99 |
| Android | 200 (20%) | 5% | 59.99 |
Et les métriques sont exportées vers le monitoring

View File

@@ -0,0 +1,83 @@
# language: fr
@api @premium @payment @mvp
Fonctionnalité: Webhooks et retry automatique des paiements
En tant que système de paiement
Je veux gérer les échecs de paiement avec retry intelligent
Afin de maximiser les conversions et minimiser le churn
Scénario: Webhook Mangopay de paiement réussi
Étant donné un abonnement Premium en cours
Quand Mangopay envoie un webhook "PAYIN_NORMAL_SUCCEEDED"
Alors le système enregistre le paiement
Et l'abonnement est prolongé de 30 jours
Et un email de confirmation est envoyé
Et un événement "PAYMENT_SUCCESS_WEBHOOK_RECEIVED" est enregistré
Scénario: Webhook de paiement échoué
Étant donné un abonnement Premium
Quand Mangopay envoie un webhook "PAYIN_NORMAL_FAILED"
Alors le système programme un retry automatique
Et l'utilisateur est notifié: "Échec de paiement. Nous réessayerons dans 3 jours."
Et un événement "PAYMENT_FAILED_WEBHOOK_RECEIVED" est enregistré
Scénario: Retry automatique après 3 jours
Étant donné un paiement échoué il y a 3 jours
Quand le système tente un retry automatique
Alors une nouvelle tentative de prélèvement est lancée
Et l'utilisateur reçoit un email: "Nouvelle tentative de paiement en cours"
Et un événement "PAYMENT_RETRY_ATTEMPTED" est enregistré
Scénario: Série de retries intelligents (3, 7, 14 jours)
Étant donné un premier échec de paiement
Alors le système programme:
| Retry | Délai | Statut abonnement |
| 1 | J+3 | Actif |
| 2 | J+7 | Actif |
| 3 | J+14 | Suspendu |
Et après le 3ème échec, l'abonnement est annulé
Et un événement "PAYMENT_RETRY_SERIES_CONFIGURED" est enregistré
Scénario: Suspension de l'abonnement après 3 échecs
Étant donné 3 tentatives de paiement échouées
Quand le 3ème retry échoue
Alors l'abonnement est suspendu
Et l'utilisateur repasse en mode Free
Et un email explique comment mettre à jour la carte
Et un événement "SUBSCRIPTION_SUSPENDED_PAYMENT_FAILURE" est enregistré
Scénario: Webhook de carte expirée
Étant donné un abonnement avec carte expirant ce mois
Quand Mangopay envoie un webhook "CARD_EXPIRING"
Alors une notification est envoyée 30 jours avant
Et rappelée 7 jours avant
Et le jour de l'expiration
Et un événement "CARD_EXPIRY_WARNING_SENT" est enregistré
Scénario: Mise à jour de carte et reprise de l'abonnement
Étant donné un utilisateur avec abonnement suspendu
Quand il met à jour sa carte bancaire
Alors un paiement est immédiatement tenté
Et si succès, l'abonnement est réactivé
Et les jours perdus sont récupérés pro-rata
Et un événement "SUBSCRIPTION_REACTIVATED_AFTER_PAYMENT" est enregistré
Scénario: Webhooks de remboursement
Étant donné un utilisateur qui annule son abonnement
Et demande un remboursement (satisfait ou remboursé 30j)
Quand Mangopay envoie "PAYOUT_NORMAL_SUCCEEDED"
Alors le remboursement est enregistré
Et l'utilisateur reçoit confirmation
Et un événement "REFUND_WEBHOOK_PROCESSED" est enregistré
Scénario: Métriques de performance des retries
Étant donné que 1000 paiements ont échoué
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur |
| Taux de succès au 1er retry (J+3)| 45% |
| Taux de succès au 2ème retry (J+7)| 25% |
| Taux de succès au 3ème retry (J+14)| 10% |
| Taux de récupération global | 80% |
| Taux d'annulation définitive | 20% |
Et les métriques sont exportées vers le monitoring