Files
Omika/docs/E2E_SYNC_VALIDATION.md
2026-10-23 18:46:50 +02:00

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-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 :

"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).