38 KiB
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-foustrue, 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 àfalseaprè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-foustrue. Le pipeline a réellement atteint le premier SUCCESS (le test a passé l'assertionAssert.NotNull(firstStatus)à la ligne 302). Le test a ensuite échoué sur un bug de disposition du helperCountInterventionsAsync(ligne 177) :System.InvalidOperationException : 'WebServiceHfSqlConnection' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.Le helper utiliseusing var scope = _factory.Services.CreateScope()(disposition synchrone) alors queWebServiceHfSqlConnectionn'implémente queIAsyncDisposable— le même problèmeIAsyncDisposabledéjà corrigé dansProgram.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 àfalseet le Worker arrêté après la tentative. Le test Replay corrigé reste à retester physiquement après correction du helperCountInterventionsAsync(disposition asynchrone).
Mise à jour 13/08/2026 17:48 — micro-fix disposition + nouvelle tentative : le helper
CountInterventionsAsynca été corrigé (disposition asynchroneawait using var scope = _factory.Services.CreateAsyncScope()), le projet de test compile. Le test Replay a été ré-exécuté physiquement (Worker E2ESync__RetryDelaySeconds=5, garde-foustrue). 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 helperCountInterventionsAsync(ligne 175) :System.InvalidOperationException : La connexion Business doit être ouverte avant d'exécuter une commande.(BusinessOleDbConnection.EnsureOpen()ligne 317). Le helper obtientIWebServiceHfSqlConnectionet appelleExecuteScalarAsyncsans 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 àfalseet le Worker arrêté après la tentative. Le test Replay corrigé reste à retester physiquement après ouverture de la connexion dansCountInterventionsAsync.
Mise à jour 13/08/2026 17:54 — micro-fix OpenAsync + E2E RÉUSSI : le helper
CountInterventionsAsynca été corrigé (ouverture explicite de la connexionawait connection.OpenAsync(cancellationToken)avantExecuteScalarAsync), le projet de test compile. Le test Replay a été ré-exécuté physiquement (Worker E2ESync__RetryDelaySeconds=5, garde-foustrue) 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 àfalseet le Worker arrêté après la tentative. Baseline Mobile : ÉCHEC sur 2 tests transactionnels (Crash_After_Commit_Does_Not_Reapply_Handler_On_Retryligne 403 etTwo_Different_OperationIds_Produce_Two_Business_Creationsligne 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ériqueFakeSyncOperationHandler(SyncOperationResult.Success()), qui retourne SUCCESS sans écrire d'Intervention dans le fake transactionnel. Le compteurtx.CommittedInterventionsest alimenté par le handler viaWriteIntervention(comme le fontThrowingAfterWriteHandler/ThrowingThenSuccessHandlerdes deux tests verts). Un handler dédiéSuccessWritingHandler(écrit une Intervention + retourne SUCCESS + exposeCallCount) a été ajouté et utilisé par les deux tests. PourCrash_After_Commit, l'assertionrepo.Succeeded.Count == 1a été remplacée parAssert.Contains(env.OperationId, repo.Succeeded): au replay, leSyncProcessorreconstitue le log terminal SUCCESS (re-marquage viaMarkSuccessAsync) sans rappeler le handler (comportement correct d'idempotence par OperationId) ; le fakeSucceededaccumule 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 toujoursfalse.
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é
Pendingcar 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 lignesE2E_TEST_5b915439sont des doublons du MÊME OperationId5b915439-d9d9-44b5-9552-1f8f90db96c3(défaut d'idempotence retry business : le handler CREATE a été rappelé à chaque retry,retryCount=16au SUCCESS, recréant une Intervention à chaque tentative). La ligneE2E_TEST_0fce297fest l'Intervention normale du test Replay (OperationId distinct0fce297f-e4e4-45ad-9027-b4e6e23d5c8a), PAS un doublon du CREATE. Aucune donnée personnelle réelle. - MobLog : logs
Pending+Successpour 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, tableTOSRV.IWebServiceHfSqlConnection→ base WebService/Business, tablesMobReceptionetMobLog.
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 :
"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 = trueServiceDatabaseConfirmedTest = trueWebServiceDatabaseConfirmedTest = 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_BLSactuelle).WebServiceConnection(viaHfsql) → base WebService/Business (BATIK_TEST/OMIKA_TESTselon l'environnement choisi).
Omika.Worker
ServiceConnection→ même base Service.WebServiceConnection(viaHfsql) → 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,
SELECTlecture surTOSRV. - WebServiceConnection : ouvrir,
SELECTlecture surMobReception,SELECTlecture surMobLog,SELECTlecture 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 :
SigningKeyde TEST (configuration locale non versionnée).- Identité
MobileTokenIdentityde 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/operationsavec 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.
TOSRVprésente dans Service - G.
MobReceptionetMobLogpré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→MobileAuthMiddlewareréellement traversé. SyncOperationService→ repository → TOSRV (base ServiceBATIK.01_BLS).- Worker externe →
MobReception(base WebServiceBATIK_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 :
-
Startup API — corrigé. Dans
src/Omika.Api/Program.cs, le blocusing (var scope = app.Services.CreateScope())disposait le scope de manière synchrone (Dispose()), alors queHfsqlConnectionn'implémente queIAsyncDisposable. Corrigé enawait using var scope = app.Services.CreateAsyncScope();(DisposeAsync propre). Validé par le testSyncEndpointsTests(3/3) qui démarre réellementWebApplicationFactory<Program>: plus aucuneInvalidOperationException IAsyncDisposable. -
Startup Worker DI — corrigé.
SyncWorkeretSyncProcessorattendent le POCOSyncOptionsdirectement dans leur constructeur, mais seulIOptions<SyncOptions>était enregistré. Corrigé danssrc/Omika.Worker/Program.csen enregistrant le POCO comme singleton résolu depuisIOptions<SyncOptions>(valeurs issues de la configuration, jamais hardcodées). Le blocusing (var scope = host.Services.CreateScope())a aussi été corrigé enCreateAsyncScope(). -
Transport Business/WebService ODBC → OLEDB — corrigé. Le chemin ODBC (
HfsqlConnection/IHfsqlConnection) était utilisé par la détection de schéma, le wrapperWebServiceHfSqlConnectionet 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é dansProgram.csde l'API et du Worker. La connexion métier passe désormais par OLE DBPCSOFT.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éIdToSrvmappée sur la colonneIDTOSRV(typeint).SyncTransferRepository.cs:SELECT/DELETE/INSERTutilisent désormaisIDTOSRV(plus aucune référenceTS_PKID).SyncTransferRepositoryTests.cs: ligne fakeTOSRValignée surIDTOSRV.
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→MobileAuthMiddlewareré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 :
Corriger le décalage de schéma— RÉSOLU : le code (TOSRVSyncTransferRepository) et le modèle (TOSRVRow) utilisent désormais la colonne réelleIDTOSRV(confirmée par probe OLE DB en lecture seule surBATIK.01_BLS). Plus aucune référenceTS_PKID. Le cycle de synchronisation ne devrait plus échouer sur cette erreur de schéma (code 70208).Passer les trois garde-fous à— FAIT (13/08/2026) : le test a été réellement exécuté avec les garde-fous àtruetrue.Démarrer un— FAIT (13/08/2026) : un Worker externe a été démarré et a traité l'opération jusqu'au handler.Omika.WorkerréelExé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É : InterventionE2E_TEST_5b915439créée, MobLogSuccess, status APISUCCESS.- 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_0fce297fest l'Intervention normale du test Replay (OperationId distinct0fce297f), PAS un doublon du CREATE5b915439. L'idempotence terminale n'est pas encore démontrée par une exécution réelle (garde-fousfalse). - 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) :
Rollbackde la transaction métier, puis retry hors de la transaction annulée (MobReception reste disponible, retryCount incrémenté). Aucune Intervention partielle durable. - PermanentFailure / REJECTED :
Rollbackdes 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).