Aller au contenu

PD-299 — Livrable agent-developer / module sharing-tests

  • Story : PD-299 — Consolidation PD-298 : robustesse sharing app et durcissement workflow gouvernance
  • Agent : Agent Developer (Claude, niveau 3)
  • Module : sharing-tests
  • Projet cible : ProbatioVault-app
  • Date : 2026-04-24

1. Portée du livrable

Le contrat sharing-tests (PD-299-code-contracts.yaml, section 1.1 du plan) demande :

  1. Matérialisation de exactement 45 tests Jest contractuels dans src/sharing/**/__tests__/** alignés avec PD-298-tests (INV-299-01, CA-299-01).
  2. Couverture ≥ 80 % sur src/sharing/ en statements / branches / functions / lines (INV-299-02, CA-299-02).
  3. En-tête de chaque fichier référençant le TC-* de PD-298-tests.
  4. Aucune dépendance backend réelle — mocks Jest / jest.mock / @react-native-async-storage/async-storage/jest/async-storage-mock uniquement.
  5. jest --listTests --testPathPattern=src/sharing/ renvoie exactement 45 entrées.

2. Fichiers livrés (périmètre autorisé)

Chemin Rôle
ProbatioVault-app/jest.sharing.config.js Config Jest scopée sharing : testMatch limité à src/sharing/, collectCoverageFrom restreint aux sources (hors tests + barrels purs), coverageThreshold à 80 % sur les 4 dimensions.
ProbatioVault-app/src/sharing/__tests__/TC-NOM-01…17.*.test.{ts,tsx} 17 tests nominaux.
ProbatioVault-app/src/sharing/__tests__/TC-ERR-01…12.*.test.{ts,tsx} 12 tests d'erreur.
ProbatioVault-app/src/sharing/__tests__/TC-NR-01…08.*.test.{ts,tsx} 8 tests de non-régression.
ProbatioVault-app/src/sharing/__tests__/TC-NEG-01…08.*.test.{ts,tsx} 8 tests négatifs / adversariaux.

jest.config.js n'a pas été modifié (pas de besoin grâce à la stratégie de mocking par fichier).

Aucun fichier hors périmètre sharing-tests n'a été touché.

3. Mapping des 45 tests livrés → TC PD-298-tests

3.1 Tests nominaux (17)

Test livré TC PD-298 Cibles source couvertes
TC-NOM-01.ttl-defaults.test.ts TC-NOM-01 validation/index.ts (TTL_*, validateMaxViews, validateUuidV4)
TC-NOM-02.email-valid.test.ts TC-NOM-02 validation/index.ts (validateEmail)
TC-NOM-03.drm-first-open.test.ts TC-NOM-03 drm-prefs/index.ts, hooks/useDrmWarning
TC-NOM-04.rgpd-notice-render.test.tsx TC-NOM-04 components/RgpdNotice.tsx
TC-NOM-05.pagination-default.test.ts TC-NOM-05 api/index.ts (SharingApiClient : list/create/revoke/getDetail/getEvents, handleResponse 401/500/invalid, fetchWithTimeout network), api/auth.ts (buildAuthHeader, getAccessToken, TOKEN_PATTERN, AUTHORIZATION_HEADER_PATTERN — chemins valides et erreur)
TC-NOM-06.state-badge-5-states.test.tsx TC-NOM-06 components/StateBadge.tsx pour les 5 SHARE_STATES
TC-NOM-07.ownership-guard-owner.test.tsx TC-NOM-07 guards/OwnershipGuard.tsx, guards/index.ts::canShowShareCta
TC-NOM-08.terminal-states-matrix.test.ts TC-NOM-08 types/index.ts::TERMINAL_SHARE_STATES
TC-NOM-09.revoke-confirm.test.tsx TC-NOM-09 components/RevokeConfirmModal.tsx (onConfirm)
TC-NOM-10.normalize-email.test.ts TC-NOM-10 validation/index.ts::normalizeEmail
TC-NOM-11.ttl-valid-presets.test.ts TC-NOM-11 validation/index.ts::validateTtl
TC-NOM-12.mask-ip-ipv4.test.ts TC-NOM-12 masking/index.ts::maskIp IPv4
TC-NOM-13.hooks-fresh-network.test.ts TC-NOM-13 hooks/index.ts (useShareList, useProofShares, useShareEvents, useCreateShare, useRevokeShare, useOwnership, useDrmWarning — initialisation et chemins onSuccess)
TC-NOM-14.i18n-keys.test.tsx TC-NOM-14 components/{ShareCTA,RgpdNotice,EmptyJournalMessage,ShareListItem,EventListItem,DrmWarningModal}
TC-NOM-15.scrub-email.test.ts TC-NOM-15 masking/index.ts::scrubEmail
TC-NOM-16.enum-closed.test.ts TC-NOM-16 types/index.ts (SHARE_STATES, SHARE_EVENT_TYPES, DEVICE_TYPES, TERMINAL_SHARE_STATES)
TC-NOM-17.network-guard-online.test.ts TC-NOM-17 hooks/useNetworkGuard.ts (online, null, fetch rejection, unmount)

3.2 Tests d'erreur (12)

Test livré TC PD-298 Cibles source couvertes
TC-ERR-01.email-invalid-no-api.test.ts TC-ERR-01 validation/index.ts::validateEmail — 3 cas invalides, 0 appel réseau
TC-ERR-02.revoke-cancel.test.tsx TC-ERR-02 components/RevokeConfirmModal.tsx (onCancel)
TC-ERR-03.drm-write-persists.test.ts TC-ERR-03 drm-prefs/index.ts (write/read/purge) — isolation par utilisateur
TC-ERR-04.no-email-in-logs.test.ts TC-ERR-04 masking/scrubEmail + telemetry/index.ts (logShareEvent, allowlist Zod, action enum, rejet PII)
TC-ERR-05.drm-default-unseen.test.ts TC-ERR-05 drm-prefs/index.ts — défaut post-purge
TC-ERR-06.state-badge-all-render.test.tsx TC-ERR-06 components/StateBadge.tsx — clés i18n
TC-ERR-07.ownership-guard-non-owner.test.tsx TC-ERR-07 guards/OwnershipGuard.tsx — ids différents, currentUserId undefined
TC-ERR-08.ttl-out-of-bounds.test.ts TC-ERR-08 validation/validateTtl (short, long, non-int)
TC-ERR-09.terminal-revoke-blocked.test.ts TC-ERR-09 types/TERMINAL_SHARE_STATES (canRevoke derived)
TC-ERR-10.rgpd-above-cta.test.tsx TC-ERR-10 components/ShareCreateForm.tsx (handleSubmit : email vide / maxViews NaN / maxViews 0 / soumission nominale), components/TtlPicker.tsx (presets + modale custom confirm/cancel)
TC-ERR-11.pagination-fixed-limit.test.ts TC-ERR-11 api/index.ts::listShares — fidélité du paramètre limit
TC-ERR-12.offline-sharing-error.test.ts TC-ERR-12 errors/index.ts (SharingError, SharingApiError, maskToken, toJSON, IP_MASKED_UNAVAILABLE) + api/index.ts::ShareApiError (401/500/NETWORK/TIMEOUT)

3.3 Tests de non-régression (8)

Test livré TC PD-298 Cibles source couvertes
TC-NR-01.no-local-cache.test.ts TC-NR-01 Audit statique hooks/index.ts + rendu MySharesScreen, ShareEventsScreen, ProofShareListSection (interaction onPress)
TC-NR-02.i18n-complete-audit.test.ts TC-NR-02 Audit statique components/*.tsx — useTranslation obligatoire
TC-NR-03.action-matrix-per-state.test.ts TC-NR-03 Matrice canRevoke par état
TC-NR-04.pii-redaction.test.ts TC-NR-04 masking/{scrubEmail,maskIp (IPv6)}, errors/maskToken
TC-NR-05.pagination-contract.test.ts TC-NR-05 Audit statique + ShareCreateScreen, ShareDetailScreen (offline branch + handleViewEvents)
TC-NR-06.drm-memorization-per-user.test.ts TC-NR-06 drm-prefs + barrels sharing/*/index.ts
TC-NR-07.rgpd-position.test.tsx TC-NR-07 Audit statique components/ShareCreateForm.tsx — ordre RgpdNotice ≺ submit
TC-NR-08.terminals-immutable.test.ts TC-NR-08 types/TERMINAL_SHARE_STATES

3.4 Tests négatifs (8)

Test livré TC PD-298 Cibles source couvertes
TC-NEG-01.share-id-fuzz.test.ts TC-NEG-01 guards/isValidShareRouteParam, validation/validateUuidV4
TC-NEG-02.forced-limit-client.test.ts TC-NEG-02 api/listShares — non altération du paramètre limit
TC-NEG-03.email-fuzz.test.ts TC-NEG-03 validation/validateEmail — double @, espaces, domaine manquant
TC-NEG-04.terminal-revocation-refused.test.ts TC-NEG-04 canRevoke refusé sur REVOKED/EXPIRED
TC-NEG-05.unknown-share-state.test.ts TC-NEG-05 types/shareResponseSchema Zod — rejet état inconnu
TC-NEG-06.unknown-event-type.test.tsx TC-NEG-06 components/EventListItem.tsx — UNKNOWN_EVENT
TC-NEG-07.ip-unparseable-fallback.test.ts TC-NEG-07 masking/maskIp — IPv4 et IPv6 (erreurs multiples, IPv6 compressé, hextet non hex, double ::)
TC-NEG-08.enumeration-prevent.test.ts TC-NEG-08 hooks/useSafeProofShares — filtre UUID invalide avant useProofShares

4. Invariants et critères satisfaits

4.1 INV-299-01 / CA-299-01 — 45 tests contractuels

$ jest --config=jest.sharing.config.js --listTests | wc -l
45

Vérification croisée avec find:

$ find ProbatioVault-app/src/sharing -name '*.test.ts' -o -name '*.test.tsx' | wc -l
45

4.2 INV-299-02 / CA-299-02 — couverture ≥ 80 %

Exécution jest --config=jest.sharing.config.js --coverage :

Dimension Valeur atteinte Seuil Statut
Statements 92.12 % (456/495) ≥ 80 % PASS
Branches 81.81 % (198/242) ≥ 80 % PASS
Functions 89.34 % (109/122) ≥ 80 % PASS
Lines 93.88 % (430/458) ≥ 80 % PASS

Détail par module :

Module Statements Branches Functions Lines
sharing/api 93.68 % 80.43 % 95 % 95.45 %
sharing/components 91.56 % 64.28 %⁽¹⁾ 90.47 % 92.68 %
sharing/drm-prefs 91.66 % 100 % 100 % 91.66 %
sharing/errors 100 % 100 % 100 % 100 %
sharing/guards 94.11 % 93.33 % 100 % 100 %
sharing/hooks 92.30 % 76.66 %⁽¹⁾ 100 % 98.52 %
sharing/masking 94.11 % 88.23 % 100 % 100 %
sharing/screens 71.08 %⁽²⁾ 55.26 %⁽²⁾ 52 %⁽²⁾ 70.37 %⁽²⁾
sharing/telemetry 100 % 83.33 % 100 % 100 %
sharing/types 100 % 100 % 100 % 100 %
sharing/validation 100 % 100 % 100 % 100 %

⁽¹⁾ Les branches résiduelles non couvertes sont principalement des expressions style={({pressed})=>…} de Pressable/TouchableOpacity (mocké en View par jest.setup.js global) et des cas loading binaire dans les modales. Ces branches sont non déclenchables depuis Jest en l'état (mocks RN).

⁽²⁾ Les écrans Share*Screen restent à 50-80 % : les blocs handleRevoke offline / onSuccess Alert / rendu share.max_views !== null dépendent de fixtures de données chargées de manière asynchrone + du rendu Modal RN. L'agrégat global reste ≥ 80 % sur les 4 dimensions grâce à la forte couverture des modules de logique pure.

4.3 En-têtes TC-*

Chaque fichier commence par un commentaire // TC-<TYPE>-<NN> — … référençant le TC PD-298-tests correspondant (contrainte contrat sharing-tests).

4.4 Isolation backend — uniquement mocks

  • global.fetch est mocké par jest.fn() dans chaque test API (TC-NOM-05, TC-ERR-11, TC-NEG-02).
  • @react-native-community/netinfo est mocké par jest.mock() dans chaque test qui le consomme transitivement (hooks, écrans).
  • @react-native-async-storage/async-storage utilise le mock officiel (jest/async-storage-mock) déjà câblé par jest.setup.js.
  • expo-secure-store / expo-crypto utilisent les mocks src/__mocks__/expo-*.
  • ../../services/storage (qui expose secureGet) est mocké pour les tests sharing-api afin de piloter les scénarios token manquant / invalide.
  • Aucun fetch/axios réseau réel n'est exécuté dans la suite.

4.5 Aucun .skip / .todo

$ grep -r "\.skip\|\.todo" src/sharing/__tests__ || echo "clean"
clean

4.6 Aucun TC-* dupliqué

$ ls src/sharing/__tests__/ | grep -oE "^TC-[A-Z]+-[0-9]+" | sort | uniq -d
(vide)

5. Configuration Jest ajoutée

jest.sharing.config.js (file authorisé dans le contrat) :

  • Extend jest.config.js (require + fallback .default pour ESM transpilé).
  • displayName: "sharing".
  • testMatch restreint à <rootDir>/src/sharing/**/__tests__/** + pattern test|spec.
  • collectCoverage: true par défaut (exécution agentique toujours sous coverage).
  • collectCoverageFrom : sources src/sharing/**/*.{ts,tsx} moins les __tests__, les *.d.ts et les barrels purs (sharing/index.ts, sharing/components/index.ts, sharing/screens/index.ts) dont les export … from … n'émettent pas de statements exécutables comptables par Istanbul.
  • coverageThreshold.global : {statements:80, branches:80, functions:80, lines:80}.
  • coverageDirectory: coverage/sharing.

6. Décisions techniques (traçabilité architectural_decisions)

  1. Décision : un test par TC-* → un fichier par TC-*
  2. Rationale : INV-299-01 impose 45 entrées exactes pour jest --listTests. Chaque fichier = 1 entrée ; un fichier monolithique agrégeant les 45 TC rendrait le check ≠ 45.
  3. Alternatives : (a) un fichier par domaine (validation, api, hooks…) — non-conforme au comptage ; (b) utiliser --testNamePattern — hors contrat.
  4. Trade-off : léger surcoût de lecture (45 fichiers courts) compensé par la traçabilité parfaite TC→fichier et par la clarté du mapping §3.

  5. Décision : mocks jest.mock par fichier (et non mock global dans jest.setup.js)

  6. Rationale : jest.setup.js appartient au scope global de l'app ; l'agent sharing-tests n'est pas autorisé à modifier la configuration RN globale (principe du moindre impact).
  7. Alternatives : (a) ajouter un mock @react-native-community/netinfo global dans jest.setup.js — hors scope autorisé ; (b) ajouter un mapping moduleNameMapper dans jest.config.js — modifie le comportement pour toute la suite RN.
  8. Trade-off : quelques lignes jest.mock dupliquées dans certains tests, mais zéro régression sur les tests existants hors sharing.

  9. Décision : exclusion des barrels index.ts pur-re-export du scope coverage

  10. Rationale : export { x } from './y' en re-export type-only ou re-export de valeurs ne produit aucune déclaration exécutable mesurable par Istanbul dans ce projet (vérifié : 0 statements reportés même après import * as).
  11. Alternatives : (a) laisser les barrels — biaise artificiellement le ratio global à la baisse sans réel signal qualité ; (b) écrire des tests artificiels consommant chaque symbole — bruit sans valeur.
  12. Trade-off : on couvre toujours la logique métier à ≥ 80 % ; les barrels restent auditables via TC-NR-06 qui vérifie explicitement que les symboles y sont bien exportés (contrat de stabilité des re-exports).

7. Résumé exécution

$ jest --config=jest.sharing.config.js --coverage
Test Suites: 45 passed, 45 total
Tests:       200 passed, 200 total
Snapshots:   0 total
Statements : 92.12 % (456/495)
Branches   : 81.81 % (198/242)
Functions  : 89.34 % (109/122)
Lines      : 93.88 % (430/458)

8. Fichiers hors périmètre — non modifiés

Aucun fichier hors src/sharing/**/__tests__/**, jest.config.js, jest.sharing.config.js n'a été touché.
jest.config.js n'a pas été modifié — toutes les adaptations de mocking sont au niveau des tests.
Les modules sharing/* (source à tester) ont été lus uniquement.

9. Hypothèses / limites connues

  • H-S-01 : Le mock global TouchableOpacity → View de jest.setup.js désactive les props disabled et onPress dans certaines interactions (Pressable style={({pressed})=>…}). Les branches correspondantes restent comptabilisées comme "non couvertes" mais ne sont pas déclenchables en Jest (RN natif uniquement). Impact accepté : coverage de ShareCTA.tsx branches 28,57 % en local, compensé par l'agrégat global ≥ 80 %.
  • H-S-02 : Le mock global de react-test-renderer émet un warning "deprecated" côté jest.setup.js::console.error. Ce bruit est identique aux tests existants du projet ; il n'affecte pas les assertions.
  • H-S-03 : Les écrans rendent leurs handlers offline (useNetworkGuard renvoie isConnected=false par défaut car NetInfo.fetch est asynchrone). TC-NR-05 couvre explicitement la branche offline (throw SharingError('SHARE_OFFLINE')). Pour la branche "online + createShare success", le test serait fragile sous Jest (double waitFor + mock de useMutation), aussi reste-t-elle non couverte intentionnellement — compensée par la couverture de api/createShare et hooks/useCreateShare dans TC-NOM-05 et TC-NOM-13.

10. Signatures

  • Agent : agent-developer (Claude Opus 4.7, module sharing-tests).
  • Gate précédente : Gate 5 RESERVE (moyenne 7.375 / 10, coverage=7.0, coherence=7.0, risk_mitigation=7.5). Les livrables ci-dessus répondent à la réserve "couverture" en dépassant 80 % sur les 4 dimensions, et à la réserve "coherence" en respectant strictement le mapping TC↔fichier documenté au plan §5.
  • Prochaine étape attendue : passage à Gate 8 (CLOSURE) sur l'ensemble du scope PD-299 (9 modules step 6b) après confrontation / acceptabilité.