diff --git a/TEST_SCENARIOS.md b/TEST_SCENARIOS.md index 56211c3..59678b3 100644 --- a/TEST_SCENARIOS.md +++ b/TEST_SCENARIOS.md @@ -156,7 +156,7 @@ est dirty. Tester avec `askBeforeDelete` **ON puis OFF** (Settings → Security) ## F — Round-trip export / import (drapeaux & provenance) -### F1 · favori + épingle survivent au restore +### F1 · favori + épingle survivent au restore ✅ - **ACTION** : épingler **et** mettre en favori une entrée → *Export vault* → supprimer l'entrée (corbeille + purge) → *Import vault* du fichier. - **ATTENDU** : l'entrée revient **épinglée et favorite**. - **Historique** : `pinned` n'était dans aucun container ; `favorite` y était écrit mais **jamais relu** (ni parser, ni body, ni INSERT) → les deux perdus au restore. @@ -173,7 +173,7 @@ est dirty. Tester avec `askBeforeDelete` **ON puis OFF** (Settings → Security) - **ACTION** : épingler/favoriser une entrée sur le device A → *Sync now* → *Sync now* sur le device B. - **ATTENDU** : sur B, l'entrée **n'est pas** épinglée/favorite. Et un sync ne doit jamais **retirer** un drapeau posé localement sur B (le PUT serveur ne touche pas ces colonnes). -### F5 · icône custom : provenance préservée +### F5 · icône custom : provenance préservée ✅ - **ACTION** : upload d'une icône custom → export → supprimer l'entrée → import → puis **Re-fetch all**. - **ATTENDU** : l'icône custom revient **et** survit au Re-fetch all (le flag `icon_custom` a fait le round-trip). @@ -214,7 +214,7 @@ Ces flux touchent la dérivation de clé et l'anti-lockout : tester sur un compt **argon2id-v2** (défaut courant). Rebuild **Delphi** requis (fixes serveur) + BuildAssets pour le JS. -### E1 · reauth — mauvais master pw reste inline (les 4 flux) +### E1 · reauth — mauvais master pw reste inline (les 4 flux) ✅ - **ACTION** : sur chacun de *Set/Change PIN*, *Enable Quick Unlock*, *Generate recovery code*, *Export* → taper un **mauvais** master pw. - **ATTENDU** : le modal **reste ouvert** avec « Wrong master password. Attempt @@ -222,13 +222,13 @@ BuildAssets pour le JS. moment = action abandonnée, rien fait. - **Historique** : PIN / Quick Unlock / recovery fermaient + toast au 1ᵉʳ essai. -### E2 · recovery setup sur compte argon2id-v2 +### E2 · recovery setup sur compte argon2id-v2 ✅ - **ACTION** : *Generate recovery code* → bon master pw. - **ATTENDU** : code généré, **pas** de « invalid password ». - **Historique** : `/recovery-key/setup` rejetait tout compte -v2 / argon2id (vérif serveur ne connaissait que pbkdf2 / pbkdf2-sha256). -### E3 · recovery generate → annuler n'écrit rien +### E3 · recovery generate → annuler n'écrit rien ✅ - **PRÉ** : un code de recovery **existe déjà**. - **ACTION** : *Regenerate code* → bon master pw → à l'affichage du code, **Cancel** (ou Esc). @@ -236,9 +236,9 @@ BuildAssets pour le JS. code fonctionne toujours au redeem. Refaire avec **I saved it** → nouveau code actif, ancien invalidé. - **Historique** : le setup était POSTé avant l'affichage → annuler écrasait - quand même l'ancien code (chemin silencieux vers le lockout). + quand même l'ancien code (chemin silencieux vers le lockout). -### E4 · recovery redeem sur compte argon2id-v2 +### E4 · recovery redeem sur compte argon2id-v2 ✅ - **ACTION** : générer un code, se déconnecter, *Use a recovery code* → username + code. - **ATTENDU** : déverrouillage OK, redirection vers *Change master password*. diff --git a/js/app.import.js b/js/app.import.js index 8603c78..aff3d7b 100644 --- a/js/app.import.js +++ b/js/app.import.js @@ -728,6 +728,19 @@ async function doImport() { // restore the file's icon separately — else overwriting // an entry whose icon was cleared never brings it back. if (src.icon_b64) await saveEntryIcon(local.id, src.icon_b64, !!src.icon_custom); + // favorite / pinned are the same story: PUT deliberately + // ignores them so a SYNC update can never clobber the local + // flags (they're device-local by design). But a restore + // must bring them back, so apply them here through their + // own toggle endpoints — only when they actually differ. + if (!!src.favorite !== !!local.favorite) { + await api('/entries/' + local.id + '/favorite', + { method: 'POST', headers: authHeaders() }); + } + if (!!src.pinned !== !!local.pinned) { + await api('/entries/' + local.id + '/pin', + { method: 'POST', headers: authHeaders() }); + } overwritten++; } catch (_) { /* skip the single row on failure */ } }