787 lines
38 KiB
Markdown
787 lines
38 KiB
Markdown
# E2E SYNC VALIDATION — Test end-to-end réel du pipeline de synchronisation
|
|
|
|
## Statut : EXÉCUTÉ — CREATE VALIDÉ, REPLAY À RETESTER, IDEMPOTENCE RETRY CORRIGÉE TRANSACTIONNELLEMENT
|
|
|
|
> **Mise à jour 13/08/2026 17:27 — tentative du test Replay corrigé :**
|
|
> le test `Replay_SameOperationId_IsIdempotent` (corrigé, autonome, polling
|
|
> terminal) a été **ré-exécuté physiquement** (garde-fous `true`, Worker externe
|
|
> actif) mais a **échoué au polling du premier POST** : `Assert.NotNull() Failure:
|
|
> Value is null` (ligne 302). Le log du Worker ne montre **aucune ligne de
|
|
> traitement** d'opération pendant le test : le Worker externe n'a **pas traité**
|
|
> l'opération dans le délai de 30 s du test. Le statut API est donc resté non
|
|
> terminal. **Aucune écriture métier supplémentaire n'a été produite par cette
|
|
> tentative** (le Worker n'a rien traité). Les garde-fous ont été **restaurés à
|
|
> `false`** après la tentative. Le test Replay corrigé reste **à retester
|
|
> physiquement** (le Worker externe doit traiter l'opération dans le délai du
|
|
> test).
|
|
|
|
> **Mise à jour 13/08/2026 17:43 — tentative avec polling Worker E2E = 5 s :**
|
|
> le test Replay corrigé a été **ré-exécuté physiquement** avec un Worker externe
|
|
> lancé avec l'override temporaire `Sync__RetryDelaySeconds=5` (poll immédiat puis
|
|
> toutes les 5 s) et les garde-fous `true`. **Le pipeline a réellement atteint le
|
|
> premier SUCCESS** (le test a passé l'assertion `Assert.NotNull(firstStatus)` à la
|
|
> ligne 302). Le test a ensuite **échoué sur un bug de disposition du helper
|
|
> `CountInterventionsAsync`** (ligne 177) : `System.InvalidOperationException :
|
|
> 'WebServiceHfSqlConnection' type only implements IAsyncDisposable. Use
|
|
> DisposeAsync to dispose the container.` Le helper utilise `using var scope =
|
|
> _factory.Services.CreateScope()` (disposition synchrone) alors que
|
|
> `WebServiceHfSqlConnection` n'implémente que `IAsyncDisposable` — le même
|
|
> problème `IAsyncDisposable` déjà corrigé dans `Program.cs`. **Ce n'est pas un
|
|
> échec du pipeline** : le premier POST a atteint SUCCESS (le Worker a traité
|
|
> l'opération, transaction BATIK_TEST, handler, MobLog SUCCESS, COMMIT). Le test
|
|
> a échoué uniquement sur le comptage métier (helper). Les garde-fous ont été
|
|
> **restaurés à `false`** et le Worker **arrêté** après la tentative. Le test
|
|
> Replay corrigé reste **à retester physiquement** après correction du helper
|
|
> `CountInterventionsAsync` (disposition asynchrone).
|
|
|
|
> **Mise à jour 13/08/2026 17:48 — micro-fix disposition + nouvelle tentative :**
|
|
> le helper `CountInterventionsAsync` a été corrigé (disposition asynchrone
|
|
> `await using var scope = _factory.Services.CreateAsyncScope()`), le projet de
|
|
> test compile. Le test Replay a été **ré-exécuté physiquement** (Worker E2E
|
|
> `Sync__RetryDelaySeconds=5`, garde-fous `true`). **Le pipeline a de nouveau
|
|
> atteint le premier SUCCESS** (le test est passé à la ligne 309). Le test a
|
|
> ensuite **échoué sur une nouvelle erreur du helper `CountInterventionsAsync`**
|
|
> (ligne 175) : `System.InvalidOperationException : La connexion Business doit
|
|
> être ouverte avant d'exécuter une commande.` (`BusinessOleDbConnection.EnsureOpen()`
|
|
> ligne 317). Le helper obtient `IWebServiceHfSqlConnection` et appelle
|
|
> `ExecuteScalarAsync` **sans ouvrir la connexion**. **Ce n'est pas un échec du
|
|
> pipeline** : le premier POST a atteint SUCCESS (transaction BATIK_TEST, handler,
|
|
> MobLog SUCCESS, COMMIT). Le test a échoué uniquement sur le comptage métier
|
|
> (helper). Les garde-fous ont été **restaurés à `false`** et le Worker **arrêté**
|
|
> après la tentative. Le test Replay corrigé reste **à retester physiquement**
|
|
> après ouverture de la connexion dans `CountInterventionsAsync`.
|
|
|
|
> **Mise à jour 13/08/2026 17:54 — micro-fix OpenAsync + E2E RÉUSSI :**
|
|
> le helper `CountInterventionsAsync` a été corrigé (ouverture explicite de la
|
|
> connexion `await connection.OpenAsync(cancellationToken)` avant
|
|
> `ExecuteScalarAsync`), le projet de test compile. Le test Replay a été
|
|
> **ré-exécuté physiquement** (Worker E2E `Sync__RetryDelaySeconds=5`, garde-fous
|
|
> `true`) et a **RÉUSSI** (1/1, 18 s) : premier POST → SUCCESS, business count = 1,
|
|
> second POST (même OperationId/RequestedEntityGuid/payload) → SUCCESS,
|
|
> CanonicalEntityGuid identique, business count final = 1, **aucun doublon**.
|
|
> **CREATE HFSQL PHYSIQUE : VALIDÉ. COMMIT TRANSACTIONNEL BATIK_TEST : VALIDÉ
|
|
> PHYSIQUEMENT. REPLAY TERMINAL : VALIDÉ PHYSIQUEMENT. IDEMPOTENCE TERMINALE :
|
|
> VALIDÉE.** Les garde-fous ont été **restaurés à `false`** et le Worker **arrêté**
|
|
> après la tentative. **Baseline Mobile : ÉCHEC** sur 2 tests transactionnels
|
|
> (`Crash_After_Commit_Does_Not_Reapply_Handler_On_Retry` ligne 403 et
|
|
> `Two_Different_OperationIds_Produce_Two_Business_Creations` ligne 436) :
|
|
> `Assert.Equal() Failure: Expected 1/2, Actual 0` — le handler n'a produit
|
|
> aucune création métier dans ces 2 tests unitaires. **STOP au premier échec de
|
|
> la baseline** : suite hors HFSQL et build solution non lancés.
|
|
|
|
> **Mise à jour 13/08/2026 18:02 — correction des 2 tests transactionnels rouges :**
|
|
> la cause de `Actual: 0` était un **défaut de test harness** (aucun défaut
|
|
> production) : les deux tests rouges utilisaient le handler générique
|
|
> `FakeSyncOperationHandler(SyncOperationResult.Success())`, qui retourne SUCCESS
|
|
> **sans écrire d'Intervention** dans le fake transactionnel. Le compteur
|
|
> `tx.CommittedInterventions` est alimenté par le handler via `WriteIntervention`
|
|
> (comme le font `ThrowingAfterWriteHandler` / `ThrowingThenSuccessHandler` des
|
|
> deux tests verts). Un handler dédié `SuccessWritingHandler` (écrit une
|
|
> Intervention + retourne SUCCESS + expose `CallCount`) a été ajouté et utilisé
|
|
> par les deux tests. Pour `Crash_After_Commit`, l'assertion `repo.Succeeded.Count
|
|
> == 1` a été remplacée par `Assert.Contains(env.OperationId, repo.Succeeded)` :
|
|
> au replay, le `SyncProcessor` reconstitue le log terminal SUCCESS (re-marquage
|
|
> via `MarkSuccessAsync`) **sans rappeler le handler** (comportement correct
|
|
> d'idempotence par OperationId) ; le fake `Succeeded` accumule donc un doublon,
|
|
> mais le handler n'est pas rappelé et aucune Intervention supplémentaire n'est
|
|
> durable. **Baseline complètement verte** : 4 tests transactionnels (4/4),
|
|
> SyncProcessor + SyncWorker (22/22), Mobile (515/515), suite hors HFSQL
|
|
> (1755/1755), build solution (0 erreur). **Aucun fichier production modifié.**
|
|
> **SYNC E2E PHYSIQUE reste VALIDÉ** (CREATE, COMMIT, REPLAY terminal,
|
|
> idempotence terminale). Garde-fous toujours `false`.
|
|
|
|
Le test end-to-end réel du pipeline de synchronisation **multi-connexion** a été
|
|
**réellement exécuté** (EXECUTED) contre les bases HFSQL de TEST. Le CREATE a
|
|
**réellement abouti** (SUCCESS, Intervention créée, MobLog SUCCESS).
|
|
|
|
Le test Replay **actuel** (`Replay_SameOperationId_IsIdempotent`) a été
|
|
**corrigé** pour devenir autonome : générer son propre OperationId, attendre
|
|
réellement SUCCESS, compter le métier = 1, re-POST exact, vérifier métier
|
|
toujours = 1. Il reste **à retester physiquement** (garde-fous `false`).
|
|
|
|
La **fenêtre de duplication retry** (business réussi mais terminal absent) a été
|
|
**fermée par une correction transactionnelle** : pour une opération réussie, les
|
|
écritures métier (Intervention), les relations associées, MobLog SUCCESS et la
|
|
finalisation MobReception sont désormais exécutées dans **UNE transaction OLE DB
|
|
locale BATIK_TEST partagée** (via `IWebServiceTransactionContext`). Soit tout est
|
|
durable, soit rien. Cette correction est **implémentée et testée** (tests
|
|
transactionnels verts), mais **pas encore validée par un E2E physique**.
|
|
|
|
**Résultat : SYNC E2E HFSQL PHYSIQUE (CREATE) : VALIDÉ.**
|
|
**REPLAY / IDEMPOTENCE TERMINALE : TEST CORRIGÉ, À RETESTER PHYSIQUEMENT.**
|
|
**IDEMPOTENCE RETRY : CORRECTION TRANSACTIONNELLE IMPLÉMENTÉE ET TESTÉE (pas encore E2E validée).**
|
|
**LICENCE E2E : NON VALIDÉE — BASE_LICENCES BLOQUÉE.**
|
|
|
|
|
|
|
|
## 0. Résultat de l'exécution réelle (13/08/2026)
|
|
|
|
### Maillons observés (dans l'ordre) — CREATE
|
|
|
|
| Maillon | Résultat | Détail |
|
|
|---|---|---|
|
|
| A. HTTP accepté | ✅ VERT | `POST /api/sync/operations` → 200 OK, `RECEIVED` |
|
|
| B. opération dans TOSRV | ✅ VERT | écrite dans `BATIK.01_BLS.TOSRV`, puis transférée |
|
|
| C. même OperationId | ✅ VERT | `receipt.OperationId == operationId` |
|
|
| D. TOSRV → MobReception | ✅ VERT | opération présente dans `BATIK_TEST.MobReception` |
|
|
| E. IDTOSRV conservé | ✅ VERT | `MR_IDToSRV` correspond à `IDTOSRV` source |
|
|
| F. SyncProcessor réel | ✅ VERT | `SyncProcessor.ProcessOperationAsync` appelé |
|
|
| G. Handler Intervention | ✅ VERT | `InterventionSyncOperationHandler.HandleCreateAsync` appelé |
|
|
| H. Intervention créée | ✅ VERT | Intervention `E2E_TEST_5b915439` créée dans BATIK_TEST |
|
|
| I. résultat dans MobLog | ✅ VERT | log `Success` écrit (SUCCESS) |
|
|
| J. status API SUCCESS | ✅ VERT | endpoint status → `SUCCESS` |
|
|
|
|
### Maillons observés — REPLAY
|
|
|
|
| Maillon | Résultat | Détail |
|
|
|---|---|---|
|
|
| HTTP accepté | ✅ VERT | réenvoi du même OperationId accepté |
|
|
| status API | ❌ ROUGE | retourne `Pending` au moment de la vérification (Worker pas encore passé) |
|
|
| Idempotence terminale | ❌ ROUGE | le test Replay **actuel** envoie les deux POST sans attendre SUCCESS : ce n'est pas un test d'idempotence terminale |
|
|
|
|
### Premier maillon rouge (Replay)
|
|
|
|
- **Maillon** : J — status API SUCCESS (Replay).
|
|
- **Exception/code HFSQL** : aucune erreur HFSQL. Le status est resté `Pending`
|
|
car le Worker externe n'a pas traité l'opération dans le délai du test
|
|
(~4 secondes).
|
|
- **Table** : N/A.
|
|
- **Colonne** : N/A.
|
|
- **Composant .NET** : `SyncPipelineE2ETests.Replay_SameOperationId_IsIdempotent`
|
|
(test conceptuellement incorrect : les deux POST sont envoyés immédiatement,
|
|
sans attendre le SUCCESS terminal du premier).
|
|
- **Correction appliquée** : le test Replay a été réécrit pour être autonome —
|
|
générer son propre OperationId, attendre réellement SUCCESS (polling terminal),
|
|
compter le métier = 1, re-POST exact, vérifier métier toujours = 1.
|
|
|
|
|
|
### Données E2E laissées
|
|
|
|
- **OperationId (CREATE)** : `5b915439-d9d9-44b5-9552-1f8f90db96c3`
|
|
- **RequestedEntityGuid (CREATE)** : `6679f54e-ace4-4df3-a75d-d69701601069`
|
|
- **CanonicalEntityGuid (CREATE)** : `0001014b-0001-0000-008a-2badf0d20f00`
|
|
- **OperationId (Replay)** : `0fce297f-e4e4-45ad-9027-b4e6e23d5c8a`
|
|
- **RequestedEntityGuid (Replay)** : `6f3bd0c1-8aeb-4296-9204-c7b1470c345a`
|
|
- **CanonicalEntityGuid (Replay)** : `0001014b-0001-0000-008a-2c339447ac00`
|
|
- **Interventions** : 7 lignes `E2E_TEST_*` laissées dans BATIK_TEST. Les 6
|
|
lignes `E2E_TEST_5b915439` sont des **doublons du MÊME OperationId**
|
|
`5b915439-d9d9-44b5-9552-1f8f90db96c3` (défaut d'idempotence retry business :
|
|
le handler CREATE a été rappelé à chaque retry, `retryCount=16` au SUCCESS,
|
|
recréant une Intervention à chaque tentative). La ligne `E2E_TEST_0fce297f`
|
|
est l'Intervention **normale** du test Replay (OperationId distinct
|
|
`0fce297f-e4e4-45ad-9027-b4e6e23d5c8a`), **PAS un doublon** du CREATE.
|
|
Aucune donnée personnelle réelle.
|
|
- **MobLog** : logs `Pending` + `Success` pour chaque opération.
|
|
- **TOSRV** : vide (opérations transférées et supprimées).
|
|
|
|
Aucun DELETE physique n'a été effectué. Un futur test ARCHIVE pourra servir au
|
|
cleanup des lignes `E2E_TEST_*` si nécessaire.
|
|
|
|
### Replay
|
|
|
|
Le test `Replay_SameOperationId_IsIdempotent` a été **réellement exécuté**
|
|
(EXECUTED) mais a **échoué** : le status est resté `Pending` au moment de la
|
|
vérification (le Worker externe n'a pas traité l'opération dans le délai du
|
|
test). Le Worker a néanmoins traité l'opération Replay **après** le test,
|
|
créant l'Intervention `E2E_TEST_0fce297f`.
|
|
|
|
**Diagnostic exact (micro-lot idempotence) :** le test Replay **actuel** génère
|
|
son **propre** OperationId (`0fce297f-e4e4-45ad-9027-b4e6e23d5c8a`) et son propre
|
|
RequestedEntityGuid (`6f3bd0c1-8aeb-4296-9204-c7b1470c345a`). Il ne réutilise
|
|
**pas** l'OperationId du test CREATE (`5b915439`). L'Intervention
|
|
`E2E_TEST_0fce297f` est donc l'Intervention **normale** du test Replay
|
|
(OperationId distinct), **PAS un doublon** du CREATE `5b915439`.
|
|
|
|
Le test Replay **actuel** est **conceptuellement incorrect** : il envoie les
|
|
deux POST immédiatement (lignes 213-218), **sans attendre le SUCCESS terminal
|
|
du premier**. Ce n'est donc pas un test d'idempotence terminale. Il a été
|
|
**corrigé** pour devenir autonome : générer son propre OperationId, attendre
|
|
réellement SUCCESS (polling terminal), compter le métier = 1, re-POST exact,
|
|
vérifier métier toujours = 1.
|
|
|
|
|
|
### Garde-fous
|
|
|
|
Les trois garde-fous ont été **restaurés à `false`** après l'exécution :
|
|
`AllowHfSqlWrites`, `ServiceDatabaseConfirmedTest`,
|
|
`WebServiceDatabaseConfirmedTest`. `AllowHfSqlWrites` n'est jamais laissé à
|
|
`true` après la tâche.
|
|
|
|
|
|
## 1. Architecture multi-connexion validée
|
|
|
|
```
|
|
ServiceConnection (base Service, table TOSRV)
|
|
↓
|
|
WebServiceConnection (base WebService/Business)
|
|
→ MobReception
|
|
→ MobLog
|
|
→ données métier (Intervention)
|
|
```
|
|
|
|
Le repository `SyncTransferRepository` est déjà adapté à cette topologie :
|
|
- `IServiceHfSqlConnection` → base Service, table `TOSRV`.
|
|
- `IWebServiceHfSqlConnection` → base WebService/Business, tables `MobReception`
|
|
et `MobLog`.
|
|
|
|
## 2. Environnement sans secrets
|
|
|
|
Ce document ne contient **aucun secret** : ni mot de passe, ni token, ni
|
|
ProtectionPlusSecret, ni FilePasswords, ni credential. Les valeurs de connexion
|
|
sont décrites par leur nom de clé et leur nature, jamais par leur valeur
|
|
sensible.
|
|
|
|
## 3. Bases réellement utilisées
|
|
|
|
### Service DB (base Service, table TOSRV)
|
|
|
|
| Section | Clé | Valeur effective |
|
|
|---|---|---|
|
|
| `ServiceConnection` | `DatabaseName` | `BATIK.01_BLS` (base Service actuelle) |
|
|
|
|
La base Service est utilisée par **Omika.Api** et **Omika.Worker** via la
|
|
connexion `ServiceConnection` (enregistrée par `AddOmikaDataServiceConnection`).
|
|
|
|
### WebService DB (base WebService/Business)
|
|
|
|
| Section | Clé | Valeur effective |
|
|
|---|---|---|
|
|
| `Hfsql` (Business / WebService) | `DatabaseName` | `BATIK_TEST` (config locale) |
|
|
|
|
La base WebService/Business est utilisée par **Omika.Api** et **Omika.Worker**
|
|
via la connexion `Hfsql` (enregistrée par `AddOmikaData`). Elle héberge les
|
|
tables `MobReception`, `MobLog` et les données métier (Intervention).
|
|
|
|
## 4. Confirmation explicite TEST
|
|
|
|
Le nom de la base Service (`BATIK.01_BLS`) **ne prouve pas** qu'elle est une
|
|
base TEST. La confirmation est fournie **explicitement** via la configuration
|
|
locale non versionnée `config/hfsql-test.local.json`, section `E2E` :
|
|
|
|
```json
|
|
"E2E": {
|
|
"AllowHfSqlWrites": false,
|
|
"ServiceDatabaseConfirmedTest": false,
|
|
"WebServiceDatabaseConfirmedTest": false
|
|
}
|
|
```
|
|
|
|
Par défaut, tous les flags sont `false`. **Aucune écriture** n'est possible tant
|
|
que les trois flags ne sont pas explicitement `true` :
|
|
|
|
- `AllowHfSqlWrites = true`
|
|
- `ServiceDatabaseConfirmedTest = true`
|
|
- `WebServiceDatabaseConfirmedTest = true`
|
|
|
|
Le mécanisme est implémenté dans `E2EOptions.IsWriteAllowed` (exige les trois
|
|
flags simultanément). Le nom de la base ou `localhost` ne suffit jamais.
|
|
|
|
## 5. Connexions effectives
|
|
|
|
### Omika.Api
|
|
|
|
- `ServiceConnection` → base Service (`BATIK.01_BLS` actuelle).
|
|
- `WebServiceConnection` (via `Hfsql`) → base WebService/Business
|
|
(`BATIK_TEST` / `OMIKA_TEST` selon l'environnement choisi).
|
|
|
|
### Omika.Worker
|
|
|
|
- `ServiceConnection` → même base Service.
|
|
- `WebServiceConnection` (via `Hfsql`) → même base WebService/Business.
|
|
|
|
Aucun mot de passe n'est loggé.
|
|
|
|
## 6. Probes lecture (avant toute écriture)
|
|
|
|
Avant toute écriture, les probes de lecture suivantes doivent réussir :
|
|
|
|
- **ServiceConnection** : ouvrir, `SELECT` lecture sur `TOSRV`.
|
|
- **WebServiceConnection** : ouvrir, `SELECT` lecture sur `MobReception`,
|
|
`SELECT` lecture sur `MobLog`, `SELECT` lecture sur les tables Intervention
|
|
nécessaires.
|
|
|
|
Aucun `INSERT`, `UPDATE` ou `DELETE`. Si un probe échoue : **STOP**.
|
|
|
|
## 7. Licence — reste STOP
|
|
|
|
`BASE_LICENCES` reste physiquement bloquée : les tables `ClientBase`, `Licence`,
|
|
`LicenceHisto` sont absentes. Rien n'est modifié dans la base Licence. Le
|
|
présent E2E ne valide **pas** le parcours Licence.
|
|
|
|
## 8. Authentification du test Sync
|
|
|
|
L'endpoint Sync reste protégé. `MobileAuthMiddleware` n'est **pas désactivé**.
|
|
|
|
Le test de pipeline Sync utilise un mécanisme explicite de TEST basé sur le
|
|
vrai `ITokenService` / `TokenService` :
|
|
|
|
- `SigningKey` de TEST (configuration locale non versionnée).
|
|
- Identité `MobileTokenIdentity` de TEST.
|
|
- Bearer réellement validé par `MobileAuthMiddleware`.
|
|
|
|
Le token est généré par le harness/test E2E (helper `CreateValidToken` dans
|
|
`tests/Omika.Tests/E2E/SyncPipelineE2ETests.cs`).
|
|
|
|
**Limite documentée** : cela valide `Bearer → middleware → endpoint Sync`, mais
|
|
**ne valide pas** `Licence → activate → token`.
|
|
|
|
Aucun bypass auth, aucun middleware désactivé, aucun token hardcodé en source,
|
|
aucun token loggé.
|
|
|
|
## 9. Test E2E automatisé SKIPPABLE
|
|
|
|
Un test E2E automatisé a été créé : `tests/Omika.Tests/E2E/SyncPipelineE2ETests.cs`.
|
|
|
|
Il est **SKIPPABLE** : il n'écrit JAMAIS par défaut. Il exige explicitement les
|
|
trois garde-fous (`AllowHfSqlWrites`, `ServiceDatabaseConfirmedTest`,
|
|
`WebServiceDatabaseConfirmedTest`) tous `true`. Sinon, le test est SKIP.
|
|
|
|
Le test couvre :
|
|
- **CREATE** : envoi via `POST /api/sync/operations` avec un Bearer valide.
|
|
- **TOSRV** : vérification de la réception dans la base Service.
|
|
- **MobReception** : transfert par le Worker.
|
|
- **Business** : création de l'Intervention.
|
|
- **MobLog** : résultat terminal SUCCESS.
|
|
- **status API** : interrogation de l'endpoint moderne avec OperationId.
|
|
- **replay** : réenvoi du même OperationId → idempotence terminale.
|
|
|
|
## 10. Pipeline validé (une fois les garde-fous satisfaits)
|
|
|
|
### CREATE
|
|
|
|
- `OperationId` = nouveau Guid.
|
|
- `RequestedEntityGuid` = nouveau Guid.
|
|
- Référence/titre : `E2E_TEST_<identifiant court>`.
|
|
- Aucune donnée personnelle réelle.
|
|
|
|
### Envoi normal
|
|
|
|
`POST /api/sync/operations` avec `Authorization: Bearer <token test valide>`.
|
|
Aucun INSERT manuel dans TOSRV pour le test principal. Le pipeline validé est :
|
|
HTTP → auth → SyncOperationService → repository → TOSRV.
|
|
|
|
### TOSRV Service
|
|
|
|
Après réception, vérifier dans `ServiceConnection.TOSRV` : `OperationId`,
|
|
`EntityType`, `OperationType`, `RequestedEntityGuid`. Aucune ligne modifiée
|
|
manuellement.
|
|
|
|
### Worker
|
|
|
|
Un seul `Omika.Worker` avec les mêmes configurations Service/WebService.
|
|
Pipeline : `SyncWorker` → `SyncProcessor` → `TransferToReceptionAsync`.
|
|
|
|
### Transfert inter-base
|
|
|
|
`Service.TOSRV` → `WebService.MobReception` → `Service.TOSRV` finalisé/supprimé
|
|
selon le comportement actuel. Vérifier surtout que `OperationId` est identique.
|
|
|
|
### Handler
|
|
|
|
`MobReception` → `SyncProcessor` → `InterventionSyncOperationHandler` (et non
|
|
`DefaultSyncOperationHandler`).
|
|
|
|
### Business result
|
|
|
|
Vérifier dans la base métier TEST que l'Intervention est créée. Ne valider que
|
|
les propriétés réellement envoyées. Identifier `RequestedEntityGuid` et
|
|
`CanonicalEntityGuid` selon le modèle Batik/Omika réellement sélectionné.
|
|
|
|
### MobLog
|
|
|
|
Valider dans `WebServiceConnection.MobLog` : `OperationId`, `SUCCESS`,
|
|
`CanonicalEntityGuid`, résultat terminal, absence d'erreur en succès.
|
|
|
|
### Status API
|
|
|
|
Interroger l'endpoint moderne de status avec `OperationId`. Attendu : `SUCCESS`
|
|
correspondant à MobLog. Le test n'est pas complet tant que ce retour API n'est
|
|
pas validé.
|
|
|
|
### Replay
|
|
|
|
Réenvoyer exactement le même `OperationId` et le même payload. Attendu :
|
|
- aucune deuxième Intervention métier ;
|
|
- résultat terminal réutilisé ;
|
|
- status toujours cohérent.
|
|
|
|
Documenter précisément **idempotence terminale** et ne pas prétendre
|
|
**exactly-once concurrent global**.
|
|
|
|
### UPDATE
|
|
|
|
Après CREATE vert, nouvel `OperationId`, tester UPDATE sur l'Intervention E2E.
|
|
Pipeline : API → Service.TOSRV → WebService.MobReception → Handler → Business →
|
|
MobLog → status SUCCESS.
|
|
|
|
### ARCHIVE
|
|
|
|
Après UPDATE vert, nouvel `OperationId`, tester ARCHIVE logique. Sert aussi de
|
|
cleanup fonctionnel de l'Intervention E2E. Aucun DELETE physique.
|
|
|
|
### REJECTED (éventuel)
|
|
|
|
Si CREATE/UPDATE/ARCHIVE sont verts, envoyer éventuellement un payload invalide
|
|
contrôlé. Attendu : `REJECTED` → MobLog → API status `REJECTED`. Ne provoque pas
|
|
une erreur d'intégrité dangereuse.
|
|
|
|
## 11. Cross-database failure
|
|
|
|
Ne provoque pas volontairement un crash sur les vraies bases. Le scénario
|
|
`MobReception insérée + TOSRV encore présente` est déjà couvert par tests fakes.
|
|
Pour l'E2E réel, observer uniquement le comportement normal.
|
|
|
|
## 12. Un seul Worker
|
|
|
|
Règle actuelle : **UN SEUL `Omika.Worker` actif**. Ne teste pas la concurrence
|
|
multi-worker.
|
|
|
|
## 13. SyncConnection obsolète
|
|
|
|
Ne supprime pas `SyncConnection` pendant ce test. Ce nettoyage reste séparé.
|
|
|
|
## 14. Critères pour autoriser l'écriture
|
|
|
|
Écriture E2E permise UNIQUEMENT si :
|
|
|
|
- A. `ServiceDatabaseConfirmedTest = true`
|
|
- B. `WebServiceDatabaseConfirmedTest = true`
|
|
- C. `AllowHfSqlWrites = true`
|
|
- D. connexion Service réussie
|
|
- E. connexion WebService réussie
|
|
- F. `TOSRV` présente dans Service
|
|
- G. `MobReception` et `MobLog` présentes dans WebService
|
|
- H. base métier cible explicitement TEST
|
|
|
|
Si un seul manque : **STOP avant écriture**.
|
|
|
|
## 15. Aucun schéma HFSQL modifié
|
|
|
|
**Aucun schéma HFSQL n'a été modifié** : aucune création/suppression de table,
|
|
aucune modification de colonne. Des **écritures de données** ont été effectuées
|
|
dans les bases TEST (TOSRV, MobReception, MobLog) lors de l'exécution E2E, mais
|
|
le schéma physique est intact.
|
|
|
|
## 16. Baseline
|
|
|
|
- Tests Mobile : **515/515** (référence 511/511 + 4 tests transactionnels ajoutés
|
|
par le micro-lot idempotence retry, confirmé).
|
|
- Suite unitaire hors HFSQL environnemental : **1755/1755** (référence
|
|
1751/1751 + 4 tests transactionnels, confirmé).
|
|
- Build solution : **VERT** (confirmé).
|
|
- Worker : **VERT**.
|
|
- Android : **VERT**.
|
|
|
|
### Niveau de validation réellement obtenu (compte rendu final)
|
|
|
|
Le test `SyncPipelineE2ETests.Create_Intervention_Pipeline_IsGreen` a été
|
|
**réellement exécuté** (EXECUTED) avec les trois garde-fous à `true` et un
|
|
`Omika.Worker` externe actif. Le pipeline a **réellement abouti** : l'Intervention
|
|
`E2E_TEST_5b915439` a été créée dans BATIK_TEST, le MobLog a enregistré
|
|
`Success`, et l'endpoint status a retourné `SUCCESS`.
|
|
|
|
**Classification : CAS A — CREATE E2E réellement validé (SUCCESS).**
|
|
|
|
Le test a réellement traversé : HTTP API → Bearer réel → MobileAuthMiddleware →
|
|
POST /api/sync/operations → ServiceConnection.TOSRV → Worker externe →
|
|
MobReception → SyncProcessor → InterventionSyncOperationHandler → Business
|
|
BATIK_TEST → MobLog → status API SUCCESS.
|
|
|
|
**Ce qui est réellement validé par ce test dans l'état actuel :**
|
|
- HTTP API réelle (via `WebApplicationFactory<Program>`).
|
|
- Bearer réel via le vrai `TokenService` → `MobileAuthMiddleware` réellement
|
|
traversé.
|
|
- `SyncOperationService` → repository → TOSRV (base Service `BATIK.01_BLS`).
|
|
- Worker externe → `MobReception` (base WebService `BATIK_TEST`).
|
|
- `SyncProcessor` → `InterventionSyncOperationHandler` (handler réellement
|
|
sélectionné).
|
|
- Intervention créée dans BATIK_TEST (Business réel).
|
|
- MobLog `Success` écrit.
|
|
- Status API `SUCCESS`.
|
|
|
|
**Replay** : le test `Replay_SameOperationId_IsIdempotent` a été **réellement
|
|
exécuté** (EXECUTED) mais a **échoué** : le status est resté `Pending` au moment
|
|
de la vérification (le Worker externe n'a pas traité l'opération dans le délai
|
|
du test). Le Worker a néanmoins traité l'opération Replay **après** le test,
|
|
créant l'Intervention `E2E_TEST_0fce297f`.
|
|
|
|
**Diagnostic exact (micro-lot idempotence) :** le test Replay **actuel** génère
|
|
son **propre** OperationId (`0fce297f-e4e4-45ad-9027-b4e6e23d5c8a`), distinct de
|
|
celui du CREATE (`5b915439`). `E2E_TEST_0fce297f` est donc l'Intervention
|
|
**normale** du test Replay, **PAS un doublon** du CREATE. Le test Replay actuel
|
|
est **conceptuellement incorrect** : il envoie les deux POST sans attendre le
|
|
SUCCESS terminal du premier. Il a été **corrigé** (autonome, polling terminal,
|
|
comptage métier = 1). L'idempotence terminale n'est donc **pas encore démontrée**
|
|
par une exécution réelle (garde-fous `false`).
|
|
|
|
|
|
|
|
**Micro-lot DÉBLOCAGE STARTUP (appliqué) :**
|
|
|
|
Trois blocages logiciels de startup ont été corrigés :
|
|
|
|
1. **Startup API — corrigé.** Dans `src/Omika.Api/Program.cs`, le bloc
|
|
`using (var scope = app.Services.CreateScope())` disposait le scope de manière
|
|
**synchrone** (`Dispose()`), alors que `HfsqlConnection` n'implémente que
|
|
`IAsyncDisposable`. Corrigé en `await using var scope =
|
|
app.Services.CreateAsyncScope();` (DisposeAsync propre). Validé par le test
|
|
`SyncEndpointsTests` (3/3) qui démarre réellement
|
|
`WebApplicationFactory<Program>` : plus aucune `InvalidOperationException
|
|
IAsyncDisposable`.
|
|
|
|
2. **Startup Worker DI — corrigé.** `SyncWorker` et `SyncProcessor` attendent le
|
|
POCO `SyncOptions` directement dans leur constructeur, mais seul
|
|
`IOptions<SyncOptions>` était enregistré. Corrigé dans
|
|
`src/Omika.Worker/Program.cs` en enregistrant le POCO comme singleton résolu
|
|
depuis `IOptions<SyncOptions>` (valeurs issues de la configuration, jamais
|
|
hardcodées). Le bloc `using (var scope = host.Services.CreateScope())` a aussi
|
|
été corrigé en `CreateAsyncScope()`.
|
|
|
|
3. **Transport Business/WebService ODBC → OLEDB — corrigé.** Le chemin ODBC
|
|
(`HfsqlConnection` / `IHfsqlConnection`) était utilisé par la détection de
|
|
schéma, le wrapper `WebServiceHfSqlConnection` et les repositories métier
|
|
(Intervention). Sur cette machine, le pilote ODBC HFSQL est absent
|
|
(`IM002 Source de données introuvable`). Un override ciblé OLE DB a été ajouté
|
|
(`BusinessOleDbConnection` + `AddOmikaDataBusinessOleDb`) et branché dans
|
|
`Program.cs` de l'API et du Worker. La connexion métier passe désormais par
|
|
OLE DB `PCSOFT.HFSQL`, sans driver ODBC.
|
|
|
|
**Startup Worker — blocage ODBC résolu, cycle réel exercé :**
|
|
|
|
Après les corrections ci-dessus, le Worker **démarre réellement** avec OLE DB
|
|
(plus aucune erreur ODBC) et atteint le cycle de synchronisation réel :
|
|
|
|
```
|
|
info: Omika.Worker.SyncWorker[0]
|
|
Worker de synchronisation démarré.
|
|
info: Microsoft.Hosting.Lifetime[0]
|
|
Application started. Press Ctrl+C to shut down.
|
|
```
|
|
|
|
Le cycle exécute réellement `SyncProcessor.ProcessAsync` →
|
|
`SyncTransferRepository.TransferToReceptionAsync` → requête SQL réelle sur la
|
|
base Service (`BATIK.01_BLS`, serveur 127.0.0.1:4900). La connexion OLE DB à la
|
|
base Service fonctionne (la requête est envoyée au serveur HFSQL).
|
|
|
|
**Décalage de schéma `TOSRV` — CORRIGÉ (alignement code ↔ schéma physique) :**
|
|
|
|
Le cycle échouait sur une erreur de **schéma** de la table `TOSRV` réelle :
|
|
|
|
```
|
|
System.Data.OleDb.OleDbException (0x80040E14): Rubrique TS_PKID inconnue dans
|
|
le fichier. Vérifiez que la requête est en phase avec la description des fichiers.
|
|
SELECT >>>>TS_PKID<<<<, TS_Date, TS_Mobile, ...
|
|
Code erreur : 70208
|
|
```
|
|
|
|
Le code (`SyncTransferRepository`) référençait une colonne `TS_PKID` dans
|
|
`TOSRV`, absente de la table réelle. Un **probe OLE DB en lecture seule** sur la
|
|
base Service (`BATIK.01_BLS`) a confirmé que la clé primaire réelle de `TOSRV`
|
|
est **`IDTOSRV`** (entier). Le code a été aligné sur le schéma physique :
|
|
|
|
- `TOSRVRow.cs` : propriété `IdToSrv` mappée sur la colonne `IDTOSRV` (type `int`).
|
|
- `SyncTransferRepository.cs` : `SELECT`/`DELETE`/`INSERT` utilisent désormais
|
|
`IDTOSRV` (plus aucune référence `TS_PKID`).
|
|
- `SyncTransferRepositoryTests.cs` : ligne fake `TOSRV` alignée sur `IDTOSRV`.
|
|
|
|
Le probe a aussi confirmé que `MobReception` (`MR_PKID`, `MR_IDToSRV`, ...) et
|
|
`MobLog` (`ML_PKID`, `ML_DateCreat`, ...) sont conformes aux colonnes utilisées
|
|
par le code. Le décalage de schéma `TOSRV` est donc **résolu**.
|
|
|
|
|
|
**Ce que le test validerait si les garde-fous étaient `true`, le pilote ODBC
|
|
HFSQL disponible ET un Worker externe tournait :**
|
|
- HTTP API réelle (via `WebApplicationFactory<Program>`).
|
|
- Bearer réel via le vrai `TokenService` → `MobileAuthMiddleware` réellement
|
|
traversé.
|
|
- `SyncOperationService` → repository → TOSRV (base Service).
|
|
- Worker externe → `MobReception` (base WebService) → handler Intervention →
|
|
Business → MobLog → status API.
|
|
|
|
**Limite restante avant un E2E physique complet :**
|
|
1. ~~Corriger le décalage de schéma `TOSRV`~~ — **RÉSOLU** : le code
|
|
(`SyncTransferRepository`) et le modèle (`TOSRVRow`) utilisent désormais la
|
|
colonne réelle `IDTOSRV` (confirmée par probe OLE DB en lecture seule sur
|
|
`BATIK.01_BLS`). Plus aucune référence `TS_PKID`. Le cycle de synchronisation
|
|
ne devrait plus échouer sur cette erreur de schéma (code 70208).
|
|
2. ~~Passer les trois garde-fous à `true`~~ — **FAIT** (13/08/2026) : le test a
|
|
été réellement exécuté avec les garde-fous à `true`.
|
|
3. ~~Démarrer un `Omika.Worker` réel~~ — **FAIT** (13/08/2026) : un Worker
|
|
externe a été démarré et a traité l'opération jusqu'au handler.
|
|
4. ~~Exécuter le test et vérifier l'écriture réelle~~ — **FAIT** (13/08/2026) :
|
|
le test CREATE a été exécuté ; TOSRV, MobReception, SyncProcessor, le handler,
|
|
le Business, MobLog et le status API ont été réellement exercés. **CREATE
|
|
VALIDÉ** : Intervention `E2E_TEST_5b915439` créée, MobLog `Success`, status
|
|
API `SUCCESS`.
|
|
5. **REPLAY — TEST CORRIGÉ, À RÉ-EXÉCUTER** : le test Replay **actuel** était
|
|
conceptuellement incorrect (deux POST sans attendre SUCCESS). Il a été
|
|
**corrigé** (autonome, polling terminal, comptage métier = 1). `E2E_TEST_0fce297f`
|
|
est l'Intervention **normale** du test Replay (OperationId distinct
|
|
`0fce297f`), **PAS un doublon** du CREATE `5b915439`. L'idempotence terminale
|
|
n'est **pas encore démontrée** par une exécution réelle (garde-fous `false`).
|
|
6. Valider le parcours Licence → activate → token (base Licence toujours
|
|
physiquement bloquée).
|
|
|
|
|
|
|
|
**Note** : le blocage ODBC (`IM002`) est **résolu** par le routage OLE DB
|
|
(`BusinessOleDbConnection`). Le décalage de schéma `TOSRV` (`TS_PKID` → `IDTOSRV`)
|
|
est **résolu** par l'alignement code ↔ schéma physique. Le Worker et l'API
|
|
démarrent réellement avec OLE DB et atteignent le cycle de synchronisation réel.
|
|
Le test E2E CREATE a été **réellement exécuté** (13/08/2026) avec les garde-fous
|
|
à `true` et un Worker externe actif, et a **abouti** (SUCCESS). Le test Replay
|
|
**actuel** était conceptuellement incorrect (deux POST sans attendre SUCCESS) et
|
|
a été **corrigé** (autonome, polling terminal, comptage métier = 1).
|
|
`E2E_TEST_0fce297f` est l'Intervention **normale** du test Replay (OperationId
|
|
distinct), **PAS un doublon** du CREATE. L'idempotence terminale n'est **pas
|
|
encore démontrée** par une exécution réelle (garde-fous `false`).
|
|
|
|
|
|
|
|
|
|
**Note sur le "faux pass" des tests E2E (garde-fous `false`) :**
|
|
|
|
Avec les garde-fous `false`, les deux tests `SyncPipelineE2ETests` affichent
|
|
"2/2 réussis" mais effectuent un `return` immédiat **avant toute assertion** :
|
|
aucun maillon du pipeline n'est exercé. Ce n'est pas un vrai SKIP xUnit (le
|
|
framework actuel ne permet pas un skip runtime sans nouvelle dépendance). Il ne
|
|
faut donc **pas** interpréter "2/2 E2E verts" comme une validation du pipeline.
|
|
Les garde-fous restent `false` : aucune écriture HFSQL n'est possible.
|
|
|
|
|
|
|
|
|
|
### Réconciliation baseline 1748 → 1742
|
|
|
|
Le filtre `FullyQualifiedName!~Omika.Tests.Connection.HfSqlOleDb` exclut les
|
|
tests dont le FullyQualifiedName contient `Omika.Tests.Connection.HfSqlOleDb`.
|
|
|
|
**Actuellement, 30 tests sont exclus par ce filtre** (8 classes de tests) :
|
|
|
|
| Classe de test | Nb tests | Nature |
|
|
|---|---|---|
|
|
| `HfSqlOleDbConnectionStringBuilderTests` | 11 | unitaires purs (aucun serveur requis) |
|
|
| `HfSqlOleDbIntegrationTests` | 4 | intégration réelle (serveur requis) |
|
|
| `HfSqlOleDbInterventionIntegrationTests` | 5 | intégration réelle (serveur requis) |
|
|
| `HfSqlOleDbCollaborateurLifecycleIntegrationTests` | 2 | intégration réelle (serveur requis) |
|
|
| `HfSqlOleDbCreateIntegrationTests` | 2 | intégration réelle (serveur requis) |
|
|
| `HfSqlOleDbDossierLifecycleIntegrationTests` | 2 | intégration réelle (serveur requis) |
|
|
| `HfSqlOleDbInterventionLifecycleIntegrationTests` | 2 | intégration réelle (serveur requis) |
|
|
| `HfSqlOleDbRendezVousLifecycleIntegrationTests` | 2 | intégration réelle (serveur requis) |
|
|
|
|
**Raison exacte de la baisse 1748 → 1742** : le dernier lot a introduit les
|
|
tests `HfSqlOleDb*` (30 tests) qui sont **exclus par le filtre** (leur
|
|
FullyQualifiedName contient `Omika.Tests.Connection.HfSqlOleDb`), ainsi que les
|
|
tests `E2EOptionsTests` (10 tests visibles). Le nombre de tests "visibles" a
|
|
donc diminué de 6 par rapport à la référence 1748, car le dernier lot a déplacé
|
|
dans le namespace `HfSqlOleDb` (exclu par le filtre) des tests qui étaient
|
|
auparavant comptés dans la suite visible. Le filtre n'a pas changé : il exclut
|
|
désormais davantage de tests car davantage de tests portent le préfixe
|
|
`HfSqlOleDb` dans leur FullyQualifiedName.
|
|
|
|
**Note** : le filtre exclut aussi des tests **unitaires purs** (ex.
|
|
`HfSqlOleDbConnectionStringBuilderTests`, 11 tests) qui ne nécessitent aucun
|
|
serveur HFSQL. Ces tests sont exclus uniquement à cause de leur nom de classe
|
|
contenant `HfSqlOleDb`, pas parce qu'ils sont des tests d'intégration. C'est un
|
|
effet de bord du filtre par FullyQualifiedName.
|
|
|
|
|
|
## 17. Correction transactionnelle — idempotence retry (implémentée et testée)
|
|
|
|
La fenêtre de duplication retry (business réussi mais terminal absent) a été
|
|
fermée par une **transaction OLE DB locale BATIK_TEST partagée** pour chaque
|
|
opération Sync réussie.
|
|
|
|
### Abstraction retenue
|
|
|
|
Une abstraction ciblée `IWebServiceTransactionContext` (pas un framework
|
|
UnitOfWork générique) expose la session transactionnelle BATIK_TEST :
|
|
|
|
- `Connection` : la connexion OLE DB BATIK_TEST physique partagée.
|
|
- `BeginAsync` / `CommitAsync` / `RollbackAsync`.
|
|
|
|
`BusinessOleDbConnection` et `WebServiceHfSqlConnection` utilisent cette même
|
|
session physique pendant le traitement, garantissant que les commandes métier
|
|
(Intervention) et les commandes Sync (MobLog, MobReception) s'exécutent sur la
|
|
**même `OleDbConnection`** et la **même `OleDbTransaction`**.
|
|
|
|
### Support transactionnel HFSQL OLE DB
|
|
|
|
`HfSqlOleDbConnection` expose désormais `BeginTransaction` / `Commit` /
|
|
`Rollback` via `System.Data.OleDb`. Les commandes exécutées pendant une
|
|
transaction active reçoivent `command.Transaction = transaction`. Les commandes
|
|
hors transaction continuent de fonctionner normalement.
|
|
|
|
### Orchestration (SyncProcessor)
|
|
|
|
L'orchestration transactionnelle reste au niveau `SyncProcessor` (le handler
|
|
métier reste pur, sans `BeginTransaction`/`Commit`/`Rollback`) :
|
|
|
|
- **SUCCESS** : `Begin` → handler → MobLog SUCCESS → finalisation MobReception →
|
|
`Commit`. Soit tout est durable, soit rien.
|
|
- **Retryable failure** (exception ou TransientFailure) : `Rollback` de la
|
|
transaction métier, puis retry hors de la transaction annulée (MobReception
|
|
reste disponible, retryCount incrémenté). Aucune Intervention partielle durable.
|
|
- **PermanentFailure / REJECTED** : `Rollback` des modifications métier, puis
|
|
MobLog REJECTED + finalisation MobReception dans une écriture séparée et
|
|
durable (le diagnostic REJECTED n'est pas perdu avec le rollback métier).
|
|
|
|
### TOSRV
|
|
|
|
TOSRV (`BATIK.01_BLS`) reste **complètement hors** de cette transaction. Aucune
|
|
transaction distribuée, aucun DTC, aucun partage de la transaction BATIK_TEST
|
|
avec `ServiceConnection`.
|
|
|
|
### GetTerminalResultAsync
|
|
|
|
`GetTerminalResultAsync` reste **impérativement** appelé **avant** le handler :
|
|
il protège le replay terminal (MobLog SUCCESS existe → handler non rappelé). La
|
|
transaction ajoute la protection complémentaire avant terminal.
|
|
|
|
### Tests ajoutés (SyncProcessorTests)
|
|
|
|
- `Rollback_On_Exception_Before_MobLog_Leaves_No_Durable_Business` : handler
|
|
écrit une Intervention puis lève une exception → ROLLBACK → Intervention
|
|
durable = 0, MobLog SUCCESS = 0.
|
|
- `Retry_After_Rollback_Commits_Business_And_MobLog` : après rollback, retry →
|
|
COMMIT → Intervention = 1, MobLog SUCCESS = 1.
|
|
- `Crash_After_Commit_Does_Not_Reapply_Handler_On_Retry` : après COMMIT, replay
|
|
→ GetTerminalResultAsync SUCCESS → handler non rappelé, aucune Intervention
|
|
supplémentaire.
|
|
- `Two_Different_OperationIds_Produce_Two_Business_Creations` : deux OperationId
|
|
distincts → 2 créations métier (la transaction n'est pas une déduplication
|
|
globale).
|
|
|
|
**État : implémentée et testée (tests transactionnels verts), mais pas encore
|
|
validée par un E2E physique (garde-fous `false`).**
|
|
|
|
|
|
## 18. Feu vert pour LOT INTERVENTION MOBILE WRITE
|
|
|
|
|
|
**PARTIEL** — le test end-to-end réel **CREATE** a été **exécuté** (EXECUTED) et
|
|
a **abouti** (SUCCESS) : l'Intervention `E2E_TEST_5b915439` a été créée dans
|
|
BATIK_TEST, le MobLog a enregistré `Success`, et l'endpoint status a retourné
|
|
`SUCCESS`. Le pipeline complet (API → TOSRV → Worker → MobReception → Handler →
|
|
Business → MobLog → Status) est **réellement validé** pour CREATE.
|
|
|
|
Le test **Replay** a été **exécuté** (EXECUTED) mais a **échoué** : le status est
|
|
resté `Pending` au moment de la vérification (le Worker externe n'a pas traité
|
|
l'opération dans le délai du test). Le Worker a néanmoins traité l'opération
|
|
Replay **après** le test, créant l'Intervention `E2E_TEST_0fce297f`.
|
|
|
|
**Diagnostic exact (micro-lot idempotence) :** le test Replay **actuel** génère
|
|
son **propre** OperationId (`0fce297f-e4e4-45ad-9027-b4e6e23d5c8a`), distinct de
|
|
celui du CREATE (`5b915439`). `E2E_TEST_0fce297f` est donc l'Intervention
|
|
**normale** du test Replay, **PAS un doublon** du CREATE. Le test Replay actuel
|
|
est **conceptuellement incorrect** : il envoie les deux POST sans attendre le
|
|
SUCCESS terminal du premier. Il a été **corrigé** (autonome, polling terminal,
|
|
comptage métier = 1). L'idempotence terminale n'est donc **pas encore démontrée**
|
|
par une exécution réelle (garde-fous `false`).
|
|
|
|
Le feu vert **complet** pour le lot INTERVENTION MOBILE WRITE reste conditionné
|
|
à une nouvelle exécution CREATE + Replay réussie avec le test Replay corrigé
|
|
(autonome, polling terminal, comptage métier = 1).
|
|
|
|
|