426 lines
17 KiB
Gherkin
426 lines
17 KiB
Gherkin
# 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
|