Ajout de 47 features Gherkin (~650 scénarios) pour couvrir 100% des règles métier : - Authentification (5) : validation mot de passe, tentatives connexion, multi-device, 2FA, récupération - Audio-guides (12) : détection mode, création, navigation piéton/voiture, ETA, gestion points, progression - Navigation (5) : notifications minimalistes, décompte 5s, stationnement, historique, basculement auto - Création contenu (3) : image auto, restrictions modification, suppression - Radio live (2) : enregistrement auto, interdictions modération - Droits auteur (6) : fair use 30s, détection musique, signalements, sanctions, appels - Modération (9) : badges Bronze/Argent/Or, score fiabilité, utilisateur confiance, audit, anti-abus - Premium (2) : webhooks Mangopay, tarification multi-canal - Profil/Partage/Recherche (5) : badge vérifié, stats arrondies, partage premium, filtres avancés, carte Tous les scénarios incluent edge cases, métriques de performance et conformité RGPD. Couverture fonctionnelle MVP maintenant complète.
326 lines
16 KiB
Gherkin
326 lines
16 KiB
Gherkin
# language: fr
|
|
|
|
@api @search @geolocation @mvp
|
|
Fonctionnalité: Recherche géographique de contenus avec Nominatim
|
|
|
|
En tant qu'utilisateur de RoadWave
|
|
Je veux rechercher des contenus audio dans une zone géographique spécifique
|
|
En utilisant un nom de lieu (ville, monument, région) et un rayon de recherche
|
|
Afin de découvrir des contenus avant de me déplacer ou planifier un trajet
|
|
|
|
Contexte:
|
|
Étant donné un utilisateur authentifié
|
|
Et l'API Nominatim est accessible
|
|
|
|
# ============================================================================
|
|
# GEOCODAGE AVEC NOMINATIM (lieu → coordonnées GPS)
|
|
# ============================================================================
|
|
|
|
Scénario: Recherche par nom de ville (cas simple)
|
|
Étant donné l'utilisateur saisit "Paris" dans la recherche géographique
|
|
Quand le système interroge Nominatim avec la requête "Paris, France"
|
|
Alors Nominatim doit retourner les coordonnées :
|
|
| lat | 48.8566 |
|
|
| lon | 2.3522 |
|
|
| type | city |
|
|
| bbox | (48.815, 48.902, 2.224, 2.470) |
|
|
Et le système doit utiliser ces coordonnées comme centre de recherche
|
|
|
|
Scénario: Recherche par monument ou POI (Point of Interest)
|
|
Étant donné l'utilisateur saisit "Tour Eiffel"
|
|
Quand le système interroge Nominatim
|
|
Alors Nominatim doit retourner :
|
|
| lat | 48.8584 |
|
|
| lon | 2.2945 |
|
|
| display_name| Tour Eiffel, Paris, France |
|
|
| type | tourism |
|
|
Et ces coordonnées doivent être utilisées pour la recherche de contenus
|
|
|
|
Scénario: Recherche avec ambiguïté (plusieurs résultats)
|
|
Étant donné l'utilisateur saisit "Montmartre"
|
|
Quand le système interroge Nominatim
|
|
Alors plusieurs résultats doivent être retournés :
|
|
| display_name | lat | lon |
|
|
| Montmartre, Paris 18e, France | 48.8867 | 2.3431 |
|
|
| Montmartre, Saskatchewan, Canada | 49.3000 | -106.0 |
|
|
| Montmartre-de-Bretagne, Ille-et-Vilaine | 48.1867 | -1.1833|
|
|
Et l'utilisateur doit choisir dans une liste déroulante
|
|
Et le choix par défaut doit être le résultat français si détecté
|
|
|
|
Scénario: Recherche avec contexte géographique (biais local)
|
|
Étant donné l'utilisateur est actuellement à Lyon (GPS actif)
|
|
Et l'utilisateur saisit "Bellecour"
|
|
Quand le système interroge Nominatim avec viewbox="Lyon area"
|
|
Alors Nominatim doit prioriser les résultats proches de Lyon
|
|
Et "Place Bellecour, Lyon" doit apparaître en premier
|
|
Avant "Bellecour, Jura" ou d'autres homonymes
|
|
|
|
Scénario: Recherche par code postal
|
|
Étant donné l'utilisateur saisit "75001"
|
|
Quand le système interroge Nominatim
|
|
Alors Nominatim doit retourner le centre du 1er arrondissement de Paris
|
|
Et un rayon par défaut de 1km doit être appliqué
|
|
|
|
Scénario: Recherche invalide ou introuvable
|
|
Étant donné l'utilisateur saisit "Azertyuiopqsdfghjklm"
|
|
Quand le système interroge Nominatim
|
|
Alors Nominatim doit retourner 0 résultat
|
|
Et un message d'erreur doit être affiché :
|
|
"""
|
|
Aucun lieu trouvé pour "Azertyuiopqsdfghjklm".
|
|
Essayez un nom de ville, monument ou adresse.
|
|
"""
|
|
|
|
# ============================================================================
|
|
# RAYON DE RECHERCHE CONFIGURABLE
|
|
# ============================================================================
|
|
|
|
Scénario: Recherche avec rayon par défaut (5 km)
|
|
Étant donné l'utilisateur cherche "Louvre, Paris"
|
|
Et Nominatim retourne les coordonnées (48.8606, 2.3376)
|
|
Et aucun rayon n'est spécifié
|
|
Quand le système effectue la recherche de contenus
|
|
Alors un rayon par défaut de 5 km doit être appliqué
|
|
Et une requête PostGIS doit être exécutée :
|
|
"""
|
|
SELECT * FROM contents
|
|
WHERE ST_DWithin(location::geography, ST_MakePoint(2.3376, 48.8606)::geography, 5000)
|
|
"""
|
|
|
|
Scénario: Recherche avec rayon personnalisé (slider 1-50 km)
|
|
Étant donné l'utilisateur cherche "Lyon"
|
|
Et l'utilisateur ajuste le slider de rayon à 15 km
|
|
Quand le système effectue la recherche de contenus
|
|
Alors une requête PostGIS avec rayon 15000m doit être exécutée
|
|
Et tous les contenus dans un rayon de 15 km autour de Lyon doivent être retournés
|
|
|
|
Scénario: Rayon minimum (1 km) - recherche ultra locale
|
|
Étant donné l'utilisateur cherche "Place de la Concorde"
|
|
Et l'utilisateur définit le rayon à 1 km (minimum)
|
|
Quand le système effectue la recherche
|
|
Alors seuls les contenus très proches (<1 km) doivent être retournés
|
|
Et le nombre de résultats peut être très faible (0-10)
|
|
|
|
Scénario: Rayon maximum (50 km) - recherche large
|
|
Étant donné l'utilisateur cherche "Versailles"
|
|
Et l'utilisateur définit le rayon à 50 km (maximum)
|
|
Quand le système effectue la recherche
|
|
Alors tous les contenus dans un rayon de 50 km doivent être retournés
|
|
Et cela peut inclure Paris, banlieue et zones périphériques
|
|
Et un avertissement "Résultats nombreux, affinez votre recherche" peut s'afficher si >500 résultats
|
|
|
|
Scénario: Mise à jour temps réel du rayon avec slider
|
|
Étant donné l'utilisateur visualise les résultats pour "Bordeaux" avec rayon 10 km
|
|
Quand l'utilisateur ajuste le slider de 10 km à 20 km
|
|
Alors une nouvelle requête API doit être déclenchée automatiquement
|
|
Et les résultats doivent être mis à jour en temps réel
|
|
Et la carte (si affichée) doit ajuster le cercle de rayon
|
|
|
|
# ============================================================================
|
|
# FILTRES COMBINÉS AVEC RECHERCHE GÉO
|
|
# ============================================================================
|
|
|
|
Scénario: Recherche géo + filtre catégorie
|
|
Étant donné l'utilisateur cherche "Marseille" avec rayon 10 km
|
|
Et l'utilisateur filtre par catégorie "Tourisme"
|
|
Quand le système effectue la recherche
|
|
Alors seuls les contenus touristiques dans le rayon doivent être retournés
|
|
Et la requête SQL doit combiner :
|
|
"""
|
|
WHERE ST_DWithin(...) AND category_id IN (SELECT id FROM categories WHERE name = 'Tourisme')
|
|
"""
|
|
|
|
Scénario: Recherche géo + filtre type de contenu
|
|
Étant donné l'utilisateur cherche "Strasbourg" avec rayon 5 km
|
|
Et l'utilisateur filtre par type "Audio-guides"
|
|
Quand le système effectue la recherche
|
|
Alors seuls les audio-guides dans le rayon doivent être retournés
|
|
Et les podcasts et radios live doivent être exclus
|
|
|
|
Scénario: Recherche géo + filtre durée
|
|
Étant donné l'utilisateur cherche "Nice" avec rayon 15 km
|
|
Et l'utilisateur filtre par durée "Court (<10 min)"
|
|
Quand le système effectue la recherche
|
|
Alors seuls les contenus de <10 minutes dans le rayon doivent être retournés
|
|
|
|
Scénario: Recherche géo + mode Kids actif
|
|
Étant donné l'utilisateur cherche "Disneyland Paris" avec rayon 5 km
|
|
Et le mode Kids est activé (utilisateur 13-15 ans)
|
|
Quand le système effectue la recherche
|
|
Alors seuls les contenus "Tout public" doivent être retournés
|
|
Et les contenus 16+ et 18+ doivent être exclus automatiquement
|
|
|
|
Scénario: Recherche géo + filtre politique désactivé
|
|
Étant donné l'utilisateur cherche "Assemblée Nationale" avec rayon 2 km
|
|
Et l'utilisateur a désactivé les contenus politiques dans ses préférences
|
|
Quand le système effectue la recherche
|
|
Alors les contenus taggés "politique" doivent être exclus
|
|
Même s'ils sont géographiquement pertinents
|
|
|
|
# ============================================================================
|
|
# AFFICHAGE DES RÉSULTATS
|
|
# ============================================================================
|
|
|
|
Scénario: Résultats triés par distance croissante
|
|
Étant donné l'utilisateur cherche "Arc de Triomphe" avec rayon 3 km
|
|
Quand les résultats sont retournés
|
|
Alors ils doivent être triés par distance croissante :
|
|
| contenu | distance |
|
|
| Balade sur les Champs | 0.1 km |
|
|
| Histoire de l'Arc | 0.2 km |
|
|
| Quartier de l'Étoile | 0.5 km |
|
|
| Secrets du 16e | 2.8 km |
|
|
Et la distance doit être affichée pour chaque résultat
|
|
|
|
Scénario: Affichage carte avec marqueurs de contenus
|
|
Étant donné l'utilisateur cherche "Toulouse" avec rayon 10 km
|
|
Quand les résultats sont affichés en mode "Carte"
|
|
Alors une carte Leaflet doit être affichée
|
|
Et un marqueur doit être placé pour chaque contenu :
|
|
| contenu | lat | lon | icone |
|
|
| Capitole de Toulouse | 43.6045 | 1.4442 | pin-tourisme |
|
|
| Bords de Garonne | 43.5986 | 1.4330 | pin-nature |
|
|
Et un cercle représentant le rayon de 10 km doit être affiché
|
|
Et le centre doit être le point géocodé de Toulouse
|
|
|
|
Scénario: Clustering de marqueurs si résultats nombreux
|
|
Étant donné l'utilisateur cherche "Paris" avec rayon 20 km
|
|
Et la recherche retourne 350 contenus
|
|
Quand la carte est affichée
|
|
Alors les marqueurs proches doivent être groupés en clusters :
|
|
| cluster_center | nb_contenus |
|
|
| Centre Paris | 120 |
|
|
| La Défense | 45 |
|
|
| Bois de Vincennes | 18 |
|
|
Et en cliquant sur un cluster, le zoom doit s'approcher
|
|
Et révéler les marqueurs individuels
|
|
|
|
Scénario: Affichage liste + carte simultanés (vue hybride)
|
|
Étant donné l'utilisateur cherche "Nantes" avec rayon 8 km
|
|
Quand l'utilisateur active la vue "Hybride"
|
|
Alors la liste de résultats doit s'afficher à gauche (60% écran)
|
|
Et la carte doit s'afficher à droite (40% écran)
|
|
Et en cliquant sur un résultat dans la liste, le marqueur doit être highlighté sur la carte
|
|
Et vice-versa
|
|
|
|
# ============================================================================
|
|
# PERFORMANCES & OPTIMISATIONS
|
|
# ============================================================================
|
|
|
|
Scénario: Cache résultats recherche géo fréquente (Paris, Lyon, Marseille)
|
|
Étant donné l'utilisateur cherche "Paris" avec rayon 5 km
|
|
Quand la requête est exécutée pour la 1ère fois
|
|
Alors les résultats doivent être mis en cache Redis pendant 10 minutes
|
|
Et les requêtes suivantes identiques doivent utiliser le cache
|
|
Et un header "X-Cache: HIT" doit être retourné
|
|
|
|
Scénario: Index PostGIS pour performances requêtes spatiales
|
|
Étant donné la table "contents" contient 100 000 contenus
|
|
Quand une recherche géo est exécutée avec ST_DWithin
|
|
Alors un index GIST sur la colonne "location" doit être utilisé
|
|
Et le temps de réponse doit être <100ms (p95)
|
|
|
|
Scénario: Pagination résultats nombreux
|
|
Étant donné l'utilisateur cherche "Île-de-France" avec rayon 50 km
|
|
Et la recherche retourne 1200 contenus
|
|
Quand les résultats sont affichés
|
|
Alors seuls les 50 premiers résultats doivent être chargés initialement
|
|
Et un scroll infini doit charger les résultats suivants par batch de 50
|
|
Et le total "1200 contenus trouvés" doit être affiché en haut
|
|
|
|
# ============================================================================
|
|
# EDGE CASES & ERREURS
|
|
# ============================================================================
|
|
|
|
Scénario: Nominatim API indisponible (fallback)
|
|
Étant donné l'utilisateur cherche "Lille"
|
|
Quand l'API Nominatim retourne une erreur 503 (service unavailable)
|
|
Alors un message d'erreur doit être affiché :
|
|
"""
|
|
Le service de recherche géographique est temporairement indisponible.
|
|
Veuillez réessayer dans quelques instants.
|
|
"""
|
|
Et l'utilisateur doit pouvoir utiliser la recherche textuelle classique
|
|
|
|
Scénario: Recherche géo sans connexion Internet
|
|
Étant donné l'utilisateur est en mode offline
|
|
Quand l'utilisateur tente une recherche géographique
|
|
Alors un message doit indiquer :
|
|
"""
|
|
La recherche géographique nécessite une connexion Internet.
|
|
Vous pouvez parcourir les contenus téléchargés.
|
|
"""
|
|
|
|
Scénario: Rate limiting Nominatim (1 req/s)
|
|
Étant donné Nominatim impose un rate limit de 1 requête/seconde
|
|
Et l'utilisateur tape rapidement "Par" → "Pari" → "Paris"
|
|
Quand le système détecte plusieurs requêtes rapides
|
|
Alors un debounce de 500ms doit être appliqué
|
|
Et seule la dernière requête ("Paris") doit être envoyée à Nominatim
|
|
|
|
Scénario: Recherche avec caractères spéciaux ou injection SQL
|
|
Étant donné l'utilisateur saisit "Paris'; DROP TABLE contents; --"
|
|
Quand le système traite la requête
|
|
Alors les caractères spéciaux doivent être échappés
|
|
Et aucune injection SQL ne doit être possible
|
|
Et Nominatim doit retourner 0 résultat (lieu invalide)
|
|
|
|
# ============================================================================
|
|
# SAUVEGARDE DES RECHERCHES (max 5)
|
|
# ============================================================================
|
|
|
|
Scénario: Sauvegarde automatique d'une recherche géo
|
|
Étant donné l'utilisateur effectue une recherche "Bordeaux" avec rayon 10 km
|
|
Quand la recherche est validée
|
|
Alors elle doit être sauvegardée dans l'historique :
|
|
| search_query | Bordeaux |
|
|
| radius_km | 10 |
|
|
| lat | 44.8378 |
|
|
| lon | -0.5792 |
|
|
| timestamp | 2026-02-03 14:30:00 |
|
|
Et l'utilisateur peut la rappeler via "Recherches récentes"
|
|
|
|
Scénario: Limite de 5 recherches sauvegardées (FIFO)
|
|
Étant donné l'utilisateur a déjà 5 recherches sauvegardées
|
|
Quand l'utilisateur effectue une 6ème recherche "Montpellier"
|
|
Alors la recherche la plus ancienne doit être supprimée
|
|
Et "Montpellier" doit être ajoutée en 1ère position
|
|
Et la limite de 5 recherches doit être respectée
|
|
|
|
Scénario: Rejouer une recherche sauvegardée
|
|
Étant donné l'utilisateur a une recherche sauvegardée "Lyon - 15 km"
|
|
Quand l'utilisateur clique sur cette recherche dans l'historique
|
|
Alors la recherche doit être ré-exécutée avec les mêmes paramètres
|
|
Et les résultats actualisés doivent être affichés
|
|
Et le rayon slider doit être positionné à 15 km
|
|
|
|
# ============================================================================
|
|
# MÉTRIQUES & ANALYTICS
|
|
# ============================================================================
|
|
|
|
Scénario: Logging des recherches géographiques pour analytics
|
|
Étant donné l'utilisateur effectue une recherche "Grenoble" avec rayon 12 km
|
|
Quand la recherche est exécutée
|
|
Alors un événement analytics doit être loggé :
|
|
| event_type | geo_search_executed |
|
|
| search_query | Grenoble |
|
|
| radius_km | 12 |
|
|
| results_count | 87 |
|
|
| response_time_ms | 145 |
|
|
| cache_hit | false |
|
|
| user_id | user-123 |
|
|
| timestamp | 2026-02-03 14:30:00 |
|
|
Et ces données doivent alimenter le dashboard de monitoring
|
|
|
|
Scénario: Top recherches géographiques (analytics)
|
|
Étant donné le système analyse les recherches sur 30 jours
|
|
Quand le dashboard analytics est consulté
|
|
Alors les lieux les plus recherchés doivent être affichés :
|
|
| lieu | nb_recherches |
|
|
| Paris | 12450 |
|
|
| Lyon | 5632 |
|
|
| Marseille | 4521 |
|
|
| Toulouse | 3890 |
|
|
| Nice | 3124 |
|
|
Et ces données doivent guider la création de contenus ciblés
|