Repository navigation
[KF-021] Test E2E déterministe pour AC #29 (race REPEATABLE READ no-op + mutation parallèle) #50
Description
Contexte
Story 7-3 (KF-004) AC #29 spécifie un test E2E qui doit reproduire la race condition REPEATABLE READ entre un no-op et une mutation parallèle, et asserter le comportement v0.1 attendu (200 OK + body = snapshot v=N stale). Ce test doit servir de régression detector : si une future migration SELECT FOR UPDATE (cf. issue #49 / KF-020) corrige la race, le test devra être mis à jour pour refléter le nouveau comportement (200 stale → 409).
L'implémentation actuelle (crates/kesh-api/tests/kf004_no_op_e2e.rs::no_op_with_parallel_mutation_returns_409_when_sequential) n'a pas pu reproduire la race de manière déterministe en CI : reproduire un commit MariaDB inter-leaved entre BEGIN et le no-op check applicatif exige une vraie concurrence (tokio::join! sur deux pools/connections distincts, voire un pause/advance orchestré côté driver). Le test livré est dégradé en smoke test du flow séquentiel qui asserte 409 (le verrouillage optimiste rejette correctement la version stale en mode séquentiel).
Conséquence : le test ne peut pas détecter une régression liée à la migration SELECT FOR UPDATE — il passe identiquement avant et après la migration.
Scope du fix
Implémenter un test E2E déterministe qui reproduit la race et asserte le comportement v0.1 (200 + stale) :
- Approche 1 :
tokio::join!sur deuxMySqlPooldistincts (pas de connection sharing) + délai contrôlé sur la transaction A pour fenêtrer leBEGINde B après le commit de A mais avant que B ne ré-SELECT. - Approche 2 : test stress en boucle (N=100 itérations) qui valide la présence d'au moins un cas
200 stale— permet de tolérer la non-déterminisme MariaDB (snapshot isolation) mais reste déterministe sur la borne inférieure. - Approche 3 : injection de hook (test-only) dans
is_no_op_changepour suspendre l'exécution à un point précis et laisser la transaction parallèle commit.
Choix de l'approche à arbitrer en fonction du framework de test (sqlx::test fournit un pool unique par test).
Critère d'acceptation
- Le test doit asserter
200 OK + version inchangée + body = snapshot stalequand une mutation parallèle commit pendant le no-op check. - Le test doit être déterministe en CI (ou avoir une probabilité de détection ≥ 99%).
- Le test doit être documenté comme régression detector — si une future migration
SELECT FOR UPDATE(issue [KF-020] invoices::update : passer en SELECT FOR UPDATE pour fermer la race no-op KF-004 résiduelle #49) corrige la race, le test doit être mis à jour pour le nouveau comportement (200 stale → 409).
Lien
- Spec source : Story 7-3 §race-condition + AC Story 6-2: Multi-tenant scoping refactor — Pass 3 remediation complete #29
- Issue parente race résiduelle : [KF-020] invoices::update : passer en SELECT FOR UPDATE pour fermer la race no-op KF-004 résiduelle #49 (KF-020 — migration
SELECT FOR UPDATEpourinvoices::update) - Implémentation actuelle (smoke test séquentiel) :
crates/kesh-api/tests/kf004_no_op_e2e.rs::no_op_with_parallel_mutation_returns_409_when_sequential
Priorité
Basse — pas un blocker v0.1. À planifier idéalement avant ou en même temps que la migration SELECT FOR UPDATE (#49) pour valider le passage 200 stale → 409 correctement.
Activity
Story 7-3: KF-004 — update() no-op short-circuit (closes #4) (#51)
fix(db): KF-020 SELECT FOR UPDATE in invoices::update (closes #49) (#64)
retro(epic-9): closure + politique « zero tech debt carry-forward » +…
Story 9-2b: Export global ZIP (souveraineté des données — FR68) (#94)
plan(9-5-1): split préventif → 9-5-1a triage + 9-5-1b/c/d placeholder…
dev(9-5-1a): triage statique 6 KFs — verdict 0/6 résolues, 6/6 encore…
review(9-5-1a): Pass 1 code-review Sonnet 4.6 — CONVERGENCE (2 LOW po…
review(9-5-1b): Pass 2 code-review Haiku 4.5 — CONVERGENCE (L1 dismis…
review(9-5-1c): Pass 1 code-review Sonnet 4.6 × 3 — CONVERGE (2 patch…
spec(9-5-1d): create story Fix specific KFs — KF #47 fallback toast E…
chore(9-5-1d): baseline pre-fix kf004_no_op_e2e — 6/6 smoke séquentie…
fix(api): close KF #50 KF-021 deterministic concurrent test for no-op…
dev(9-5-1d): closure documentaire — T1-T8 cochés, status review
retro(epic-9-5): Technical Debt Closure formellement closed
[Epic 9.5] Technical Debt Closure — 7 stories + retrospective (closes #…