# language: fr Fonctionnalité: API - Notifications géolocalisées et quota anti-spam En tant qu'API backend Je veux gérer les notifications de contenus géolocalisés Afin de respecter les quotas et détecter le bon timing Contexte: Étant donné que l'API RoadWave est disponible Et que Redis est accessible Et qu'un utilisateur "user123" existe en mode voiture # Calcul ETA et déclenchement notification Scénario: API calcule ETA vers point géolocalisé Étant donné qu'un contenu géolocalisé existe à la position (48.8584, 2.2945) Tour Eiffel Et que l'utilisateur roule à 50 km/h vers ce point Et qu'il est actuellement à 98 mètres Quand je POST /api/v1/geo-notifications/check-eta """json { "user_id": "user123", "current_lat": 48.8577, "current_lon": 2.2950, "speed_kmh": 50, "target_content_id": "content_tower" } """ Alors le statut de réponse est 200 Et la réponse contient: """json { "eta_seconds": 7, "distance_meters": 98, "should_notify": true, "notification_trigger": "eta_threshold" } """ Scénario: API déclenche notification 7 secondes avant arrivée Étant donné qu'un contenu géolocalisé existe au point GPS Et que l'ETA calculé est 7 secondes Quand je POST /api/v1/geo-notifications/trigger """json { "user_id": "user123", "content_id": "content_tower", "eta_seconds": 7 } """ Alors le statut de réponse est 201 Et une notification est envoyée au mobile Et la notification contient: | champ | valeur | | type | geo_anchored | | countdown | 7 | | icon | 🏛️ (selon type contenu) | | sound | bip court | Scénario: API ne déclenche pas si ETA >7 secondes Étant donné qu'un contenu géolocalisé existe Et que l'ETA calculé est 10 secondes Quand je POST /api/v1/geo-notifications/check-eta Alors le statut de réponse est 200 Et la réponse contient: """json { "eta_seconds": 10, "should_notify": false, "reason": "eta_above_threshold" } """ Et aucune notification n'est envoyée Scénario: API déclenche notification immédiate si vitesse faible et proche Étant donné qu'un contenu géolocalisé existe Et que l'utilisateur roule à 3 km/h Et qu'il est à 40 mètres du point Quand je POST /api/v1/geo-notifications/check-eta Alors le statut de réponse est 200 Et la réponse contient: """json { "eta_seconds": 0, "distance_meters": 40, "speed_kmh": 3, "should_notify": true, "notification_trigger": "proximity_and_low_speed" } """ Et une notification est envoyée immédiatement # Quota anti-spam Scénario: API vérifie quota avant notification (6 contenus max/heure) Étant donné que l'utilisateur a déjà reçu 5 notifications géo dans la dernière heure Quand je POST /api/v1/geo-notifications/check-quota """json { "user_id": "user123" } """ Alors le statut de réponse est 200 Et la réponse contient: """json { "quota_available": true, "notifications_count": 5, "quota_limit": 6, "remaining": 1 } """ Scénario: API refuse notification si quota atteint (6/6) Étant donné que l'utilisateur a déjà reçu 6 notifications géo dans la dernière heure Quand je POST /api/v1/geo-notifications/trigger """json { "user_id": "user123", "content_id": "content_x" } """ Alors le statut de réponse est 429 Et la réponse contient: """json { "error": "QUOTA_EXCEEDED", "message": "Quota horaire de notifications géolocalisées atteint (6/6)", "notifications_count": 6, "quota_limit": 6, "reset_in_seconds": 1800 } """ Et aucune notification n'est envoyée Scénario: API incrémente quota après notification envoyée Étant donné que l'utilisateur a reçu 3 notifications géo Quand je POST /api/v1/geo-notifications/trigger (succès) Alors le quota passe à 4/6 Et en Redis, la clé "user:user123:geo_quota" contient 4 entrées Et le sorted set contient les timestamps des 4 notifications Scénario: API utilise rolling window pour quota horaire Étant donné que l'utilisateur a reçu 6 notifications: | timestamp | | 10:00:00 | | 10:15:00 | | 10:30:00 | | 10:45:00 | | 11:00:00 | | 11:05:00 | Et que l'heure actuelle est 11:10:00 Quand je vérifie le quota Alors les notifications avant 10:10:00 sont expirées Et seules 4 notifications sont comptées (10:15, 10:30, 10:45, 11:00, 11:05 = 5) Et le quota disponible est 1/6 Scénario: API exclut séquences d'audio-guide multi-séquences du quota Étant donné qu'un audio-guide "Visite Louvre" a 12 séquences Et que l'utilisateur a écouté les 12 séquences Quand je calcule le quota Alors l'audio-guide compte pour 1 seule notification Et les 12 séquences ne consomment pas 12 quota Et le quota utilisé est 1/6 # Cooldown après ignorance Scénario: API active cooldown 10 min si notification ignorée Étant donné qu'une notification géo a été envoyée à 10:00:00 Et que l'utilisateur n'a pas appuyé sur "Suivant" dans les 7 secondes Quand je POST /api/v1/geo-notifications/ignored """json { "user_id": "user123", "notification_id": "notif123" } """ Alors le statut de réponse est 200 Et un cooldown de 10 minutes est activé Et en Redis, la clé "user:user123:geo_cooldown" est créée Et le TTL est 600 secondes (10 minutes) Scénario: API refuse notification si cooldown actif Étant donné qu'un cooldown est actif depuis 5 minutes Et qu'il reste 5 minutes de cooldown Quand je POST /api/v1/geo-notifications/trigger Alors le statut de réponse est 429 Et la réponse contient: """json { "error": "COOLDOWN_ACTIVE", "message": "Cooldown actif après notification ignorée", "cooldown_remaining_seconds": 300 } """ Et aucune notification n'est envoyée Scénario: API autorise notification après expiration cooldown Étant donné qu'un cooldown a été activé à 10:00:00 Et que l'heure actuelle est 10:11:00 (11 minutes après) Quand je POST /api/v1/geo-notifications/check-quota Alors le cooldown a expiré Et les notifications sont à nouveau autorisées # Tracking GPS temps réel Scénario: API reçoit positions GPS toutes les secondes Étant donné que le mobile envoie des positions GPS Quand je POST /api/v1/gps/track """json { "user_id": "user123", "latitude": 48.8577, "longitude": 2.2950, "speed_kmh": 50, "timestamp": "2026-02-02T10:30:00Z" } """ Alors le statut de réponse est 200 Et la position est stockée dans l'historique GPS (Redis) Et l'historique conserve les 30 derniers points Scénario: API calcule vitesse moyenne sur 30 derniers points Étant donné que l'historique GPS contient 30 points Et que les vitesses enregistrées sont variables Quand je GET /api/v1/gps/average-speed Alors le statut de réponse est 200 Et la réponse contient: """json { "average_speed_kmh": 48.5, "samples_count": 30, "period_seconds": 30 } """ Scénario: API détecte contenus géolocalisés dans rayon 500m Étant donné que l'utilisateur est à la position (48.8577, 2.2950) Et qu'un contenu géolocalisé existe à 300m Quand je GET /api/v1/geo-notifications/nearby """json { "user_id": "user123", "latitude": 48.8577, "longitude": 2.2950, "radius_meters": 500 } """ Alors le statut de réponse est 200 Et la réponse contient: """json { "nearby_contents": [ { "id": "content_tower", "distance_meters": 300, "eta_seconds": 21, "should_notify_soon": false } ] } """ # Validation contenu géolocalisé Scénario: API valide que contenu géolocalisé accepté Étant donné qu'une notification géo a été envoyée Et que l'utilisateur a appuyé sur "Suivant" dans les 7 secondes Quand je POST /api/v1/geo-notifications/accepted """json { "user_id": "user123", "notification_id": "notif123", "content_id": "content_tower" } """ Alors le statut de réponse est 200 Et le contenu géolocalisé est inséré en priorité Et un décompte de 5 secondes est déclenché côté client Et le quota n'est PAS incrémenté (acceptation = pas de spam) Scénario: API enregistre contenu géolocalisé perdu Étant donné qu'une notification géo a été envoyée Et que l'utilisateur n'a pas appuyé sur "Suivant" Quand je POST /api/v1/geo-notifications/ignored Alors le contenu géolocalisé est perdu Et il n'est PAS inséré dans la file d'attente Et une métrique "geo_notification_ignored" est enregistrée Et le cooldown 10 min est activé # Métriques Scénario: API stocke métriques de notifications géolocalisées Étant donné que l'API envoie des notifications géo Quand je consulte les métriques Alors les données suivantes sont disponibles: | métrique | description | | geo_notifications_sent | Nombre total envoyées | | geo_notifications_accepted | Nombre acceptées (Suivant) | | geo_notifications_ignored | Nombre ignorées | | geo_acceptance_rate | % acceptation | | average_eta_seconds | ETA moyen au déclenchement | | quota_exceeded_count | Nombre de refus quota | # Redis structures Scénario: API utilise sorted set Redis pour quota Étant donné que l'utilisateur a reçu 3 notifications Quand je consulte Redis "user:user123:geo_quota" Alors la structure est un sorted set: """ ZADD user:user123:geo_quota 1707047400 "notif1" 1707047700 "notif2" 1707048000 "notif3" """ Et les scores sont les timestamps Unix Et le TTL est 3600 secondes (1 heure) Scénario: API compte notifications avec ZCOUNT Étant donné que le sorted set contient plusieurs notifications Et que l'heure actuelle est 11:00:00 (timestamp 1707048000) Quand l'API vérifie le quota Alors la commande Redis est: """ ZCOUNT user:user123:geo_quota 1707044400 1707048000 """ Et seules les notifications des 60 dernières minutes sont comptées # Gestion erreurs Scénario: API gère échec calcul ETA gracieusement Étant donné qu'une position GPS est invalide Quand je POST /api/v1/geo-notifications/check-eta """json { "user_id": "user123", "current_lat": 200, "current_lon": 300 } """ Alors le statut de réponse est 400 Et la réponse contient: """json { "error": "INVALID_GPS_COORDINATES", "message": "Coordonnées GPS invalides" } """ Scénario: API gère vitesse nulle sans crash Étant donné que l'utilisateur est à l'arrêt (vitesse = 0) Et qu'un contenu géolocalisé est à 100m Quand je POST /api/v1/geo-notifications/check-eta Alors l'ETA est incalculable (vitesse nulle) Et aucune notification n'est envoyée Et la réponse contient: """json { "should_notify": false, "reason": "speed_zero" } """ Plan du Scénario: Déclenchement selon ETA Étant donné qu'un contenu géolocalisé existe Et que l'ETA calculé est secondes Quand je vérifie si notification doit être envoyée Alors la décision est Exemples: | eta | decision | | 3 | notifier | | 5 | notifier | | 7 | notifier | | 8 | ne pas notifier | | 10 | ne pas notifier | | 15 | ne pas notifier | Plan du Scénario: Quota selon nombre notifications Étant donné que l'utilisateur a reçu notifications dans l'heure Quand je vérifie le quota Alors le quota est Exemples: | count | status | | 0 | disponible | | 3 | disponible | | 5 | disponible | | 6 | atteint | | 7 | atteint | # Edge cases : haute vitesse Scénario: API calcule ETA correctement à 130 km/h Étant donné qu'un contenu géolocalisé existe Et que l'utilisateur roule à 130 km/h (36.1 m/s) Et qu'il est à 252 mètres du point Quand je POST /api/v1/geo-notifications/check-eta """json { "user_id": "user123", "speed_kmh": 130, "distance_meters": 252 } """ Alors le statut de réponse est 200 Et la réponse contient: """json { "eta_seconds": 7, "speed_ms": 36.1, "should_notify": true, "notification_trigger": "eta_threshold" } """ Et une notification est envoyée 252m avant le point Scénario: API calcule distance notification selon vitesse Étant donné qu'un contenu géolocalisé existe Quand l'utilisateur roule à 180 km/h (50 m/s) Alors la notification est déclenchée à 350 mètres avant le point Et après décompte 5s, user a parcouru 250m Et le contenu démarre 100m avant le point Et le système fonctionne même à vitesse extrême # Edge cases : multiples points proches Scénario: API gère multiples points géolocalisés proches (800m chacun) Étant donné que 3 châteaux existent espacés de 800m chacun | contenu | position_km | | Château A | 0 | | Château B | 0.8 | | Château C | 1.6 | Et que l'utilisateur roule à 50 km/h Quand une notification Château A est envoyée et acceptée Alors le quota passe à 1/6 Et aucun cooldown n'est activé (notification acceptée) Quand 57 secondes s'écoulent (temps pour parcourir 800m) Et que l'utilisateur atteint Château B Alors une notification Château B est envoyée Car le quota n'est pas atteint (1/6) Et il n'y a pas de cooldown actif Scénario: API active cooldown réduit après validations multiples Étant donné que l'utilisateur a validé 2 notifications consécutives Et que les notifications ont toutes été acceptées (clic "Suivant") Quand une 3ème notification est ignorée Alors le cooldown activé est 5 minutes (réduit) Et non 10 minutes (standard) Car l'utilisateur a montré de l'engagement précédemment Scénario: API ignore notifications si cooldown actif après ignorance Étant donné qu'une notification a été ignorée à 10:00:00 Et qu'un cooldown de 10 minutes est actif Et que 3 contenus géolocalisés existent à 10:05, 10:08, 10:11 Quand l'utilisateur passe devant le contenu à 10:05 Alors aucune notification n'est envoyée (cooldown actif, reste 5 min) Quand l'utilisateur passe devant le contenu à 10:08 Alors aucune notification n'est envoyée (cooldown actif, reste 2 min) Quand l'utilisateur passe devant le contenu à 10:11 Alors une notification est envoyée (cooldown expiré après 10 min) # Edge cases : mode stationnement Scénario: API détecte mode stationnement (vitesse < 1 km/h pendant 2 min) Étant donné que l'utilisateur roule à 50 km/h Quand la vitesse passe à 0.5 km/h (arrêt complet) Et que la vitesse reste < 1 km/h pendant 2 minutes consécutives Alors le mode "stationnement" est activé Et aucune notification géolocalisée n'est envoyée Et le système bascule automatiquement en mode piéton Et un événement "mode_stationnement_detected" est enregistré Scénario: API sort du mode stationnement quand vitesse > 5 km/h Étant donné que le mode stationnement est actif depuis 30 minutes Et que l'utilisateur était à l'arrêt près d'un château Quand la vitesse passe à 20 km/h pendant 10 secondes Alors le mode voiture est réactivé Et les notifications géolocalisées reprennent Et le quota horaire est vérifié avant nouvelle notification Scénario: API refuse notification si user arrêté longtemps près d'un point Étant donné qu'un contenu géolocalisé existe à 30 mètres Et que l'utilisateur est à l'arrêt (vitesse 0 km/h) depuis 3 minutes Quand le système détecte la proximité Alors le mode stationnement est actif Et aucune notification n'est envoyée Car l'utilisateur est probablement stationné (parking) Et pas en mode conduite