PD-172 — Scénarios de tests contractuels
1. Références
- Spécification : PD-172-specification.md
- Epic : EPIC-XX (référence produit : PD-186 — Backend Core sécurisé et modulaire)
2. Matrice de couverture
| ID Invariant | ID Critère | ID Test | Couverture | Commentaire |
| INV-172-01 | CA-172-01 | TC-NOM-01 | Oui | Cohérence multi-instances sur tuple normalisé identique. |
| INV-172-02 | CA-172-02 | TC-NOM-02 | Partiel | 429 + headers testable; valeur métier exacte des seuils non spécifiée. |
| INV-172-04 | CA-172-03 | TC-NOM-03 | Partiel | Différenciation sensible/générique testable; seuils exacts manquants. |
| INV-172-01 | CA-172-04 | TC-NOM-04 | Oui | Variation unitaire de chaque dimension V1. |
| INV-172-01 | CA-172-05 | TC-NOM-05, TC-NOM-06 | Partiel | Mécanisme burst+sustained testable; bornes contractuelles manquantes. |
| INV-172-03 | CA-172-06 | TC-NOM-07 | Oui | Refus tracé en log structuré + métrique. |
| INV-172-03 | CA-172-07 | TC-NOM-08 | Partiel | Exploitabilité testable, granularité/labels max à clarifier. |
| INV-172-05 | CA-172-08 | TC-NOM-09, TC-NOM-10, TC-NOM-11 | Partiel | Matrice fail-open/fail-closed testable; code HTTP fail-closed exact ambigu. |
| N/A | CA-172-09 | TC-NOM-12 | Partiel | Mesure P99 testable en env cible instrumenté uniquement. |
| INV-172-08 | CA-172-10 | TC-NOM-13 | Oui | Stabilité config au boot et absence de reload live. |
| INV-172-07 | N/A | TC-NOM-14 | Oui | Aucune whitelist/bypass active en V1. |
| INV-172-06 | N/A | TC-NOM-15, TC-NOM-16 | Oui | Transitions autorisées uniquement, terminaux stricts. |
| INV-172-09 | N/A | TC-INV-09 | Partiel | Vérification documentaire/contrat, pas purement runtime. |
| INV-172-10 | N/A | TC-NOM-17 | Oui | Non-applicabilité crypto démontrée fonctionnellement. |
3. Scénarios de test – Flux nominaux
Préconditions communes (P0) pour TC-NOM-01 à TC-NOM-17 :
- Environnement de test figé avec 2 instances backend (
A,B) version identique. - Redis dédié, DB index conforme, initialisé à vide avant chaque test.
- Snapshot de configuration boot
C_boot archivé (familles de routes, limites, fenêtres). - Horloges synchronisées; campagne exécutée sans bascule de fenêtre non maîtrisée.
- Sondes d’observabilité accessibles (logs structurés + métriques) avec corrélation
request_id. - Sonde de logique métier disponible pour prouver exécution/non-exécution (compteur d’effet de bord).
TEST-ID: TC-NOM-01
Référence spec: INV-172-01, CA-172-01
GIVEN
- P0
- Tuple normalisé T = (route, ip, user_id, tenant) identique pour toutes les requêtes
WHEN
- Le trafic saturant T est réparti entre A et B jusqu’au dépassement
THEN
- La décision de refus est identique quelle que soit l’instance ciblée
AND
- Le compteur restant observé est cohérent inter-instances pour T
TEST-ID: TC-NOM-02
Référence spec: INV-172-02, CA-172-02
GIVEN
- P0
- Une route protégée avec quota atteignable dans la fenêtre active
WHEN
- Une requête supplémentaire est émise après dépassement du quota
THEN
- Réponse HTTP = 429
- Headers présents et conformes regex: X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After
AND
- Aucun effet métier n’est exécuté (compteur inchangé)
TEST-ID: TC-NOM-03
Référence spec: INV-172-04, CA-172-03
GIVEN
- P0
- Une route sensible et une route générique avec profils chargés depuis C_boot
WHEN
- Deux campagnes strictement identiques sont exécutées (même tuple, même cadence)
THEN
- La route sensible atteint le refus plus tôt sur au moins une fenêtre ou dimension
AND
- La différenciation est observable en réponse et métriques
TEST-ID: TC-NOM-04
Référence spec: INV-172-01, CA-172-04
GIVEN
- P0
- Un tuple de base saturé proche de la limite
WHEN
- Quatre essais sont exécutés en ne modifiant qu’une dimension à la fois: ip puis user_id puis tenant puis route
THEN
- Chaque variation produit une décision recalculée sur une clé distincte
AND
- Aucune autre dimension n’explique le changement de décision
TEST-ID: TC-NOM-05
Référence spec: INV-172-01, CA-172-05
GIVEN
- P0
- Paramètres burst/sustained lus depuis C_boot
WHEN
- burst est dépassé sans dépasser sustained
THEN
- Décision = THROTTLED immédiate
AND
- La cause de refus est attribuée à la fenêtre burst
TEST-ID: TC-NOM-06
Référence spec: INV-172-01, CA-172-05
GIVEN
- P0
- Paramètres burst/sustained lus depuis C_boot
WHEN
- sustained est dépassé avec trafic cadencé pour rester sous burst à chaque sous-fenêtre
THEN
- Décision = THROTTLED
AND
- La cause de refus est attribuée à la fenêtre sustained
TEST-ID: TC-NOM-07
Référence spec: INV-172-03, CA-172-06
GIVEN
- P0
- Au moins un refus THROTTLED et un refus DENIED_DEGRADED générés
WHEN
- Les observables sont interrogés par request_id
THEN
- Chaque refus possède un log structuré corrélé
AND
- Chaque refus incrémente une métrique dédiée
TEST-ID: TC-NOM-08
Référence spec: INV-172-03, CA-172-07
GIVEN
- P0
- Trafic mixte contrôlé (allowed/refused, familles auth/read/costly)
WHEN
- Les métriques d’exploitation sont extraites sur intervalle fixe
THEN
- Les comptes sont disponibles par route, famille, cause, type de refus
AND
- La somme des métriques est réconciliable avec le volume injecté
TEST-ID: TC-NOM-09
Référence spec: INV-172-05, CA-172-08
GIVEN
- P0
- Redis indisponible
- Route de famille read
WHEN
- Une requête valide est envoyée
THEN
- Décision = BYPASS_DEGRADED (fail-open contrôlé)
AND
- La logique métier est exécutée
TEST-ID: TC-NOM-10
Référence spec: INV-172-05, INV-172-02, CA-172-08
GIVEN
- P0
- Redis indisponible
- Route de famille auth
WHEN
- Une requête valide est envoyée
THEN
- Décision = DENIED_DEGRADED (fail-closed)
AND
- La logique métier n’est pas exécutée
TEST-ID: TC-NOM-11
Référence spec: INV-172-05, INV-172-02, CA-172-08
GIVEN
- P0
- Redis indisponible
- Route de famille costly
WHEN
- Une requête valide est envoyée
THEN
- Décision = DENIED_DEGRADED (fail-closed)
AND
- La logique métier n’est pas exécutée
TEST-ID: TC-NOM-12
Référence spec: CA-172-09
GIVEN
- P0
- Protocole de charge reproductible (durée, QPS, concurrence, mix routes) figé
WHEN
- La campagne est exécutée et la latence du check rate-limit est mesurée
THEN
- P99(latency_rate_limit_check) <= 5 ms
AND
- Le check effectue au plus 1 RTT Redis par requête
TEST-ID: TC-NOM-13
Référence spec: INV-172-08, CA-172-10
GIVEN
- P0
- Système démarré avec configuration C1
WHEN
- La source de config est modifiée vers C2 sans redémarrage, puis avec redémarrage contrôlé
THEN
- Sans redémarrage: comportement inchangé (C1)
AND
- Après redémarrage: comportement conforme à C2
TEST-ID: TC-NOM-14
Référence spec: INV-172-07
GIVEN
- P0
- Tentatives explicites de bypass (headers internes, flags, pseudo-liste d’exemption)
WHEN
- Le trafic saturant est exécuté
THEN
- Aucune exemption n’est appliquée
AND
- Les décisions restent strictement celles du rate-limit normal
TEST-ID: TC-NOM-15
Référence spec: INV-172-06
GIVEN
- P0
- Cinq contextes contrôlés couvrant toutes transitions autorisées depuis RECEIVED
WHEN
- Une requête est exécutée par contexte
THEN
- Transition observée = RECEIVED vers un unique état terminal autorisé
AND
- Aucun état hors enum contractuel n’apparaît
TEST-ID: TC-NOM-16
Référence spec: INV-172-06
GIVEN
- P0
- Traces d’état intra-requête activées
WHEN
- Une requête atteint un état terminal
THEN
- Aucune transition sortante n’est observée depuis cet état terminal
AND
- Une seule décision finale est émise par request_id
TEST-ID: TC-NOM-17
Référence spec: INV-172-10
GIVEN
- P0
- Deux requêtes identiques sur dimensions V1, l’une avec métadonnées crypto, l’autre sans
WHEN
- Les deux requêtes sont exécutées dans le même état de quota
THEN
- La décision rate-limit est identique
AND
- Aucune dépendance envelope encryption n’est requise
4. Scénarios de test – Cas d’erreur
TEST-ID: TC-ERR-01
Référence spec: ERR-172-01
GIVEN
- Quota burst ou sustained déjà dépassé
WHEN
- Une requête additionnelle est envoyée
THEN
- 429 + headers standards obligatoires
- decision_state = THROTTLED
AND
- Absence d’exécution métier
TEST-ID: TC-ERR-02
Référence spec: ERR-172-02
GIVEN
- Redis indisponible
- Route fail-closed (auth ou costly)
WHEN
- Une requête valide est envoyée
THEN
- Refus fail-closed + decision_state = DENIED_DEGRADED
AND
- Absence d’exécution métier
- Vérification du code HTTP exact = NON TESTABLE tant que Q-172-03 n’est pas tranché
TEST-ID: TC-ERR-03
Référence spec: ERR-172-03
GIVEN
- Contexte dimensions invalide ou incomplet (ip/user/tenant/route)
WHEN
- La requête est traitée
THEN
- 400 + decision_state = REJECTED_INVALID_CONTEXT
AND
- Absence d’exécution métier
TEST-ID: TC-ERR-04
Référence spec: ERR-172-04
GIVEN
- Capacité d’injection de faute test activée pour produire une redis_key hors regex contractuelle
WHEN
- Une requête au contexte valide est traitée
THEN
- 500 + code ERR-172-04
AND
- Absence d’exécution métier
TEST-ID: TC-ERR-05
Référence spec: ERR-172-05
GIVEN
- Configuration de boot invalide (dimension manquante, fenêtre invalide, enum invalide)
WHEN
- Le service démarre
THEN
- Démarrage refusé
AND
- Aucun endpoint ne sert de trafic
TEST-ID: TC-ERR-06
Référence spec: ERR-172-06
GIVEN
- Timeout du backend de quota simulé
WHEN
- Requêtes envoyées sur read puis auth/costly
THEN
- read => BYPASS_DEGRADED
- auth/costly => DENIED_DEGRADED
AND
- Observabilité porte explicitement la cause timeout
TEST-ID: TC-ERR-07
Référence spec: ERR-172-07
GIVEN
- Chaîne proxy client ambiguë/non fiable empêchant résolution IP sûre
WHEN
- Une requête est envoyée
THEN
- 400 + code ERR-172-07
AND
- Absence d’exécution métier
TEST-ID: TC-ERR-08
Référence spec: ERR-172-08
GIVEN
- Campagne adversariale de cardinalité anormale des clés
WHEN
- Le seuil opérationnel de saturation est franchi
THEN
- Dégradation contrôlée activée + alerte exploitation émise
AND
- Service reste stable (pas de crash)
5. Tests d’invariants (non négociables)
| Invariant | Test(s) dédiés | Observable | Commentaire |
| INV-172-01 | TC-NOM-01, TC-NOM-04, TC-NOM-05, TC-NOM-06 | Décision identique pour même tuple/fenêtres; recalcul si dimension modifiée | Cohérence distribuée + multi-dimensions. |
| INV-172-02 | TC-NOM-02, TC-NOM-10, TC-NOM-11, TC-ERR-03 | Aucun effet métier sur refus | Atomicité quota-métier. |
| INV-172-03 | TC-NOM-07, TC-ERR-01, TC-ERR-02, TC-ERR-06 | Log structuré + métrique par refus | Exploitabilité refus obligatoire. |
| INV-172-04 | TC-NOM-03 | Profil sensible plus restrictif | Hiérarchie de protection. |
| INV-172-05 | TC-NOM-09, TC-NOM-10, TC-NOM-11, TC-ERR-06 | read fail-open; auth/costly fail-closed | Matrice Redis-down respectée. |
| INV-172-06 | TC-NOM-15, TC-NOM-16 | Uniquement transitions autorisées; terminaux sans sortie | Machine d’états normative. |
| INV-172-07 | TC-NOM-14 | Aucune exemption active | Whitelist/bypass interdit en V1. |
| INV-172-08 | TC-NOM-13, TC-ERR-05 | Configuration appliquée uniquement au boot | Stabilité opérationnelle. |
| INV-172-09 | TC-INV-09 | Formats runtime alignés sur §5.1 sans divergence | Vérification documentaire + schémas. |
| INV-172-10 | TC-NOM-17 | Décision RL indépendante des métadonnées crypto | Non-applicabilité crypto confirmée. |
TEST-ID: TC-INV-09
Référence spec: INV-172-09
GIVEN
- Canon contractuel (§5.1) figé
- Artefacts de validation exposés (schema config, validation API, contrats d’erreurs)
WHEN
- Une vérification de cohérence compare tous formats/enums/regex utilisés
THEN
- Chaque donnée de §5.1 a une unique définition effective
AND
- Toute divergence de format est classée non-conformité
6. Tests de non-régression
| Test ID | Objet | Observable | Commentaire |
| TC-NR-01 | Quota global inter-instances | Résultat TC-NOM-01 inchangé après montée de version | Protège CA-172-01. |
| TC-NR-02 | Contrat 429 + headers | Résultat TC-NOM-02 inchangé | Protège CA-172-02. |
| TC-NR-03 | Matrice fail-open/fail-closed | Résultats TC-NOM-09/10/11 inchangés | Protège CA-172-08. |
| TC-NR-04 | Blocage exécution métier sur refus | Sonde métier inchangée sur refus | Protège INV-172-02. |
| TC-NR-05 | Machine d’états terminale | Résultats TC-NOM-15/16 inchangés | Protège INV-172-06. |
| TC-NR-06 | Config figée au boot | Résultat TC-NOM-13 inchangé | Protège INV-172-08/CA-172-10. |
| TC-NR-07 | Observabilité des refus | Résultat TC-NOM-07 inchangé | Protège INV-172-03/CA-172-06. |
7. Tests négatifs et adversariaux
| Test ID | Entrée invalide / abus | Résultat attendu | Observable |
| TC-NEG-01 | route invalide (regex/longueur) | 400 ERR-172-03 | Réponse + log invalid_context |
| TC-NEG-02 | dimension hors enum (IP, api_key) | 400 ERR-172-03 | Réponse + log invalid_context |
| TC-NEG-03 | identifier invalide (caractère interdit / >128) | 400 ERR-172-03 | Réponse + log invalid_context |
| TC-NEG-04 | Contexte incomplet (tenant ou user absent) | 400 ERR-172-03 | Réponse + REJECTED_INVALID_CONTEXT |
| TC-NEG-05 | Chaîne proxy forgée / ambiguë | 400 ERR-172-07 | Réponse + trace résolution IP |
| TC-NEG-06 | Bruteforce distribué sur A/B même tuple | Refus global cohérent | Réponses + métriques cross-instance |
| TC-NEG-07 | Injection headers de bypass (X-Internal-*) | Aucun bypass, quota normal | Réponses + absence d’état bypass whitelist |
| TC-NEG-08 | Tempête de timeouts Redis | Dégradation par famille conforme | BYPASS_DEGRADED sur read, DENIED_DEGRADED sinon |
| TC-NEG-09 | Spray haute cardinalité clés | Dégradation contrôlée + alerte | Alarme ops + stabilité service |
8. Observabilité requise pour les tests
- État système : disponibilité Redis, instance backend, snapshot
C_boot, état décisionnel final par requête. - Réponse API : code HTTP,
X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After, code d’erreur ERR-172-*. - Journal d’audit : log structuré corrélable (
request_id, route, route_family, decision_state, cause, instance_id, horodatage UTC). - Événement signé / horodaté : horodatage obligatoire pour rejouabilité; signature cryptographique non spécifiée contractuellement.
- Export probatoire : jeu d’entrées de test, captures réponses, requêtes métriques, extraits logs, snapshot config, rapport de campagne perf.
9. Règles non testables
| Règle | Raison | Impact |
Valeurs exactes burst.max_requests | Non fournies au contrat (§5.2) | Bloquant |
Valeurs exactes burst.window_seconds | Non fournies au contrat (§5.2) | Bloquant |
Valeurs exactes sustained.max_requests | Non fournies au contrat (§5.2) | Bloquant |
Valeurs exactes sustained.window_seconds | Non fournies au contrat (§5.2) | Bloquant |
burst_window_ttl / sustained_window_ttl | SLA numériques absents (§5.3) | Majeur |
degraded_clearing_cycles | Valeur N absente (§5.3, §5.7) | Majeur |
Paramètres de réconciliation (cron, seuil orphelin) | Données absentes (§5.7) | Majeur |
| Code HTTP exact fail-closed | Q-172-03 non tranchée (503 ?), §6 ambigu | Majeur |
Liste exhaustive routes read fail-open | Non fournie (§10.2 Q-172-04) | Majeur |
Format final identifier (brut vs pseudonymisé) | Contradiction RG/clé Redis non arbitrée (§5.1, §10.2 Q-172-05) | Majeur |
| Politique santé/monitoring vs no-whitelist | Contradiction potentielle non résolue (Q-172-06) | Majeur |
| Granularité labels métriques et cardinalité max | Non définies (Q-172-09) | Majeur |
Convention publique stable ERR-172-* | Non figée (Q-172-10) | Mineur |
10. Verdict QA
- ⚠️ Testable partiellement (avec réserves listées)
Motif : les mécanismes principaux (cohérence distribuée, machine d’états, observabilité, stratégie Redis-down, non-régression) sont testables de manière déterministe; la conformité complète reste bloquée par les paramètres contractuels manquants/ambigus listés en section 9.