Méthode pour valider le flux complet login → catalogue → saisie spraying → sync sur un device réel contre une instance Ekylibre de test. Le focus est de dérisquer P6 avant pilote : confirmer que l'app marche bout en bout, et trancher la question ouverte d'idempotence sur
provider.id(cf. arch §11.1, CHANGELOG P6.4 — seul vrai blocker pour démarrer le pilote).Pré-requis supposés acquis : une instance Ekylibre (API v2 + catalogue seedé minimal, cf.
docs/P0-checklist.md§1.6). L'instance peut être locale (Docker,https://<tenant>.ekylibre.localhost) ou distante — et ngrok n'est pas obligatoire : voir §0.2 pour le détail par cas. Les tests curl (dont le blocker S4) tournent contre une instance locale sans tunnel (validé le 2026-05-30 contrehttps://demo.ekylibre.localhost).
Le dev client P1/P2 ne marche plus depuis P3 (WatermelonDB JSI),
P5.1 (expo-crypto) et P5.3a (@react-native-community/datetimepicker).
Si tu n'as pas rebuild depuis ces phases, le binaire installé sur le
device va crasher au démarrage.
nvm use # Node 24
pnpm install
pnpm doctor # vérifie la cohérence Expo SDK
pnpm build:dev:ios # ou build:dev:androidInstalle le .ipa / .apk sortant sur le device (Expo te donne un QR
code TestFlight ou un lien direct). Ne pas tenter avec Expo Go.
Deux modes. ngrok n'est PAS un pré-requis : il ne sert qu'à exposer une instance locale à un device physique en HTTPS public. Tout le reste (curl, simulateur, émulateur) marche sans tunnel.
A. Instance locale — recommandé pour le dev quotidien.
Ekylibre core tourne en local via son docker compose (reverse-proxy qui
sert chaque tenant en https://<tenant>.ekylibre.localhost, certificat de
dev). Setup côté repo core (hors de ce repo mobile) :
# Dans le repo ekylibre/ekylibre
docker compose up -d # web + db + reverse-proxy
# Création tenant + seed démo : les tâches rake EXACTES dépendent de la
# version d'Ekylibre core — se référer à son README. À la fin on dispose :
# - d'une URL https://<tenant>.ekylibre.localhost (API v2 incluse)
# - d'un compte de test (email + mot de passe)→ EKY_INSTANCE_URL = https://<tenant>.ekylibre.localhost (ex. demo).
B. Instance distante : VM/serveur Ekylibre joignable en HTTPS public
(cf. docs/P0-checklist.md §1.6). EKY_INSTANCE_URL = son URL. RAS.
Le piège du test local n'est pas l'instance, c'est qui lui parle.
*.ekylibre.localhost ne résout qu'en 127.0.0.1, sur la machine de dev.
| Depuis… | URL à utiliser | ngrok ? |
|---|---|---|
| curl (machine de dev) | https://<tenant>.ekylibre.localhost |
❌ non — c'est le cas de TOUS les tests curl : sanity §0.4, S4 idempotence, vérifs serveur S3/S5/S10. |
| Simulateur iOS | https://<tenant>.ekylibre.localhost |
❌ non — partage la pile réseau du Mac (ajouter le vhost à /etc/hosts si .localhost ne résout pas ; importer le cert de dev au trousseau). |
| Émulateur Android | adb reverse tcp:443 tcp:443 puis l'URL .localhost, ou https://10.0.2.2 |
❌ non — adb reverse mappe le loopback de l'hôte dans l'émulateur. |
| Device physique (Wi-Fi) | IP LAN de la machine (https://192.168.x.x) + instance acceptant ce host, ou tunnel |
.localhost + cert self-signed ne sont ni résolus ni valides depuis le téléphone. |
TL;DR
- Le blocker S4 et tous les tests curl : instance locale, sans ngrok.
- Scénarios UI sur simulateur/émulateur : instance locale, sans ngrok.
- Scénarios UI sur device physique contre une instance locale : c'est le seul cas où ngrok (ou une IP LAN + cert accepté) est requis. Sinon, pointer le device vers une instance distante (mode B).
Cert self-signed : si curl refuse le certificat de l'instance locale, ajouter
-kaux commandes (§0.4). Sur device, l'app rejettera un cert de dev non approuvé — attendu ; préférer une instance distante pour l'UI device.
Note dans ton password manager (jamais commit) :
| Clé | Valeur attendue |
|---|---|
EKY_INSTANCE_URL |
https://<ton-instance> (sans slash final) |
EKY_TEST_EMAIL |
email du compte utilisateur |
EKY_TEST_PASSWORD |
mot de passe |
Avant de lancer l'app, vérifie que l'instance répond au schéma attendu.
Lancé depuis la machine de dev, donc OK contre une instance locale comme
distante (cf. §0.2). Si le cert local est self-signed, ajoute -k à chaque
curl.
# Login → récupère un token
TOKEN=$(curl -s -X POST "$EKY_INSTANCE_URL/api/v2/tokens" \
-H 'Content-Type: application/json' \
-d "{\"email\":\"$EKY_TEST_EMAIL\",\"password\":\"$EKY_TEST_PASSWORD\"}" \
| python3 -c "import json,sys; print(json.load(sys.stdin)['token'])")
# Vérifie les 5 endpoints catalogue + interventions
for ep in procedures products cultivable_zones variants interventions; do
echo "--- $ep ---"
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Authorization: simple-token $EKY_TEST_EMAIL $TOKEN" \
"$EKY_INSTANCE_URL/api/v2/$ep"
doneTous doivent retourner 200. Si 401/403 → token KO. Si 404 → API v2 pas activée. Stoppe ici et corrige avant de continuer.
pnpm start # dev client uniquement, pas Expo GoSur le device, ouvre l'app dev client → scanne le QR code Metro ou colle l'URL.
Chaque scénario a 4 cases : Action, Attendu UI, Vérif WDB (via React Native Debugger ou logs Sentry breadcrumbs si DSN configuré), Vérif serveur (web Ekylibre ou curl).
| Étape | Détail |
|---|---|
| Action | Lancer l'app, saisir URL + email + password, taper « Se connecter ». |
| Attendu UI | Redirection sur InitialSyncScreen, barre de progression qui passe les 4 étapes (procédures → produits × 3 sous-types → parcelles → variantes), arrivée sur l'onglet Interventions vide. |
| Vérif WDB | Tables procedures, products, cultivable_zones, variants non vides. sync_state.current_step = 'done'. |
| Vérif serveur | (rien, lecture seule) |
Mode dégradé à tester : couper le réseau pendant la sync initiale. L'écran doit afficher l'erreur. Réseau rétabli + tap retry → reprend depuis l'étape qui avait échoué (cf. P3 — sync resumable).
| Étape | Détail |
|---|---|
| Action | Activer le mode avion. Tap « + Nouvelle intervention » → « Pulvérisation ». Remplir : dates (par défaut OK), parcelle, conducteur, ≥1 intrant phyto (produit + variant + quantité + handler), pulvérisateur, notes. Tap « Enregistrer ». |
| Attendu UI | Alert succès, retour à la liste avec 1 ligne pending (badge ambre), bandeau jaune « 1 intervention à synchroniser », badge tab 1. |
| Vérif WDB | Table interventions : 1 ligne avec client_uuid non vide, server_id = null, sync_state = 'pending', sync_attempt_count = 0. Tables enfants : 1 doer + N inputs + 1 target + 1 tool + 1 working_period rattachés. |
| Vérif serveur | (rien, encore offline) |
Edge case multi-intrants : ajouter 2-3 lignes phyto, retirer la 2e, vérifier que les indices se réordonnent et que la sauvegarde garde les bons inputs.
| Étape | Détail |
|---|---|
| Action | Désactiver le mode avion. Tap bouton « Synchroniser » dans le header. |
| Attendu UI | Header passe « Téléchargement du catalogue… » → « Envoi des interventions… » → « Prêt ». La ligne pending bascule en synced (badge vert). Bandeau pending disparaît. Tab badge à 0. Texte « Dernière sync : HH:MM ». |
| Vérif WDB | interventions[0].server_id != null, sync_state = 'synced', last_synced_at récent. |
| Vérif serveur | Sur Ekylibre web, ouvrir l'intervention créée (id = server_id). Vérifier : procédure spraying, dates, parcelle, conducteur, intrants (qty + unit), pulvérisateur. Vérifier le tag provider : devrait avoir vendor=ekylibre-mobile, id=<UUID> (clientUuid local). |
Vérif curl côté serveur :
curl -s -H "Authorization: simple-token $EKY_TEST_EMAIL $TOKEN" \
"$EKY_INSTANCE_URL/api/v2/interventions" \
| python3 -m json.tool | head -60Le tableau retourné doit contenir l'intervention avec son provider.id.
✅ RÉSOLU (2026-05-30, core). L'API v2 dédoublonne désormais sur
(provider.vendor, provider.id): un 2ᵉ POST identique renvoie200avec le mêmeid(= Résultat A ci-dessous). Le blocker P6.5 est levé. Section conservée pour mémoire / non-régression.
C'était le seul vrai blocker P6 pour passer en pilote (cf. CHANGELOG
P6.4/P6.5). On voulait savoir si Ekylibre dédoublonne sur provider.id.
| Étape | Détail |
|---|---|
| Setup | Récupérer un payload exact qu'on enverrait au POST. Le plus simple : faire S2 + S3, puis lire les logs Metro pour voir le body envoyé. Ou intercepter via Charles Proxy. Sauvegarder le payload comme payload.json. |
| Action 1 | curl -X POST -H "Authorization: simple-token $EKY_TEST_EMAIL $TOKEN" -H "Content-Type: application/json" -d @payload.json "$EKY_INSTANCE_URL/api/v2/interventions" → noter id retourné. |
| Action 2 | Re-jouer exactement le même curl (même provider.id UUID). |
| Résultat A — server dédoublonne | Le 2e appel retourne le même id (ou 200 sans création). ✅ Idempotent. Décision : trust (P6 ship as-is). Documenter dans CHANGELOG. |
| Résultat B — server crée un doublon | Le 2e appel retourne un id différent. ❌ Pas idempotent. C'est le cas observé le 2026-05-30 (ids 44≠45 / 46≠47). provider n'est pas sérialisé par GET /api/v2/interventions et il n'existe pas de filtre provider_id. Fix → ticket P6.5 (dédup côté serveur priorisée). |
Variante : tester avec un payload qui a déjà été POSTé via l'app
mobile (réutilise un client_uuid existant en local). Si l'app
re-tente après un ack perdu, c'est ce cas-là qui se déclenche en prod.
| Étape | Détail |
|---|---|
| Setup | Saisir un spraying où on triche : par exemple, sélectionner un produit qui n'existe pas en plant_medicine côté serveur (rare, mais possible si le catalogue a évolué). Ou modifier le payload côté curl pour omettre un doer. |
| Action | Tap Synchroniser. |
| Attendu UI | Header passe « Échec de la synchronisation ». Bandeau rouge persistant « 1 intervention en erreur ». Ligne reste en badge rouge. Ouvrir le détail → message serveur visible. |
| Vérif WDB | sync_state = 'error', sync_error_message rempli avec le message Eky. sync_attempt_count = 0 (un 422 n'incrémente pas — c'est un retry inutile sans correction). |
| Étape | Détail |
|---|---|
| Setup | Difficile à provoquer. Méthode : couper l'API Eky côté serveur le temps du test, ou utiliser un proxy qui retourne 500 sur la route /api/v2/interventions POST. |
| Action | Saisir + tap Synchroniser. |
| Attendu UI | Header « Échec ». Bandeau syncError affiche « Erreur serveur (500)… ». Ligne reste pending (pas error). |
| Vérif WDB | sync_state = 'pending' (retry-able), sync_attempt_count = 1. |
| Action 2 | Réactiver l'API serveur, tap Synchroniser à nouveau → la ligne passe synced, attempt reset (en pratique : non remis à 0, on garde la trace). |
| Étape | Détail |
|---|---|
| Setup | Saisir l'intervention online, couper le réseau juste avant de tap Synchroniser. |
| Action | Tap Synchroniser. |
| Attendu UI | Header « Échec ». Bandeau syncError « Connexion perdue… ». Pull catalogue échoue, push tente quand même puis échoue → ligne reste pending. |
| Vérif WDB | sync_attempt_count = 1, sync_error_message parle de connexion. |
| Action 2 | Réseau rétabli, tap Synchroniser → tout passe synced. |
| Étape | Détail |
|---|---|
| Setup | Saisir un spraying avec un produit X. Avant de synchroniser, retirer X du catalogue Ekylibre (via web ou curl DELETE si supporté). Tap Synchroniser : le pull va supprimer X de WDB local, mais l'intervention référence toujours son local id. |
| Action | Tap Synchroniser après suppression. |
| Attendu UI | Push échoue avec MissingServerIdError (capté par le moteur). Ligne error, message « Référence X introuvable dans le catalogue ». |
| Vérif WDB | Pas d'appel API tenté pour cette intervention (économie d'aller-retour). |
| Étape | Détail |
|---|---|
| Setup | Avoir ≥1 intervention en pending ou error. |
| Action | Settings → « Se déconnecter ». |
| Attendu UI | Alert avec 2 paragraphes : « Vos identifiants seront effacés » + « N intervention(s) non synchronisée(s) seront perdues ». |
| Action 2 | Tap « Se déconnecter ». |
| Vérif | Retour Login. Re-login → pas d'interventions reprises (les locales ont été purgées). |
| Étape | Détail |
|---|---|
| Setup | Côté Ekylibre web ou curl : révoquer le token de la session courante (DELETE /api/v2/tokens/$TOKEN). |
| Action | Tap Synchroniser. |
| Attendu UI | Redirection automatique sur LoginScreen avec bandeau « Votre session a expiré, veuillez vous reconnecter ». |
| Vérif WDB | Tables non purgées (la déconnexion 401 garde les interventions pending). Re-login → elles sont toujours là. |
| Étape | Détail |
|---|---|
| Action | Avoir 1 pending, double-tap rapide sur le bouton Synchroniser. |
| Attendu | 1 seul cycle se lance (pas 2). Le hook useSyncCycle a un lock useRef anti-réentrance. |
| Vérif réseau | Charles / adb logcat doivent montrer 1 seul POST, pas 2. |
Idem S11 mais avec pull-to-refresh et bouton tapés en parallèle. Le 2e geste doit être no-op silencieusement (pas d'erreur UI).
À cocher avant d'envisager la phase pilote :
- S1 Login + sync initiale → catalogue téléchargé sur le device
(validé device 2026-05-30, après fix géométrie
cultivable_zones). - S2 Saisie spraying offline → ligne pending visible (validé device 2026-06-14 sur build pilote ; 2 bémols hors-périmètre S2 trackés : handler qui ne lock pas l'unité auto, équipements non filtrés par procedure).
- S3 Sync online → ligne synced + intervention visible côté Eky web avec
le bon
provider.id(validé après fix core P6.6 spraying Procedo + fix app du parsing réponse{ id }qui laissait la ligne « à synchroniser »). - S4 Test bac à sable idempotence → ✅ RÉSOLU côté core (2026-05-30) :
l'API v2 dédoublonne sur
provider.id(2ᵉ POST identique →200, mêmeid). Le blocker P6.5 est levé. Repro initiale viascripts/s4-idempotence.sh(RÉSULTAT B), confirmée corrigée depuis. - S5 422 → message serveur visible dans détail (validé device 2026-06-14).
- S6 5xx → reste pending + retry OK (validé device 2026-06-14 ; observé en pratique : 404 quand l'API Rails est down, traité comme transient par le client — retry OK quand l'API remonte).
- S7 Network down → reste pending + retry OK (validé device 2026-06-14, message d'erreur connexion distinct du 5xx).
- S8 Catalogue stale → MissingServerIdError visible (validé device 2026-06-14, 0 POST tenté pour cette intervention).
- S9 Logout warning quand pending > 0 (validé device 2026-06-14, alert à 2 paragraphes + purge des locales au logout volontaire).
- S10 401 → redirect login avec bannière (validé device 2026-06-14, interventions pending non purgées, retrouvées au re-login).
- S11 Double-tap Synchroniser → 1 seul POST (validé device 2026-06-14).
- S12 Pull-to-refresh + bouton parallèle → 1 seul POST (validé device 2026-06-14).
- Vérification visuelle du payload dans Charles/logs :
providerprésent avecvendor='ekylibre-mobile',name,id=UUID,data.app_version,data.os,data.locale(validé device 2026-06-14).
Si tous verts → green light pour préparer P7 (carte) et lancer le panel pilote en parallèle (cf. P0-checklist §1.6 + workflow §11 checkpoints).
Si un rouge → ne pas distribuer au panel. Ouvrir un ticket ciblé sur le scénario qui casse.
MAJ 2026-05-30 — le blocker historique S4 (idempotence) est levé côté core, et S3 (push spraying) passe désormais bout-en-bout après le fix core P6.6 (procédure spraying : nœuds Procedo
ActorPresenceTest/Division/nil.unit, cf.docs/p6.6-ekylibre-spraying-procedo-issue.md) et le fix app du parsing de la réponse d'écriture ({ id }).MAJ 2026-06-14 — smoke device complet validé sur build pilote (post-rebuild EAS, post-PR Ekylibre
shape_geojson, post-re-sync catalogue). 10/10 verts (S2 + S5–S12 + vérif payloadprovider). Green light côté code ; iOS/TestFlight prêt à soumettre une fois les placeholderseas.jsonrenseignés. Android attend l'approbation du compte developer Google. Les 2 follow-ups initialement identifiés (handler qui ne lock pas l'unité auto, filtrage des équipements par procedure) ont été livrés dans la même session — cf. CHANGELOG entrées Polish UX form spraying et Filtrage des équipements par procedure. Re-sync catalogue requise sur device pour le 2ᵉ (peupleproducts.abilities).
- React Native Debugger ou Flipper — inspecter l'état WDB pendant les tests (les hooks observables n'écrivent pas de log, il faut peeker dans la DB).
- Charles Proxy ou Proxyman (macOS) — capter les POSTs réels avec le payload exact. Indispensable pour S4 (extraire le payload à re-rejouer en curl).
adb logcat | grep -i metro(Android) — voir lesconsole.logdu dev client.- Safari Web Inspector (iOS, Develop menu) — devtools sur le dev client iOS.
- Sentry breadcrumbs — si le DSN dev est configuré (cf. P0-checklist §1.4), chaque cycle de sync laisse des traces.
Une fois les tests passés, mettre à jour docs/CHANGELOG-v1.md :
- Section P6.4 Action requise : marquer DONE ou DEFERRED selon résultat S4.
- Si KO sur S4 : créer une entrée Unreleased — P6.5 listant le ticket GET défensif.
- Si tous verts : ajouter une entrée Unreleased — Validation dev local P6 avec date + résumé (3 lignes max).