Aller au contenu

Histoire ING-006 : Pack de tests d'intégration transversaux (isolation, grand fichier, octet magique, matrice de crash, prod flag)

Module : ingestion Tranche : Sous-tranche de niveau 1 ING-001-e (architecture §11) Side : [BACKEND] Version cible : V0.0.0 Priorité : 6 Dépend de : ING-002, ING-003, ING-004, ING-005 (tous les comportements sont implémentés) Peut être développé en même temps que : ING-005 (l'échafaudage de test peut commencer ; les assertions sont épinglées une fois que l'ING-005 a été mise en place) Ordre de fusion : Dernière - porte la version v0.0.0 de l'ING Complexité estimée : M Histoires d'utilisateurs de la DRP : N/A - Technical Story (release-gate test pack) Cadre de travail : N/A - backend uniquement


Objectif

Lier la version v0.0.0 d'ING avec un pack de tests d'intégration de niveau régression couvrant les préoccupations transversales mentionnées dans l'architecture §12 (Risques A1-A10) et §13 (Infrastructure de test) : isolation SHA-256 inter-locataires, gestion de grands fichiers de 5 000 lignes dans le budget mémoire, rejet de l'usurpation d'octets magiques, matrice complète de récupération en cas de crash, et une fumée de drapeau de fonctionnalité fermée pour le profil prod. Ces tests permettent de détecter les régressions que les tests ING-002..005 laisseraient passer.


Portée du backend

Entités

Aucune nouvelle. Tests de lecture des lignes existantes ingestion_import, ingestion_import_row, customer, contract, et audit_log pour les assertions.

Migrations

Aucune.

Couche de service

Pas de code de production. Nouvelles classes de test uniquement (sous backend/src/test/java/.../ingestion/) : - IngestionCrossTenantIsolationIT (architecture §13.3 sample) - même SHA-256 déposé dans les locataires A et B ; affirmer que chacun obtient sa propre ligne COMMITTED et qu'il n'y a pas de pollution croisée. - IngestionLargeFileMemoryIT - 5 000 lignes CSV générées ; assert end-to-end commit ≤10 s parse-validate + ≤5 s commit (architecture §8) et peak heap <256 MB via JFR. - IngestionMagicByteSpoofIT - Fichier nommé .csv dont les octets sont OOXML → PARSE_FAILED avec FORMAT_NON_SUPPORTE ; fichier nommé .xlsx dont les octets sont CSV → PARSE_FAILED avec FORMAT_NON_SUPPORTE. - IngestionCrashRecoveryMatrixIT - trois scénarios selon l'architecture §5.1 : (a) crash après INSERT PICKED_UP avant parse → recovery kicks in ; (b) crash après UPDATE PARSE_FAILED avant MinIO move → next tick repeats the idempotent COPY+DELETE ; (c) crash après COMMIT business TX avant MinIO move → next tick repeats the move (file moves to processed even though the import is already COMMITTED). - IngestionProdFlagClosedSmokeIT (architecture §13.5 sample) - spring.profiles.active=prod + feature.artci.phone-auth.granted=false ; tick → no-op + WARN log + file untouched. - IngestionDoubleProfileSmokeIT - exécute le happy path AC-001 de ING-004 deux fois, une fois avec SHARED datasource, une fois avec BYO datasource provisionné à la configuration du test (architecture §4.2). Affirme les mêmes résultats.

Points d'extrémité de l'API

Aucun.

Règles de validation

Aucune nouvelle. Ces tests confirment le comportement de validation existant.

Considérations sur les multi-tenants

L'objectif de cette tranche : prouver l'isolation des locataires en cas de collision SHA-256 (la régression la plus risquée selon l'architecture §12 A10). L'outil de test contient 3 locataires par défaut. La fumée BYO est couverte explicitement dans IngestionDoubleProfileSmokeIT.

Audit & Logging

Pas de nouveaux événements. Les tests confirment le contenu de audit_log : - Test inter-locataires : exactement un ING_COMMITTED par locataire ; zéro ING_DUPLICATE_FILE. - Test des gros fichiers : exactement un ING_COMMITTED avec rows_created=5000. - Matrice de crash : ING_PARSE_FAILED pour (a) ; les ING_COMMITTED/ING_PARSE_FAILED originaux d'avant le crash pour (b) et (c) ; pas d'émission d'audit dupliquée sur le second tick.


Champ d'application du frontend

N/A - backend uniquement.

États de l'interface utilisateur (TOUS OBLIGATOIRES)

State Behavior French Copy
Loading N/A — backend only
Empty N/A — backend only
Error N/A — backend only
Offline / low network N/A — backend only
Success N/A — backend only

Comportement réactif

N/A.

Interactions

N/A.


Critères d'acceptation

AC-001: Cross-tenant SHA-256 isolation
Given the same byte-identical CSV dropped by tenant A and tenant B
When IngestionService.tick() runs
Then both tenants have exactly 1 `ingestion_import` row with status=COMMITTED
  And neither tenant has a status=DUPLICATE row
  And `customer` rows for tenant A have only tenant_id=A; rows for tenant B have only tenant_id=B
  And the SQL `SELECT count(*) FROM customer WHERE tenant_id=B AND source_customer_id IN (SELECT source_customer_id FROM customer WHERE tenant_id=A)` returns the number of distinct source ids (rows ARE the same source ids — they are not the SAME rows in DB).
AC-002: Large file (5 000 rows) commits within performance + memory budget
Given a generated 5 000-row CSV in `/<tenantA>/inbox/`
When IngestionService.tick() runs
Then parse + validate completes in ≤10 s
  And the commit transaction completes in ≤5 s
  And max heap usage during the run is <256 MB (asserted via JFR snapshot)
  And `ingestion_import.rows_created = 5000`.
AC-003: Crash recovery matrix — all three crash points reach a clean steady state by tick #2
Given test scenarios (a) crash after PICKED_UP INSERT, (b) crash after PARSE_FAILED UPDATE but before MinIO move, (c) crash after COMMIT but before MinIO move
When the application boots and the next tick runs
Then (a): row is now PARSE_FAILED via startup recovery, file moved to /rejected/, audit emitted
  And (b): row remains PARSE_FAILED, file is now in /rejected/ (idempotent re-move), no duplicate audit
  And (c): row remains COMMITTED, file is now in /processed/ (idempotent re-move), no duplicate audit and no duplicate customer/contract rows.
AC-004: Prod feature-flag-closed smoke + magic-byte spoof rejection
Given `spring.profiles.active=prod` and `feature.artci.phone-auth.granted=false`
  And a file `<tenantA>/inbox/clean-5-rows.csv`
When IngestionService.tick() runs
Then zero `ingestion_import` rows are created, the file stays in inbox, and the `[LEGAL-REVIEW] cron skipped` WARN is logged

And given (separately) the flag opened and a spoofed file `<tenantA>/inbox/spoof.csv` whose first bytes are `50 4B 03 04` (OOXML)
When the next tick runs
Then `ingestion_import.status=PARSE_FAILED` with `parse_error_detail.anomaly_counts_by_code.FORMAT_NON_SUPPORTE >= 1`
  And the file is in /<tenantA>/rejected/ with sidecar JSON containing `"FORMAT_NON_SUPPORTE"`.

Règles de conformité

  • Règle critique #1 (isolation des locataires) - IngestionCrossTenantIsolationIT est le garde de régression dédié.
  • Architecture §7.6 A1 (zip-bomb / XXE) + A5 (POI heap) - IngestionLargeFileMemoryIT applique le plafond de 256 Mo sur le chemin le plus sollicité.
  • Architecture §7.6 A4 (évasion de clés entre locataires) - couvert par l'invariant SQL de l'AC-001.
  • Position de conformité ARTCI (V0 PRD §6.4) - IngestionProdFlagClosedSmokeIT est la porte de libération binaire.

Normes et conventions

  • docs/standards/critical-rules.md (#1, #3).
  • docs/standards/multi-datasource-patterns.md § modèles de test d'intégration sous SHARED + BYO.
  • docs/standards/java-spring-guidelines.md § test d'intégration slicing + JFR.
  • docs/standards/git-workflow.md - branch feat/ingestion-ING-006 ; PR title test(ingestion) : cross-cutting integration pack (ING-006).

Exigences de test

Tests unitaires

  • N/A (cette tranche correspond à des tests d'intégration par définition).

Tests d'intégration

Les six classes de tests listées sous "Service Layer" ci-dessus. CI doit les exécuter sur chaque PR touchant ingestion/.

Ce que l'AQ validera

  • ./gradlew :backend:integrationTest --tests "*Ingestion*IT" vert sur un checkout propre.
  • Le rapport CI montre une ligne de tas affirmée par JFR pour IngestionLargeFileMemoryIT.
  • BYO datasource smoke variant passe - confirme la promesse de l'architecture §4.2 qu'aucun changement de code ING n'est nécessaire à travers les profils DB.

Hors du champ d'application

  • Tests de performance au-delà de 5 000 lignes (V1).
  • Tests de chaos / d'injection de fautes sur la disponibilité de MinIO (V1).
  • Tests de régression de l'ordonnancement multi-instance de ShedLock (V1 - déploiement d'une seule instance dans la v0.0.0).
  • Tests de détection de la dérive du DOB (ING_DOB_DRIFT_DETECTED) - V0.0.2.
  • Tests du flux Atelier - V0.0.3.
  • Tests de l'interface utilisateur - N/A (pas d'interface utilisateur).
  • Assertions de bout en bout du consommateur OP (seulement que l'événement a été publié ; le comportement de rafraîchissement de la cohorte côté OP est testé dans la propre histoire de l'OP).

Définition de Fait

  • [Tous les endpoints du backend sont implémentés et renvoient des réponses correctes (N/A - aucun endpoint n'a été ajouté)
  • [Toutes les pages du frontend s'affichent correctement à 360 / 768 / 1024 px (N/A - backend uniquement)
  • [Les 5 états de l'interface utilisateur sont implémentés (N/A - backend uniquement)
  • [La micro-copie française correspond exactement aux spécifications du design (N/A - backend uniquement)
  • [Tous les critères d'acceptation sont passés manuellement
  • [Tests unitaires et d'intégration écrits et réussis (six classes informatiques ci-dessus)
  • [L'isolation des locataires est vérifiée (classe informatique dédiée + assertion d'invariant SQL)
  • [ ] [Histoires de clients] payload + Slow-3G + resumability (N/A - backend uniquement)
  • [Les entrées du journal d'audit sont présentes pour chaque événement requis (chaque test confirme la présence des lignes attendues du audit_log et l'absence de doublons)
  • [Pas de défaillances silencieuses sur des erreurs de réseau (la matrice de crash IT couvre les cas de défaillances partielles de MinIO + DB)