Aller au contenu

PD-40 — Tests contractuels (Rotation de clés HSM)

User Story : PD-40 — Rotation de clés HSM et re-signature probatoire Epic : PD-189 — CRYPTO Date : 2026-01-01 Statut : CONTRACTUEL (référence minimale obligatoire)


1. Objet et portée

Ce document définit le socle minimal contractuel de tests obligatoires pour PD-40.

Règles normatives :

  • Tous les tests TC-* ci-dessous doivent être implémentés (ou explicitement justifiés si impossibles).
  • Chaque test automatisé contractuel doit porter l'identifiant TC-* dans le nom du test ou un commentaire/annotation.
  • Les tests additionnels (unitaires, intégration, non-régression) sont autorisés et encouragés tant qu'ils n'étendent ni ne contredisent la spec.

2. Tests des invariants (INV-*)

TC-INV-01 : Clés privées ne sortent jamais du HSM

Invariant : INV-1 (Spec §7)

Objectif : Vérifier que les clés privées sont non-exportables.

Préconditions : - HSM configuré et accessible - Aucune clé existante

Étapes : 1. Générer une nouvelle clé Ed25519 via HsmKeyManager.generateKeyPair() 2. Récupérer les métadonnées de la clé (keyId, algorithm, status) 3. Tenter d'exporter la clé privée via API HSM

Résultat attendu : - La tentative d'export de clé privée échoue avec erreur KEY_NOT_EXPORTABLE ou équivalent - Les métadonnées (keyId, algorithm, createdAt, status) sont exportables - La clé publique est exportable

Criticité : BLOQUANTE


TC-INV-02 : Signatures produites exclusivement par HSM

Invariant : INV-2 (Spec §7)

Objectif : Vérifier que toute signature passe par le HSM.

Préconditions : - Clé ACTIVE existante dans HSM

Étapes : 1. Construire un payload de test canonique (JSON RFC 8785) 2. Appeler HsmKeyManager.sign(keyId, payload) 3. Vérifier que la signature est produite 4. Vérifier via logs/traces que l'appel HSM a été effectué

Résultat attendu : - Signature retournée au format base64 - Log HSM montre l'opération de signature - Aucune signature n'est générée en software (vérification par mock/spy)

Criticité : BLOQUANTE


TC-INV-03 : Unicité stricte de la clé active

Invariant : INV-3 (Spec §7)

Objectif : Vérifier qu'à tout instant, il existe exactement UNE clé avec status=ACTIVE.

Préconditions : - Base de données vide

Étapes : 1. Insérer une clé K1 avec status=ACTIVE 2. Tenter d'insérer une deuxième clé K2 avec status=ACTIVE 3. Vérifier la contrainte DB

Résultat attendu : - Insertion de K2 échoue avec erreur de contrainte UNIQUE - SELECT COUNT(*) FROM keys WHERE status='ACTIVE' retourne exactement 1

Criticité : BLOQUANTE


TC-INV-04 : Clé archivée ne peut produire signatures ACTIVE

Invariant : INV-4 (Spec §7)

Objectif : Vérifier qu'une clé ARCHIVED/CANDIDATE/DISCARDED ne peut jamais signer.

Préconditions : - Clé K1 avec status=ARCHIVED dans la DB

Étapes : 1. Appeler HsmKeyManager.sign(K1.keyId, payload) 2. Observer l'exception

Résultat attendu : - Exception KEY_NOT_ACTIVE_ERROR ou équivalent - Message : "Key status must be ACTIVE to sign" (ou similaire) - Aucune signature produite

Criticité : BLOQUANTE


TC-INV-05 : Modèle append-only (événements et signatures)

Invariant : INV-5 (Spec §7)

Objectif : Vérifier que les signatures ne sont jamais supprimées ni modifiées.

Préconditions : - Événement E1 avec 2 signatures existantes

Étapes : 1. Appeler EventRepository.appendSignature(E1.event_id, newSignature) 2. Recharger l'événement E1 3. Vérifier que les 2 signatures précédentes sont intactes 4. Vérifier que la nouvelle signature est ajoutée en fin de tableau

Résultat attendu : - E1.signatures.length === 3 - Les 2 premières signatures ont les mêmes valeurs (keyId, signature, signed_at inchangés) - La 3ème signature est la nouvelle

Criticité : BLOQUANTE


TC-INV-06 : Signature valide ssi state=ACTIVE

Invariant : INV-6 (Spec §7)

Objectif : Vérifier que les signatures CANDIDATE sont ignorées lors de la vérification.

Préconditions : - Événement E1 avec : - 1 signature S1 (state=ACTIVE, keyId=K1) - 1 signature S2 (state=CANDIDATE, keyId=K2)

Étapes : 1. Récupérer les signatures actives de E1 : E1.signatures.filter(s => s.state === 'ACTIVE') 2. Compter les signatures valides

Résultat attendu : - Exactement 1 signature active (S1) - S2 est présente dans le tableau mais ignorée (state=CANDIDATE)

Criticité : BLOQUANTE


TC-INV-07 : Toute signature ACTIVE reste vérifiable

Invariant : INV-7 (Spec §7)

Objectif : Vérifier que les signatures ACTIVE peuvent être vérifiées même après rotation (clé archivée).

Préconditions : - Événement E1 signé avec clé K1 (state=ACTIVE) - Rotation effectuée : K1 → ARCHIVED, K2 → ACTIVE - Événement E1 a maintenant 2 signatures : S1 (K1, ACTIVE) et S2 (K2, ACTIVE)

Étapes : 1. Exporter la clé publique de K1 (archivée) 2. Récupérer la signature S1 de E1 3. Vérifier la signature S1 avec la clé publique K1

Résultat attendu : - Clé publique K1 est accessible malgré status=ARCHIVED - Vérification cryptographique de S1 réussit - Signature S1 est toujours marquée state=ACTIVE

Criticité : BLOQUANTE


TC-INV-08 : Atomicité par promotion d'état

Invariant : INV-8 (Spec §7)

Objectif : Vérifier que la promotion est tout-ou-rien.

Préconditions : - 100 événements re-signés avec signatures CANDIDATE (rotation R1)

Étapes : 1. Simuler échec DB au milieu de la transaction de promotion (après 50 UPDATE) 2. Vérifier rollback automatique 3. Compter les signatures promues

Résultat attendu : - Transaction échoue avec exception - Rollback automatique : TOUTES les signatures restent CANDIDATE (aucune promue à ACTIVE) - Ancienne clé reste ACTIVE, nouvelle clé reste CANDIDATE

Criticité : BLOQUANTE


TC-INV-09 : Sélection E déterministe, bornée, testable

Invariant : INV-9 (Spec §7)

Objectif : Vérifier que l'ensemble E est déterministe et reproductible.

Préconditions : - 150 événements en base avec timestamps variés - rotation_start_time = T0 = 2026-01-15T12:00:00Z

Étapes : 1. Appeler EventSelector.selectEligibleEvents(T0) → E1 2. Appeler à nouveau EventSelector.selectEligibleEvents(T0) → E2 3. Comparer E1 et E2 (mêmes event_id, même ordre)

Résultat attendu : - E1 et E2 sont identiques (mêmes événements, même ordre) - Tous les événements sont dans la fenêtre ]T0 - 24h ; T0[ - Tous ont type ∈ {CREATE, UPDATE_METADATA, ACCESS_LOG, PRE_DELEGATION, REKEY} - Tous ont status=FINALIZED - Ordre : (timestamp ASC, event_id ASC)

Criticité : BLOQUANTE


TC-INV-10 : Rotation authentifiée, autorisée, journalisée, exportable

Invariant : INV-10 (Spec §7)

Objectif : Vérifier que toute rotation est tracée dans un journal immuable exportable.

Préconditions : - Rotation R1 effectuée (succès ou échec)

Étapes : 1. Rechercher le log de rotation dans rotation_audit_logs WHERE rotation_id=R1 2. Vérifier la présence de tous les champs obligatoires (§10.1 spec) 3. Tenter de modifier le log (UPDATE) 4. Tenter de supprimer le log (DELETE) 5. Exporter les logs en format NDJSON et JSON_CANONICAL

Résultat attendu : - Log existe avec tous les champs : rotation_id, rotation_start_time, rotation_end_time, trigger, initiator, old_keyId, new_keyId, algorithm, eligible_count, event_ids, final_status - UPDATE échoue avec exception "Audit logs are immutable (PD-40 §10.3)" - DELETE échoue avec exception "Audit logs are immutable (PD-40 §10.3)" - Export NDJSON : une ligne JSON par log - Export JSON_CANONICAL : canonicalisation RFC 8785 (clés triées, déterministe)

Criticité : BLOQUANTE


3. Tests des critères d'acceptation (CA*)

TC-CA1 : Unicité de la clé active à tout instant

Critère : CA1 (Spec §11)

Objectif : À tout instant, il existe exactement UNE clé pouvant produire des signatures ACTIVE.

Préconditions : - Rotation complète effectuée (K1 → K2)

Étapes : 1. Vérifier SELECT COUNT(*) FROM keys WHERE status='ACTIVE' 2. Vérifier que K2 a status=ACTIVE 3. Vérifier que K1 a status=ARCHIVED

Résultat attendu : - Exactement 1 clé active (K2) - K1 est archivée

Criticité : BLOQUANTE


TC-CA2 : Événement signé avant rotation reste vérifiable

Critère : CA2 (Spec §11)

Objectif : Les signatures pré-rotation restent valides.

Préconditions : - Événement E1 signé avec K1 avant rotation - Rotation effectuée : K1 → K2

Étapes : 1. Vérifier que E1 a une signature S1 (keyId=K1, state=ACTIVE) 2. Exporter clé publique K1 3. Vérifier cryptographiquement S1

Résultat attendu : - S1 reste state=ACTIVE après rotation - Vérification cryptographique réussit - K1 est accessible (métadonnées) malgré status=ARCHIVED

Criticité : BLOQUANTE


TC-CA3 : Clé archivée ne peut produire signatures ACTIVE

Critère : CA3 (Spec §11)

Objectif : Une clé archivée est inutilisable pour signature.

Préconditions : - K1 archivée après rotation

Étapes : 1. Appeler HsmKeyManager.sign(K1.keyId, payload)

Résultat attendu : - Exception KEY_NOT_ACTIVE_ERROR - Aucune signature produite

Criticité : BLOQUANTE


TC-CA4 : Chaque signature expose keyId et algorithm=Ed25519

Critère : CA4 (Spec §11)

Objectif : Métadonnées de signature complètes.

Préconditions : - Événement E1 signé

Étapes : 1. Récupérer E1.signatures[0] 2. Vérifier présence et valeurs des champs

Résultat attendu : - signature.keyId existe et est un UUID valide - signature.algorithm === 'Ed25519' - signature.signature est une chaîne base64 - signature.signed_at est un ISO-8601 - signature.rotation_id est un UUID (si rotation) - signature.state ∈ {CANDIDATE, ACTIVE}

Criticité : BLOQUANTE


TC-CA5 : Après succès, tous événements E ont signature ACTIVE supplémentaire

Critère : CA5 (Spec §11)

Objectif : Vérifier couverture complète de E.

Préconditions : - 1000 événements éligibles (E) - Rotation R1 réussie (K1 → K2)

Étapes : 1. Pour chaque événement dans E : - Compter les signatures avec rotation_id=R1 et state=ACTIVE - Vérifier keyId=K2

Résultat attendu : - Chaque événement de E a exactement 1 signature ACTIVE supplémentaire (nouvelle signature) - Toutes ces signatures ont keyId=K2, rotation_id=R1, state=ACTIVE - Les signatures pré-existantes (K1, ACTIVE) sont intactes

Criticité : BLOQUANTE


TC-CA6 : En cas d'échec, aucune signature ACTIVE supplémentaire

Critère : CA6 (Spec §11)

Objectif : Vérifier absence de promotion en cas d'échec.

Préconditions : - 100 événements éligibles - Simulation d'échec HSM après 50 signatures

Étapes : 1. Démarrer rotation R1 2. Simuler échec HSM après 50 signatures 3. Vérifier que ResigningService retourne success=false 4. Compter les signatures ACTIVE ajoutées (rotation_id=R1, state=ACTIVE)

Résultat attendu : - 0 signatures ACTIVE ajoutées (aucune promotion) - 50 signatures CANDIDATE existent (ignorées) - Ancienne clé K1 reste ACTIVE - Nouvelle clé K2 a status=DISCARDED ou CANDIDATE - Log d'audit : final_status=ROTATION_FAILED avec failure_reason

Criticité : BLOQUANTE


TC-CA7 : Journal d'audit complet, durable, exportable

Critère : CA7 (Spec §11)

Objectif : Conformité audit (§10 spec).

Préconditions : - Rotation R1 effectuée (succès) - Rotation R2 effectuée (échec)

Étapes : 1. Exporter logs entre date_debut et date_fin en NDJSON 2. Exporter logs en JSON_CANONICAL 3. Vérifier immutabilité (test UPDATE/DELETE) 4. Vérifier contenu des logs R1 et R2

Résultat attendu : - Export NDJSON : 2 lignes (R1, R2), chacune un JSON valide - Export JSON_CANONICAL : 2 lignes, canonicalisation RFC 8785 (clés triées) - UPDATE/DELETE échouent avec exception trigger - Logs contiennent tous les champs obligatoires (§10.1 spec) - R1 : final_status=SUCCESS - R2 : final_status=ROTATION_FAILED avec failure_reason renseigné

Criticité : BLOQUANTE


4. Tests des flux nominaux (N*)

TC-N1 : Déclenchement de rotation

Flux : N1 (Spec §8)

Objectif : Vérifier initialisation de session de rotation.

Préconditions : - Acteur autorisé (role=ADMIN) - Clé K1 active

Étapes : 1. Appeler RotationOrchestrator.initiateRotation(trigger=MANUAL, initiator=USER_123) 2. Vérifier création de RotationSession

Résultat attendu : - RotationSession créée avec : - rotation_id (UUID v4) - rotation_start_time ≈ now() (±5s) - trigger=MANUAL - initiator.userId=USER_123 - status=INITIATED - oldKeyId=K1

Criticité : BLOQUANTE


TC-N2 : Génération de clé

Flux : N2 (Spec §8)

Objectif : Vérifier génération HSM et création métadonnées.

Préconditions : - RotationSession R1 initiée

Étapes : 1. Appeler HsmKeyManager.generateKeyPair('Ed25519') 2. Vérifier persistence dans KeyRepository

Résultat attendu : - KeyMetadata créée avec : - keyId (UUID) - algorithm=Ed25519 - createdAt ≈ now() - status=CANDIDATE - rotationId=R1 - Session mise à jour : newKeyId=K2

Criticité : BLOQUANTE


TC-N3 : Construction ensemble E

Flux : N3 (Spec §8)

Objectif : Vérifier sélection déterministe.

Préconditions : - 200 événements en base avec types et statuts variés - rotation_start_time=T0

Étapes : 1. Appeler EventSelector.selectEligibleEvents(T0) 2. Vérifier taille et contenu de E

Résultat attendu : - E contient uniquement événements avec : - timestamp ∈ ]T0 - 24h ; T0[ - type ∈ {CREATE, UPDATE_METADATA, ACCESS_LOG, PRE_DELEGATION, REKEY} - status=FINALIZED - Ordre : (timestamp ASC, event_id ASC) - |E| ≤ 100 000

Criticité : BLOQUANTE


TC-N4 : Re-signature phase CANDIDATE

Flux : N4 (Spec §8)

Objectif : Vérifier signature en lot avec state=CANDIDATE.

Préconditions : - Ensemble E de 50 événements - Clé K2 (CANDIDATE) - rotation_id=R1

Étapes : 1. Appeler ResigningService.resignEvents(E, K2, R1) 2. Vérifier succès et état signatures

Résultat attendu : - ResigningResult.success=true - ResigningResult.processedCount=50 - Chaque événement de E a une nouvelle signature avec : - keyId=K2 - algorithm=Ed25519 - signature (base64, longueur 86-88 caractères) - signed_at ≈ now() - rotation_id=R1 - state=CANDIDATE - Aucune signature promue à ACTIVE

Criticité : BLOQUANTE


TC-N5 : Promotion atomique

Flux : N5 (Spec §8)

Objectif : Vérifier promotion tout-ou-rien.

Préconditions : - 50 signatures CANDIDATE (rotation R1) - eligible_count=50

Étapes : 1. Appeler PromotionService.promoteSignatures(R1) 2. Vérifier état final

Résultat attendu : - Transaction réussit - Toutes les 50 signatures ont state=ACTIVE - Clé K2 a status=ACTIVE - Clé K1 a status=ARCHIVED, archivedAt ≈ now() - Log d'audit créé avec final_status=SUCCESS

Criticité : BLOQUANTE


TC-N6 : Échec et rollback

Flux : N6 (Spec §8)

Objectif : Vérifier gestion d'échec sans promotion.

Préconditions : - 100 événements éligibles - Simulation échec HSM après 30 signatures

Étapes : 1. Démarrer rotation R1 2. Simuler échec HSM (timeout/erreur) 3. Vérifier état final

Résultat attendu : - ResigningResult.success=false - 30 signatures CANDIDATE existent (ignorées, jamais promues) - 0 signatures ACTIVE ajoutées - Clé K1 reste ACTIVE - Clé K2 a status=DISCARDED - Log d'audit : final_status=ROTATION_FAILED, failure_reason="HSM timeout" (ou similaire)

Criticité : BLOQUANTE


5. Tests des cas d'erreur (E*)

TC-E4 : Échec partiel de re-signature

Cas d'erreur : E4 (Spec §9)

Objectif : Vérifier cohérence append-only après échec.

Préconditions : - 1000 événements éligibles - Échec HSM après 500 signatures

Étapes : 1. Démarrer rotation 2. Simuler échec HSM au milieu 3. Vérifier état DB

Résultat attendu : - 500 signatures CANDIDATE existent - Aucune promotion d'état (state reste CANDIDATE) - Aucune signature ACTIVE ajoutée - État cohérent append-only (pas de suppression des 500 signatures CANDIDATE) - Ancienne clé reste ACTIVE

Criticité : BLOQUANTE


TC-ERR-HSM-UNREACHABLE : HSM indisponible

Objectif : Vérifier gestion timeout HSM.

Préconditions : - HSM simulé indisponible (timeout 30s)

Étapes : 1. Appeler HsmKeyManager.generateKeyPair()

Résultat attendu : - Exception HSM_UNREACHABLE après timeout - Rotation échoue avant re-signature - Log d'audit : final_status=ROTATION_FAILED, failure_reason="HSM unreachable"

Criticité : MOYENNE


TC-ERR-ELIGIBLE-SET-TOO-LARGE : Dépassement N_max

Objectif : Vérifier rejet si |E| > 100 000.

Préconditions : - 150 000 événements éligibles dans fenêtre 24h

Étapes : 1. Appeler EventSelector.selectEligibleEvents(T0)

Résultat attendu : - Exception ELIGIBLE_SET_TOO_LARGE avec message "Eligible set size 150000 exceeds N_max=100000" - Rotation échoue avant re-signature - Recommandation dans logs : fenêtre plus courte ou rotation intermédiaire

Criticité : MOYENNE


TC-ERR-UNAUTHORIZED-TRIGGER : Déclenchement non autorisé

Objectif : Vérifier contrôle d'accès.

Préconditions : - Utilisateur USER_456 avec role=USER (non-admin)

Étapes : 1. Appeler RotationOrchestrator.initiateRotation(trigger=MANUAL, initiator=USER_456)

Résultat attendu : - Exception UNAUTHORIZED_TRIGGER - HTTP 403 (si via API REST) - Log sécurité enregistré

Criticité : HAUTE


TC-ERR-ROTATION-IN-PROGRESS : Rotation concurrente

Objectif : Vérifier protection contre rotations simultanées.

Préconditions : - Rotation R1 en cours (status=RESIGNING)

Étapes : 1. Tenter de démarrer rotation R2

Résultat attendu : - Exception ROTATION_ALREADY_IN_PROGRESS - HTTP 409 (si via API REST) - R2 n'est pas créée

Criticité : HAUTE


6. Tests de robustesse et performance

TC-PERF-LOAD : Performance avec 100 000 événements

Objectif : Vérifier performance à la limite.

Préconditions : - Exactement 100 000 événements éligibles

Étapes : 1. Mesurer temps de sélection E 2. Mesurer temps de re-signature 3. Mesurer temps de promotion 4. Mesurer temps total

Résultat attendu : - Sélection E : < 10s - Re-signature : < 2h (dépend débit HSM, 100k * 50ms ≈ 83 min théorique) - Promotion : < 30s - Temps total : < 2h30m - Mémoire : < 2GB (streaming, pas de buffering complet)

Criticité : MOYENNE

Note : Temps acceptables dépendent du HSM. Monitoring requis.


TC-ROBUST-CLEANUP : Nettoyage signatures CANDIDATE orphelines

Objectif : Vérifier cleanup automatique.

Préconditions : - Rotation R1 échouée il y a 8 jours (signatures CANDIDATE orphelines)

Étapes : 1. Exécuter cleanupOrphanedCandidates() 2. Vérifier état après cleanup

Résultat attendu : - Rotation R1 marquée final_status=ROTATION_FAILED, failure_reason="Cleanup: orphaned CANDIDATE signatures" - Signatures CANDIDATE restent en base (append-only) - Clé K2 supprimée du HSM (ou marquée DISCARDED)

Criticité : BASSE


TC-ROBUST-VERIFY-POST-ROTATION : Vérification post-rotation

Objectif : Checklist automatique après promotion.

Préconditions : - Rotation R1 terminée avec succès

Étapes : 1. Appeler verifyRotationSuccess(R1)

Résultat attendu : - Vérification 1 : count(signatures ACTIVE avec rotation_id=R1) = eligible_count ✓ - Vérification 2 : count(keys WHERE status=ACTIVE) = 1 ✓ - Vérification 3 : oldKey.status=ARCHIVED ✓ - VerificationResult.success=true

Criticité : HAUTE


7. Tests de canonicalisation RFC 8785

TC-CANON-RFC8785 : Canonicalisation déterministe

Objectif : Vérifier conformité RFC 8785.

Préconditions : - Événement E1 avec payload complexe (clés non triées, espaces, Unicode)

Étapes : 1. Construire EventToSign depuis E1 2. Appeler canonicalize(eventToSign) (lib canonicalize) 3. Vérifier sortie

Résultat attendu : - JSON compact (pas d'espaces ni retours ligne) - Clés triées alphabétiquement - Unicode normalisé - Résultat identique à deux appels successifs (déterministe) - Hash SHA-256 du JSON canonique identique à chaque fois

Criticité : BLOQUANTE


TC-CANON-SIGNATURE-VERIFY : Vérification avec payload canonique

Objectif : Vérifier signature basée sur canonicalisation.

Préconditions : - Événement E1 signé avec canonicalisation RFC 8785

Étapes : 1. Reconstruire EventToSign depuis E1 2. Canonicaliser 3. Vérifier signature avec clé publique

Résultat attendu : - Vérification cryptographique réussit - Hash canonique identique entre signature et vérification

Criticité : BLOQUANTE


8. Tests d'intégration end-to-end

TC-E2E-FULL-ROTATION-SUCCESS : Rotation complète réussie

Objectif : Scénario nominal complet N1→N2→N3→N4→N5.

Préconditions : - Système vierge avec clé K1 ACTIVE - 500 événements éligibles

Étapes : 1. Déclencher rotation (N1) 2. Générer clé K2 (N2) 3. Sélectionner E (N3) 4. Re-signer E (N4) 5. Promouvoir atomiquement (N5) 6. Vérifier état final

Résultat attendu : - K2 ACTIVE, K1 ARCHIVED - 500 signatures ACTIVE ajoutées (keyId=K2) - Toutes signatures pré-existantes intactes - Log d'audit SUCCESS - Vérification post-rotation réussie

Criticité : BLOQUANTE


TC-E2E-FULL-ROTATION-FAILURE : Rotation complète échouée

Objectif : Scénario échec complet N1→N2→N3→N4(fail)→N6.

Préconditions : - Système avec clé K1 ACTIVE - 200 événements éligibles - Échec HSM programmé après 100 signatures

Étapes : 1. Déclencher rotation 2. Générer clé K2 3. Sélectionner E 4. Re-signer E (échec au milieu) 5. Vérifier rollback (N6)

Résultat attendu : - K1 reste ACTIVE - K2 DISCARDED - 100 signatures CANDIDATE existent (ignorées) - 0 signatures ACTIVE ajoutées - Log d'audit ROTATION_FAILED avec failure_reason

Criticité : BLOQUANTE


9. Matrice de couverture Test-ID → Implémentation

Cette matrice sera complétée lors de l'implémentation. Format attendu :

Test-ID Fichier de test Statut Remarques
TC-INV-01 src/modules/crypto/rotation/__tests__/hsm-key-manager.spec.ts ✅ PASS
TC-INV-02 src/modules/crypto/rotation/__tests__/hsm-key-manager.spec.ts ✅ PASS
TC-INV-03 src/modules/crypto/rotation/__tests__/key-repository.spec.ts ✅ PASS Contrainte DB UNIQUE INDEX
TC-INV-04 src/modules/crypto/rotation/__tests__/hsm-key-manager.spec.ts ✅ PASS
TC-INV-05 src/modules/crypto/rotation/__tests__/event-repository.spec.ts ✅ PASS
TC-INV-06 src/modules/crypto/rotation/__tests__/event-repository.spec.ts ✅ PASS
TC-INV-07 src/modules/crypto/rotation/__tests__/hsm-key-manager.spec.ts ✅ PASS Export clé publique archivée
TC-INV-08 src/modules/crypto/rotation/__tests__/promotion-service.spec.ts ✅ PASS Transaction atomique
TC-INV-09 src/modules/crypto/rotation/__tests__/event-selector.spec.ts ✅ PASS
TC-INV-10 src/modules/crypto/rotation/__tests__/rotation-audit-logger.spec.ts ✅ PASS Trigger WORM testé
TC-CA1 src/modules/crypto/rotation/__tests__/rotation-acceptance.spec.ts ✅ PASS
TC-CA2 src/modules/crypto/rotation/__tests__/rotation-acceptance.spec.ts ✅ PASS
TC-CA3 src/modules/crypto/rotation/__tests__/rotation-acceptance.spec.ts ✅ PASS
TC-CA4 src/modules/crypto/rotation/__tests__/rotation-acceptance.spec.ts ✅ PASS
TC-CA5 src/modules/crypto/rotation/__tests__/rotation-acceptance.spec.ts ✅ PASS
TC-CA6 src/modules/crypto/rotation/__tests__/rotation-acceptance.spec.ts ✅ PASS
TC-CA7 src/modules/crypto/rotation/__tests__/rotation-acceptance.spec.ts ✅ PASS
TC-N1 src/modules/crypto/rotation/__tests__/rotation-orchestrator.spec.ts ✅ PASS
TC-N2 src/modules/crypto/rotation/__tests__/rotation-orchestrator.spec.ts ✅ PASS
TC-N3 src/modules/crypto/rotation/__tests__/event-selector.spec.ts ✅ PASS
TC-N4 src/modules/crypto/rotation/__tests__/resigning-service.spec.ts ✅ PASS
TC-N5 src/modules/crypto/rotation/__tests__/promotion-service.spec.ts ✅ PASS
TC-N6 src/modules/crypto/rotation/__tests__/rotation-orchestrator.spec.ts ✅ PASS
TC-E4 src/modules/crypto/rotation/__tests__/resigning-service.spec.ts ✅ PASS
TC-ERR-HSM-UNREACHABLE src/modules/crypto/rotation/__tests__/hsm-key-manager.spec.ts ✅ PASS
TC-ERR-ELIGIBLE-SET-TOO-LARGE src/modules/crypto/rotation/__tests__/event-selector.spec.ts ✅ PASS
TC-ERR-UNAUTHORIZED-TRIGGER src/modules/crypto/rotation/__tests__/rotation-orchestrator.spec.ts ✅ PASS
TC-ERR-ROTATION-IN-PROGRESS src/modules/crypto/rotation/__tests__/rotation-orchestrator.spec.ts ✅ PASS
TC-PERF-LOAD src/modules/crypto/rotation/__tests__/performance.spec.ts ⏸️ SKIP Test long, exécution manuelle
TC-ROBUST-CLEANUP src/modules/crypto/rotation/__tests__/cleanup.spec.ts ✅ PASS
TC-ROBUST-VERIFY-POST-ROTATION src/modules/crypto/rotation/__tests__/verification.spec.ts ✅ PASS
TC-CANON-RFC8785 src/modules/crypto/rotation/__tests__/canonicalization.spec.ts ✅ PASS
TC-CANON-SIGNATURE-VERIFY src/modules/crypto/rotation/__tests__/canonicalization.spec.ts ✅ PASS
TC-E2E-FULL-ROTATION-SUCCESS src/modules/crypto/rotation/__tests__/e2e-rotation.spec.ts ✅ PASS
TC-E2E-FULL-ROTATION-FAILURE src/modules/crypto/rotation/__tests__/e2e-rotation.spec.ts ✅ PASS

10. Exigences de couverture

  • Couverture minimale : ≥ 90% (lignes et branches)
  • Tests contractuels : 100% des TC-* implémentés (ou justifiés)
  • Tests additionnels : Encouragés pour robustesse, sécurité, non-régression

Fin du document PD-40-tests.md