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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user