Aller au contenu

PD-278 — Agent QA Unit/Integration Report: dip-tests

Module: dip-tests Agent: agent-qa-unit-integration Date: 2026-03-01 Story: PD-278 — NF Z42-013: ajout contractuel de l'etat DIP


1. Fichiers crees

Fichier Tests Lignes Description
src/modules/documents/services/dissemination.service.spec.ts 54 1119 Service layer: transitions, guards, transactions, audit, attestation
src/modules/documents/controllers/dissemination.controller.spec.ts 10 258 Controller layer: delegation, DTO mapping, actor extraction
src/modules/documents/guards/dissemination-rate-limit.guard.spec.ts 15 286 Guard layer: dual Redis counters, fail-closed, boundaries
src/modules/documents/filters/dissemination-audit-exception.filter.spec.ts 20 482 Filter layer: synchronous audit, error routing, transaction lifecycle
Total 99 2145

2. Fichier modifie (bug fix)

Fichier Action Description
src/modules/documents/dto/dissemination-response.dto.ts MODIFIE Reordonnancement des classes: DisseminationAttestationResponseDto deplace AVANT DisseminationResponseDto pour corriger un ReferenceError TDZ (Temporal Dead Zone)

3. Couverture des scenarios contractuels

3.1 TC-NOM (nominaux)

TC Description Fichier(s) Statut
TC-NOM-01 SEALED -> DIP mono-document service.spec, controller.spec PASS
TC-NOM-02 SEALED -> DIP multi-document (package) service.spec PASS
TC-NOM-03 DIP -> SEALED retour explicite service.spec, controller.spec PASS
TC-NOM-04 motif_communication persist service.spec, controller.spec PASS
TC-NOM-06 package_id=NULL mono, UUID multi service.spec PASS
TC-NOM-07 returned_at >= disseminated_at (GREATEST) service.spec PASS

3.2 TC-ERR (erreurs)

TC Description Fichier(s) Statut
TC-ERR-01 UUID format invalide (ParseUUIDPipe) controller.spec PASS
TC-ERR-02 Champs readonly rejetes (DTO) controller.spec PASS
TC-ERR-03 401 Unauthorized -> audit DENIED filter.spec PASS
TC-ERR-04 403 Forbidden (role) -> audit DENIED filter.spec PASS
TC-ERR-05 403 RLS -> audit DENIED filter.spec PASS
TC-ERR-06 409 E-409-STATE (PENDING -> DIP) service.spec PASS
TC-ERR-07 409 E-409-STATE (EXPIRED -> DIP) service.spec PASS
TC-ERR-09 409 E-409-STATE (DIP -> DIP self) service.spec PASS
TC-ERR-10 422 E-422-GUARD-COPIES service.spec PASS
TC-ERR-11 409 E-409-STATE (return from non-DIP) service.spec PASS
TC-ERR-12 403 role denial on F2 -> audit filter.spec PASS
TC-ERR-13 429 rate-limit -> audit DENIED guard.spec, filter.spec PASS
TC-ERR-14 409 E-409-RETENTION-DUE service.spec, filter.spec PASS

3.3 TC-INV (invariants)

TC Description Fichier(s) Statut
TC-INV-01 DIP in DocumentStatus enum service.spec PASS
TC-INV-02 Gardes exhaustives (auth, role, rate-limit, etat, copies, retention) service.spec PASS
TC-INV-03 Retour explicite uniquement, pas de timeout service.spec PASS
TC-INV-04 Audit synchrone sur refus (filter) filter.spec PASS
TC-INV-05 1 attestation par requete (mono et multi) service.spec PASS
TC-INV-08 GREATEST(NOW(), disseminated_at) service.spec PASS
TC-INV-11 Package atomique: 1 echec = rejet global service.spec PASS
TC-INV-12 SELECT FOR UPDATE avec ORDER BY ASC service.spec PASS
TC-INV-13 returned_at >= disseminated_at service.spec PASS

3.4 TC-NR (non-regression)

TC Description Fichier(s) Statut
TC-NR-01 DocumentStatus contient les 5 valeurs attendues service.spec PASS
TC-NR-03/04 Audit DISSEMINATED/RETURNED sur succes service.spec PASS

3.5 TC-NEG (negatifs)

TC Description Fichier(s) Statut
TC-NEG-02 RESTITUTED -> DIP interdit service.spec PASS
TC-NEG-06 Pas de mutation des champs immutables (hash, metadata, path) service.spec PASS
TC-NEG-07 Pas de methode PENDING -> SEALED exposee service.spec PASS

3.6 TC-FML (formels)

TC Description Fichier(s) Statut
TC-FML-02 Couverture des 14 invariants INV-278 service.spec PASS

4. Details d'implementation par fichier

4.1 dissemination.service.spec.ts (54 tests)

Pattern: mock QueryRunner avec query.mockImplementation() routant les SQL (SELECT FOR UPDATE, UPDATE, INSERT INTO attestations, INSERT INTO journal).

Helpers: - createDocRow(overrides) — factory pour un document SEALED nominal - createDipDocRow(overrides) — factory pour un document DIP nominal - setupNominalDisseminate(overrides) — setup complet mock pour F1

Couverture specifique: - Transitions interdites: PENDING, EXPIRED, DIP, RESTITUTED -> DIP (4 tests) - Transaction ACID: rollback sur echec attestation, journal, etat invalide (3 tests) - Package atomique: rejet global si 1 document invalide sur N (3 tests) - Temporal: GREATEST dans SQL, rejection si violation d'ordre (2 tests) - Champs immutables: absence de encrypted_metadata/file_hash/ovh_path dans UPDATE (1 test) - Server-only timestamps: NOW() dans SQL, pas de client timestamps (1 test) - Locking: SELECT FOR UPDATE avec ORDER BY ASC pour deadlock prevention (2 tests) - E-404-NOT-FOUND: document inexistant sur disseminate et return (2 tests)

4.2 dissemination.controller.spec.ts (10 tests)

Pattern: Test.createTestingModule avec overrideGuard().useValue({ canActivate: () => true }) pour JwtAuthGuard, AuthorizationGuard, DisseminationRateLimitGuard.

Couverture: - Delegation service: arguments corrects (documents, actorId, motif) - DTO mapping: DisseminationResponseDto.fromResult() et DisseminationReturnResponseDto.fromResult() - Actor extraction: user.sub (JWT claim) extrait via @CurrentUser() - Propagation d'erreurs transparente - Champs extra ignores (whitelist DTO)

4.3 dissemination-rate-limit.guard.spec.ts (15 tests)

Pattern: jest.mock('ioredis') avec mock Redis INCR/EXPIRE/TTL.

Couverture: - Dual counter: per-minute (dip:rate:{actorId}) + daily quota (dip:quota:{actorId}:{date}) - TTL management: EXPIRE uniquement sur premiere requete (TTL=-2) - Fail-closed 503: Redis ECONNREFUSED et Connection timeout - Boundary values: exact limit (pass) vs limit+1 (reject) pour les deux compteurs - Actor identity: absence de sub, sub vide, fallback user.id - Key format: pattern dip:rate: et dip:quota: verifie

4.4 dissemination-audit-exception.filter.spec.ts (20 tests)

Pattern: mock ArgumentsHost + DataSource.createQueryRunner() + AuditLogService.logAsync().

Couverture: - Erreurs auditables: 401, 403-ROLE, 403-RLS, 429, 409-RETENTION-DUE (5 tests) - Erreurs NON auditables: 409-STATE, 409-CONFLICT, 422, 400, 500 (5 tests) - Persistence synchrone: commit AVANT response HTTP (test d'ordonnancement) - Transaction lifecycle: commit sur succes, rollback sur echec audit, release systematique - Document ID extraction: params.id (F2), body.documents[0] (F1), graceful null - Error code extraction: structured error_code, fallback status-based - Non-HttpException: retour 500 sans audit


5. Bug decouvert et corrige

TDZ (Temporal Dead Zone) dans dissemination-response.dto.ts

Symptome: ReferenceError: Cannot access 'DisseminationAttestationResponseDto' before initialization

Cause racine: DisseminationResponseDto (defini en premier) referencait DisseminationAttestationResponseDto (defini apres) dans son annotation de type de propriete. Les annotations TypeScript avec decorateurs @ApiProperty() sont evaluees au moment de la definition de la classe, causant une erreur TDZ.

Correction: Deplacement de DisseminationAttestationResponseDto avant DisseminationResponseDto dans le fichier. Le fichier contenait deja un commentaire NOTE: Defined before ... to avoid TDZ mais les classes etaient dans le mauvais ordre.


6. Resultat final

Test Suites: 4 passed, 4 total
Tests:       99 passed, 99 total
Snapshots:   0 total
Time:        ~1s

Tous les tests passent. Aucun test en echec, aucun test saute.


7. Decisions techniques

Decision Justification
jest.mock('ioredis') au niveau module Isolation Redis sans connexion reelle — test unitaire, pas integration
query.mockImplementation() avec routage SQL Permet de verifier le contenu SQL exact (GREATEST, FOR UPDATE, ORDER BY)
expect.objectContaining() pour audit assertions Verifie les champs critiques sans fragiliser sur les champs annexes
expect.any(Array) au lieu de expect.anything() pour args nullable Jest 29.7: expect.anything() ne match pas null ni undefined
Pas de jest.useFakeTimers() Les timestamps sont generes cote SQL (NOW()), pas cote applicatif
Helper factories avec overrides Pattern spread operator pour eviter la mutation de fixtures partagees (REX PD-237)