PD-80 — Tests (v3)
1. Références
- Spécification :
PD-80-specification.md (v3) - Epic :
EPIC-XX
2. Matrice de couverture
| ID Invariant | ID Critère | ID Test | Couverture | Commentaire |
| INV-80-01 | CA-14 | TC-INV-01, TC-ERR-09, TC-ERR-10, TC-NOM-16, TC-NOM-17 | Oui | Machine d'états close-world vérifiée + transitions retour testées. |
| INV-80-02 | CA-01 | TC-NOM-01 | Oui | Mineur auto fast-track sans action manuelle. |
| INV-80-03 | CA-02, CA-07, CA-08 | TC-NOM-02, TC-ERR-02, TC-ERR-03, TC-ERR-12 | Oui | Éligibilité + quota + rate-limit, y compris enterprise. |
| INV-80-04 | CA-03, CA-06 | TC-NOM-03, TC-ERR-07 | Oui | P95<15 min sur fenêtre 24h glissante, N≥100, cutoff >=120 min. |
| INV-80-05 | CA-04 | TC-NOM-04 | Oui | Mini-batch 1..20 et flush<=5, pas d'ancrage individuel urgent. |
| INV-80-06 | CA-05 | TC-ERR-05, TC-ERR-06, TC-ERR-11 | Oui | Fail-soft + retries contractuels ⅕/15/30 + comportement post-épuisement. |
| INV-80-07 | CA-07, CA-08 | TC-ERR-02, TC-ERR-03, TC-ERR-12 | Oui | Contrôles admission en queue prioritaire. |
| INV-80-08 | CA-09, CA-10, CA-11, CA-11b | TC-NOM-05, TC-NOM-06, TC-NOM-12, TC-ERR-07, TC-NOM-13 | Oui | Notification obligatoire, fallback push, webhook FAILED_TIMEOUT. |
| INV-80-09 | CA-15 | TC-NOM-09, TC-INV-09 | Oui | Vérification absence secret clair en base + chiffrement at-rest. |
| INV-80-10 | CA-16 | TC-NOM-10, TC-INV-10, TC-NOM-11, TC-NOM-18 | Oui | Atomicité DB/async, idempotence, rattrapage post-crash + réconciliation + protection double exécution. |
| INV-80-11 | CA-13 | TC-NOM-08 | Oui | Ratio pondéré 5:1, débit standard ≥ 16.7% sur fenêtre 10 min. |
| — | CA-12 | TC-NOM-07 | Oui | Exposition métriques contractuelles. |
| — | CA-17 | TC-NOM-14 | Oui | Webhook retry avec backoff. |
| — | CA-18 | TC-NOM-15 | Oui | Clamps journalisés au démarrage. |
3. Scénarios de test – Flux nominaux
TEST-ID: TC-NOM-01
Référence spec: INV-80-02, CA-01, §5.5
GIVEN
- Compte avec account_type=minor
- Upload valide (formats §5.1)
- Quota et rate-limit disponibles
WHEN
- Le document est reçu
THEN
- L'état passe à QUEUED_PRIORITY sans action manuelle
- Le pipeline prioritaire est déclenché
AND
- Une trace d'audit corrèle document_id, user_id, transition d'état
TEST-ID: TC-NOM-02
Référence spec: INV-80-03, CA-02, §5.6
GIVEN
- Compte standard/premium/enterprise éligible
- Quota disponible et rate-limit non dépassé
- Requête explicite urgent valide
WHEN
- La demande urgente est soumise
THEN
- La demande est admise en queue prioritaire (QUEUED_PRIORITY)
AND
- L'audit enregistre l'origine "manuel" et l'éligibilité contrôlée
TEST-ID: TC-NOM-03
Référence spec: INV-80-04, CA-03, §5.2, §10.1
GIVEN
- Contexte de référence perf (§10.1)
- Jeu de charge mixte représentatif, horodatage synchronisé UTC (NTP ±1s)
- N ≥ 100 scellements sur la fenêtre d'observation de 24h (N minimum configurable via p95_min_samples, défaut 100, bornes [10, 10000])
WHEN
- N scellements urgents sont exécutés sur la fenêtre 24h glissante
THEN
- La métrique seal_latency_seconds présente P95 < 900s (15 min)
- Le calcul P95 utilise exactement les N échantillons de la fenêtre (pas de sous-échantillonnage)
AND
- Les transitions terminales SEALED sont observables avec timestamps corrélables
- Si N < p95_min_samples (100 par défaut), le P95 est marqué INSUFFICIENT_DATA et aucune violation SLA n'est émise
- Le nombre d'échantillons N est exposé dans la métrique pour vérification
TEST-ID: TC-NOM-04
Référence spec: INV-80-05, CA-04, §5.2
GIVEN
- Flux urgent actif avec preuves prêtes à ancrer
WHEN
- Le mécanisme de mini-batch déclenche un flush
THEN
- Chaque lot urgent contient entre 1 et 20 preuves
- Le délai de flush n'excède pas 5 minutes
AND
- Aucune preuve urgente n'est ancrée individuellement hors mini-batch
TEST-ID: TC-NOM-05
Référence spec: INV-80-08, CA-09, CA-10
GIVEN
- Canaux push/email configurés
- Device push enregistré pour l'utilisateur
- Document urgent arrivant à SEALED
WHEN
- La transition vers SEALED est persistée
THEN
- Une notification push est émise et horodatée
- Un email est émis contenant hash, tx blockchain, lien preuve, horodatage TSA
AND
- Les événements de notification sont auditables
TEST-ID: TC-NOM-06
Référence spec: INV-80-08, CA-11, §5.1, §5.11
GIVEN
- Webhook configuré et endpoint disponible
- Transition terminale SEALED
WHEN
- L'événement proof_sealed est généré
THEN
- Un POST webhook est observé
- Le payload contient document_id, status=SEALED, sealed_at, blockchain_tx, merkle_root
AND
- La tentative et le résultat de livraison sont journalisés
TEST-ID: TC-NOM-07
Référence spec: CA-12
GIVEN
- Service démarré en environnement de test
WHEN
- L'endpoint de métriques est interrogé
THEN
- Les métriques seal_latency_seconds, priority_queue_depth, tsa_latency_seconds, anchor_latency_seconds sont présentes
AND
- Les unités et labels sont cohérents avec les définitions contractuelles
TEST-ID: TC-NOM-08
Référence spec: INV-80-11, CA-13
GIVEN
- Charge mixte continue (urgent + standard)
- Politique de pondération priorité 5:1 active
- Fenêtre d'observation de 10 minutes
WHEN
- Le scheduler traite les files sur la période stabilisée
THEN
- Le débit standard mesuré ≥ 1/(5+1) ≈ 16.7% du débit total sur la fenêtre 10 min
AND
- Les jobs standard montrent une progression continue (pas de blocage durable)
TEST-ID: TC-NOM-09
Référence spec: INV-80-09, CA-15
GIVEN
- Artefacts temporaires (clé/fragment/DEK/ReKey) persistés
WHEN
- Un audit de stockage est exécuté
THEN
- Aucun secret n'apparaît en clair en base
- Les enregistrements démontrent chiffrement at-rest (AES-256-GCM ou enveloppe HSM)
AND
- Les preuves d'audit sont archivées et horodatées
TEST-ID: TC-NOM-10
Référence spec: INV-80-10, §5.7
GIVEN
- Une opération de scellement urgent en cours
WHEN
- Crash simulé pré-commit puis post-commit
THEN
- Pré-commit: rollback total, aucun artefact persistant
- Post-commit: état DB reste source de vérité
AND
- Les traitements async sont rattrapés sans duplication non contrôlée
TEST-ID: TC-NOM-11
Référence spec: INV-80-10, CA-16, §5.10
GIVEN
- Un job de scellement urgent en état TSA_PENDING
- Le processus BullMQ a crashé, laissant le job orphelin
- last_activity_at du job > reconciliation_orphan_threshold (10 min)
WHEN
- Le worker de réconciliation effectue son scan périodique (toutes les 5 min)
THEN
- Le job orphelin est détecté (état non terminal + pas de job BullMQ actif)
- Un nouveau job BullMQ est créé avec flag reconciled=true
- Le compteur de retry est préservé
AND
- Le rattrapage s'effectue sous reconciliation_max_catchup_delay (30 min)
- L'événement de réconciliation est journalisé dans l'audit
TEST-ID: TC-NOM-12
Référence spec: INV-80-08, CA-11b, §5.11
GIVEN
- Webhook configuré et endpoint disponible
- Transition terminale FAILED_TIMEOUT
WHEN
- L'événement proof_failed est généré
THEN
- Un POST webhook est observé
- Le payload contient document_id, status=FAILED_TIMEOUT, failed_at, reason, last_state
AND
- La tentative et le résultat de livraison sont journalisés
TEST-ID: TC-NOM-13
Référence spec: INV-80-08, CA-09, §5.12
GIVEN
- Aucun device push enregistré pour l'utilisateur
- Email configuré
- Document urgent arrivant à SEALED
WHEN
- La transition vers SEALED est persistée
THEN
- L'email est envoyé comme fallback primaire
- Aucune erreur push n'est levée
- Un log INFO "no device registered" est émis
AND
- INV-80-08 est satisfait (au moins un canal réussi)
TEST-ID: TC-NOM-14
Référence spec: CA-17, §5.11
GIVEN
- Webhook configuré mais endpoint temporairement indisponible
- Transition terminale SEALED
WHEN
- La première tentative de livraison webhook échoue
THEN
- Retries planifiés aux délais 1/5/15 min
- Chaque tentative et son résultat (HTTP status, durée) sont journalisés
AND
- Après épuisement des 3 retries, un événement webhook_delivery_failed est émis dans l'audit
- Le flux principal de scellement n'est pas bloqué
TEST-ID: TC-NOM-15
Référence spec: CA-18, §5.2
GIVEN
- Configuration avec des valeurs hors bornes :
batch_size_min=0, priority_weight=15, batch_time_max=20
WHEN
- Le service démarre
THEN
- Les valeurs sont clampées aux bornes : batch_size_min=1, priority_weight=10, batch_time_max=15
- Un log WARN est émis pour chaque paramètre clampé, incluant :
le nom du paramètre, la valeur fournie et la valeur clampée
TEST-ID: TC-NOM-16
Référence spec: INV-80-01, §5.4 (transition retour TSA_PENDING → QUEUED_PRIORITY)
GIVEN
- Job en état TSA_PENDING
- Invalidation technique du résultat TSA détectée (ex : token TSA corrompu avant ancrage)
WHEN
- La transition retour TSA_PENDING → QUEUED_PRIORITY est déclenchée
THEN
- Le job revient en QUEUED_PRIORITY
- Le motif de requeue est tracé dans le journal d'audit (seal_id, état source, état cible, motif)
AND
- Le compteur de retry est préservé (pas remis à zéro)
- Le job est réadmis en queue prioritaire pour retraitement TSA
TEST-ID: TC-NOM-17
Référence spec: INV-80-01, §5.4 (transition retour TSA_SEALED → TSA_PENDING)
GIVEN
- Job en état TSA_SEALED
- Retry technique nécessaire (ex : token TSA valide mais inclusion Merkle impossible)
WHEN
- La transition retour TSA_SEALED → TSA_PENDING est déclenchée
THEN
- Le job revient en TSA_PENDING pour retraitement
- Le motif de retour est tracé dans le journal d'audit
AND
- L'horodatage TSA précédent est invalidé (nouveau TSA requis)
- Le compteur de retry est incrémenté
TEST-ID: TC-NOM-18
Référence spec: §5.10 (protection double exécution réconciliation)
GIVEN
- Job orphelin détecté par le worker de réconciliation
- Un lock distribué Redis reconciliation:lock:{seal_id} est déjà détenu (autre instance)
WHEN
- Le worker tente d'acquérir le lock pour recréer le job
THEN
- Le lock échoue (déjà détenu)
- Le job est ignoré pour ce cycle de scan (pas de doublon créé)
AND
- Un log INFO est émis : "lock already held, skipping reconciliation for {seal_id}"
- Au cycle suivant, si le lock est libéré et le job toujours orphelin, il est rattrapé
4. Scénarios de test – Cas d'erreur
TEST-ID: TC-ERR-01
Référence spec: §6 (400), §5.1
GIVEN
- Requête avec hash_document invalide (regex non conforme)
WHEN
- La validation d'entrée est exécutée
THEN
- Réponse 400 BAD_REQUEST
- Aucune admission en queue prioritaire
TEST-ID: TC-ERR-02
Référence spec: §6 (403), CA-07
GIVEN
- Compte standard/freemium à 3/3 (ou premium 10/10 ou enterprise 1000/1000) sur le mois
WHEN
- Une demande urgent est soumise
THEN
- Réponse 403 FORBIDDEN
- Aucun job urgent créé
- Le corps de réponse ne contient ni le nom du plan ni le quota restant
TEST-ID: TC-ERR-03
Référence spec: §6 (429), CA-08
GIVEN
- Utilisateur avec 1 demande urgent déjà effectuée dans l'heure (standard/premium)
WHEN
- Une seconde demande urgent est soumise dans la même heure
THEN
- Réponse 429 TOO_MANY_REQUESTS
- Aucun enqueuing prioritaire
- Le corps de réponse ne contient ni le nom du plan ni le quota restant
TEST-ID: TC-ERR-04
Référence spec: §6 (422), §5.1
GIVEN
- Réponse de dépendance externe invalide :
(a) token TSA non parseable reçu du TSA server, ou
(b) merkle_proof invalide retourné par le worker Merkle, ou
(c) blockchain_tx invalide retourné par le service blockchain
WHEN
- La validation de la réponse de dépendance est exécutée en pipeline interne
THEN
- L'artefact invalide est rejeté avec journalisation de l'erreur
- Un retry est déclenché selon la politique de backoff contractuelle
- Aucun passage en état terminal SEALED avec un artefact invalide
AND
- L'erreur est tracée dans le journal d'audit avec l'identifiant de la dépendance fautive
TEST-ID: TC-ERR-05
Référence spec: INV-80-06, CA-05, §6 (503)
GIVEN
- Dépendance TSA indisponible temporairement
WHEN
- Une tentative TSA urgent échoue
THEN
- Retries planifiés aux délais 1/5/15/30 min
- L'état reste TSA_PENDING pendant les retries (pas de changement d'état)
- Le compteur de retry est incrémenté sur le job BullMQ
AND
- Aucun FAILED_TIMEOUT immédiat avant final_timeout
TEST-ID: TC-ERR-06
Référence spec: INV-80-06, CA-05, §6 (503)
GIVEN
- Dépendance blockchain indisponible temporairement
WHEN
- L'étape d'ancrage urgent échoue
THEN
- Retries conformes au backoff contractuel
- L'état reste ANCHOR_PENDING pendant les retries
AND
- Le workflow reste non terminal tant que final_timeout non atteint
TEST-ID: TC-ERR-07
Référence spec: INV-80-04, CA-06, §6 (504)
GIVEN
- Workflow non terminal dépassant final_timeout (>=120 min)
WHEN
- Le timer final expire
THEN
- Transition forcée vers FAILED_TIMEOUT
- Notification d'échec émise (push/email + webhook proof_failed si configuré)
AND
- Événement d'escalade opérationnelle tracé
TEST-ID: TC-ERR-08
Référence spec: §5.1 sealed_status
GIVEN
- État persisté inconnu (hors enum contractuel)
WHEN
- Le statut est lu par le service
THEN
- Erreur 500 est produite
- L'anomalie est journalisée pour remédiation
TEST-ID: TC-ERR-09
Référence spec: INV-80-01, CA-14
GIVEN
- Preuve en état terminal SEALED
WHEN
- Une transition sortante est demandée
THEN
- Transition refusée explicitement
- État SEALED inchangé
TEST-ID: TC-ERR-10
Référence spec: INV-80-01, §5.4
GIVEN
- Workflow dans un état non terminal
WHEN
- Une transition non listée par la machine d'états est tentée
THEN
- Transition rejetée explicitement
- Audit de refus enregistré
TEST-ID: TC-ERR-11
Référence spec: INV-80-06, §5.4
GIVEN
- Job TSA_PENDING ayant épuisé ses 4 retries (5e échec consécutif)
- final_timeout non encore atteint
WHEN
- Le 5e échec survient
THEN
- Le job reste en état TSA_PENDING (pas de transition)
- Aucun nouveau retry n'est planifié
- Le timer final_timeout continue à s'écouler
AND
- Un événement "retries_exhausted" est journalisé dans l'audit
- La transition FAILED_TIMEOUT interviendra à l'expiration du final_timeout
TEST-ID: TC-ERR-12
Référence spec: INV-80-07, CA-08, §5.2
GIVEN
- Compte enterprise ayant effectué 10 demandes urgentes dans l'heure
WHEN
- Une 11e demande urgente est soumise dans la même heure
THEN
- Réponse 429 TOO_MANY_REQUESTS
- Aucun enqueuing prioritaire
5. Tests d'invariants (non négociables)
| Invariant | Test(s) dédiés | Observable | Commentaire |
| INV-80-01 | TC-INV-01 (= TC-ERR-09 + TC-ERR-10) | Rejet transitions interdites | Machine d'états close-world. |
| INV-80-02 | TC-NOM-01 | QUEUED_PRIORITY auto pour minor | Sans action utilisateur. |
| INV-80-03 | TC-NOM-02, TC-ERR-02, TC-ERR-03, TC-ERR-12 | Admission conditionnée | Éligibilité + quota + rate-limit, y compris enterprise. |
| INV-80-04 | TC-NOM-03, TC-ERR-07 | P95<15 min (24h, N≥100) et cutoff >=120 min | Fenêtre P95 contractualisée. |
| INV-80-05 | TC-NOM-04 | Lot 1..20, flush<=5, pas d'individuel | Cohérence mini-batch. |
| INV-80-06 | TC-ERR-05, TC-ERR-06, TC-ERR-11 | Retries ⅕/15/30, même état, post-épuisement | Fail-soft prouvé + comportement après 4 retries. |
| INV-80-07 | TC-ERR-02, TC-ERR-03, TC-ERR-12 | Rejets 403/429 avant queue | Contrôle admission y compris enterprise. |
| INV-80-08 | TC-NOM-05, TC-NOM-06, TC-NOM-12, TC-NOM-13, TC-ERR-07 | Notifications succès/échec + fallback | Multi-canal + webhook FAILED_TIMEOUT + fallback push. |
| INV-80-09 | TC-INV-09 (= TC-NOM-09) | Absence secrets en clair | Conformité crypto at-rest. |
| INV-80-10 | TC-INV-10 (= TC-NOM-10), TC-NOM-11 | Atomicité + rattrapage + réconciliation | Idempotence async + worker réconciliation. |
| INV-80-11 | TC-NOM-08 | Débit standard ≥ 16.7% sur 10 min | Seuil observable et testable. |
6. Tests de non-régression
| Test ID | Objet | Observable | Commentaire |
| TC-NR-01 | Contrat machine d'états | Aucune transition non listée acceptée | À rejouer à chaque release. |
| TC-NR-02 | Contrat codes d'erreur | 400/403/429/422/503/504 stables | Empêche dérive API. |
| TC-NR-03 | Contrat quotas/rate-limit | Seuils plan inchangés | Freemium 3/premium 10/enterprise 1000 + rate-limit enterprise 10/h. |
| TC-NR-04 | Contrat retries | Delays ⅕/15/30 observés, même état | Détecte régression de backoff. |
| TC-NR-05 | Contrat notification terminale | SEALED/FAILED_TIMEOUT toujours notifiés (y compris webhook) | Aucun échec silencieux. |
| TC-NR-06 | Contrat crypto at-rest | Zéro secret clair en base | Garde-fou sécurité. |
| TC-NR-07 | Contrat métriques | 4 métriques obligatoires exposées | Monitoring non régressif. |
| TC-NR-08 | Contrat équité scheduler | Débit standard ≥ 16.7% sur 10 min | Contrôle anti-starvation avec seuil. |
| TC-NR-09 | Contrat réconciliation | Worker détecte et rattrape orphelins sous 30 min | Résilience post-crash. |
| TC-NR-10 | Contrat webhook retry | Retries ⅕/15 min, webhook_delivery_failed après épuisement | Politique retry webhook stable. |
| TC-NR-11 | Contrat clamp logging | Chaque clamp journalisé WARN au démarrage | Observabilité configuration. |
| TC-NR-12 | Contrat lock réconciliation | Lock Redis acquis avant toute recréation de job | Protection double exécution. |
| TC-NR-13 | Contrat rate-limit mineur | Comptes minor limités à 2 urgent/h, alerte sécurité si dépassement | Anti-abus compte mineur compromis. |
7. Tests négatifs et adversariaux
| Test ID | Entrée invalide / abus | Résultat attendu | Observable |
| TC-NEG-01 | UUID non v4/casse invalide | Rejet 400 | Réponse API + absence queue job |
| TC-NEG-02 | hash_document en uppercase | Rejet 400 | Validation regex [a-f0-9]{64} |
| TC-NEG-03 | Rafale >1 urgent/h utilisateur (standard) | Rejet 429 stable | Compteur rate-limit + audit |
| TC-NEG-04 | Freemium dépassement quota mensuel (>3) | Rejet 403 | Compteur quota + audit |
| TC-NEG-05 | Tentative transition terminale forcée | Rejet explicite | État inchangé + événement refus |
| TC-NEG-06 | Réponse TSA corrompue reçue du serveur TSA | Retry déclenché, artefact rejeté | Journal d'audit + retry backoff |
| TC-NEG-07 | Simuler indisponibilité dépendance intermittente | Retries sans échec immédiat, état inchangé | Chronologie tentatives conforme |
| TC-NEG-08 | Charge prioritaire extrême continue | Débit standard ≥ 16.7% sur 10 min | Seuil mesuré et vérifié |
| TC-NEG-09 | Enterprise dépassement quota mensuel (>1000) | Rejet 403 | Compteur quota + audit |
| TC-NEG-10 | Enterprise rafale >10 urgent/h | Rejet 429 | Compteur rate-limit + audit |
| TC-NEG-11 | Corps de réponse 403/429 inspecté | Ni nom de plan ni quota restant exposé | Anti-disclosure vérifié |
| TC-NEG-12 | Compte mineur avec >2 urgents/h | Rejet 429 + alerte sécurité émise | Anti-abus mineur compromis |
8. Observabilité requise pour les tests
- État système : historique des transitions d'état avec timestamps UTC RFC3339 et corrélation
document_id. - Réponse API : code HTTP, corps d'erreur contractuel (sans disclosure plan/quota), identifiant de corrélation.
- Journal d'audit : admission/rejet quota-rate-limit, transitions, retries (avec état maintenu), refus de transition, escalade, réconciliation, retries_exhausted, webhook_delivery_failed.
- Événement signé / horodaté : événements TSA/ancrage/notification avec trace temporelle vérifiable (NTP ±1s).
- Export probatoire : proof package contenant hash, preuve Merkle, référence TSA, tx blockchain, statut final.
- Logs démarrage : WARN pour chaque clamp appliqué, CRITICAL pour dérive NTP >1s.
- Métriques réconciliation : compteur jobs orphelins détectés/rattrapés, durée de rattrapage.
9. Règles non testables
| Règle | Raison | Impact |
| Admissibilité judiciaire de la preuve | Hors périmètre technique explicitement indiqué | Mineur |
| Escalade opérationnelle détaillée (§10.2.5) | Destinataires/SLA d'acquittement/criticité absents | Majeur |
| Schéma/version du proof package (§10.2.4) | Format canonique manquant pour validation stricte | Majeur |
10. Verdict QA
- ✅ Testable (réserves résiduelles mineures)
Les invariants et critères sont testables de façon déterministe. Les écarts bloquants v1 (fenêtre P95, QUEUED_PRIORITY dans SLA, note de brouillon) sont résolus. Les écarts majeurs v1 (retries/transitions, webhook policy, réconciliation, enterprise cap, fallback push, enum freemium, starvation seuil) sont contractualisés. Réserves résiduelles limitées au schéma du proof package et aux détails d'escalade opérationnelle (données métier attendues du PO).