Files
roadwave/features/api/navigation/notifications-geolocalisees.feature
jpgiannetti ea77aa8ac7 feat(gherkin): ajouter features interactions et navigation
Couverture complète des règles métier 05-interactions-navigation.md :

API (Backend) :
- File d'attente : pré-calcul 5 contenus, recalcul auto (>10km, 10min, <3 contenus), invalidation, Redis cache
- Notifications géolocalisées : calcul ETA, déclenchement 7s avant, quota 6/h, cooldown 10min, tracking GPS
- Jauges d'intérêt : architecture services séparés (Calculation + Update), pattern addition points absolus, persistance Redis/PostgreSQL

UI (Frontend) :
- Mode piéton : notifications push arrière-plan, rayon 200m, permissions stratégie progressive, geofencing iOS/Android
- Basculement automatique voiture↔piéton : détection vitesse GPS, hysteresis 10s, transition transparente

Fichiers créés :
- features/api/navigation/file-attente.feature
- features/api/navigation/notifications-geolocalisees.feature
- features/ui/navigation/mode-pieton-notifications-push.feature

Fichiers enrichis :
- features/api/interest-gauges/evolution-jauges.feature (ajout scénarios architecture backend)
2026-02-02 22:41:00 +01:00

391 lines
13 KiB
Gherkin
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <eta> secondes
Quand je vérifie si notification doit être envoyée
Alors la décision est <decision>
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 <count> notifications dans l'heure
Quand je vérifie le quota
Alors le quota est <status>
Exemples:
| count | status |
| 0 | disponible |
| 3 | disponible |
| 5 | disponible |
| 6 | atteint |
| 7 | atteint |