Chaos Métier¶
Le Chaos Métier injecte 16 anomalies fonctionnelles dans le parcours e-commerce. Contrairement au Chaos Performance qui dégrade l'infrastructure et au Chaos Fonctionnel qui provoque des exceptions JVM, ces anomalies touchent la logique applicative : prix, stock, commandes, livraison, fidélité, devise.
L'objectif pédagogique est aligné sur les certifications ISTQB Foundation et TMAP : apprendre à détecter et documenter des anomalies métier qui n'apparaissent pas dans les logs d'erreur classiques.
Service et endpoint¶
Classe : BusinessChaosService.java
Controller : BusinessChaosController.java
Endpoint admin : POST /api/admin/chaos/business body {"level": 0-4}
Endpoint public : GET /api/chaos/public/business/{status,logs,anomalies}
Niveaux¶
| Niveau | Label | Anomalies actives | Cumul |
|---|---|---|---|
| 0 | Désactivé | aucune | 0 |
| 1 | Junior | A1 – A3 | 3 |
| 2 | Confirmé | A1 – A7 | 7 |
| 3 | Expert | A1 – A11 | 11 |
| 4 | Master | A1 – A16 | 16 |
Règle d'or¶
Avec les bonnes données de test, une commande doit toujours pouvoir aboutir quel que soit le niveau de chaos métier actif. Aucune anomalie business ne lève d'exception bloquante — elles introduisent des écarts silencieux dans les calculs, les états ou les retours d'API.
Niveau 1 — Junior (A1 – A3)¶
A1 — TVA 19,6 % au lieu de 20 %¶
Méthode : getTvaRate(String userEmail) / applyTva(BigDecimal, String)
Compteur : chaos.business.a1.tva
Le taux de TVA appliqué au calcul TTC passe de 20 % à 19,6 % (l'ancien taux français pré-2014). L'écart est faible — 0,33 % sur le montant TTC — ce qui rend l'anomalie difficile à détecter par inspection visuelle. Sur une commande de 120 € TTC, le client paye 119,60 €.
Symptômes : montants TTC légèrement sous-évalués, écart visible
uniquement par recalcul manuel ou test d'assertion sur la TVA.
Logs : [BusinessChaos][A1] TVA incorrecte appliquee: 19.6% au lieu de 20%
A2 — Arrondi prix à l'entier inférieur¶
Méthode : applyPriceRounding(BigDecimal price, String productName, String userEmail)
Compteur : chaos.business.a2.arrondi
Tout produit dont le prix est strictement supérieur à 10 € est arrondi à
l'entier inférieur (RoundingMode.FLOOR, scale 0). Un produit affiché à
99,99 € est facturé 99,00 €. La perte par article peut atteindre 0,99 €,
soit ≈ 1 % de marge par produit.
Symptômes : écart prix catalogue vs prix panier, manque à gagner
silencieux. Logs : [BusinessChaos][A2] Arrondi prix plancher: 99.99
-> 99.00 EUR (perte: 0.99 EUR)
A3 — Stock non décrémenté¶
Méthode : shouldDecrementStock(String productName, int quantity, String userEmail)
Compteur : chaos.business.a3.stock
Après une commande validée, la méthode retourne false — le stock du
produit n'est jamais mis à jour en base. Le produit reste éternellement
affiché comme disponible, quelle que soit la quantité commandée.
Symptômes : stock affiché identique avant et après commande,
survente possible en théorie illimitée. Logs :
[BusinessChaos][A3] Stock NON decremente: '{produit}' x{quantite}
Niveau 2 — Confirmé (A4 – A7)¶
A4 — Email de confirmation : frais de port absents¶
Méthode : getEmailConfirmationAmount(BigDecimal totalWithShipping, BigDecimal shippingCost, String orderNumber, String userEmail)
Compteur : chaos.business.a4.email
L'email de confirmation envoyé au client affiche un montant excluant les frais de port — le client voit un total inférieur à ce qui sera réellement débité. Le montant en base reste correct.
Symptômes : écart entre l'email reçu et le débit bancaire, plaintes
clients sur le « surcoût caché ». Détection : test d'assertion
comparant le total du JSON commande avec le montant de l'email.
A5 — Double commande sur double-clic¶
Méthode : isOrderDuplicate(String userEmail, String cartHash)
Compteur : chaos.business.a5.doublon
La protection idempotence par (userEmail, cartHash) est désactivée — un
double-clic sur le bouton « Commander » crée deux commandes identiques. Au
niveau 0, la méthode retourne true si une commande identique a été
créée dans les 3 dernières secondes ; au niveau 2+, elle retourne toujours
false.
Symptômes : deux commandes consécutives à quelques millisecondes d'écart pour le même utilisateur avec le même hash de panier.
A6 — Code promo invalide accepté silencieusement¶
Méthode : validatePromoCode(String code, String userEmail)
Compteur : chaos.business.a6.promo
Un code promo inexistant (par exemple BLABLA42) est accepté avec une
réduction de 0 % et un message neutre « Code promo applique ». L'API
retourne valid: true au lieu de valid: false. Le client croit avoir
appliqué une promotion alors qu'aucune réduction n'est effective.
Symptômes : message « code appliqué » affiché côté frontend, mais total inchangé. Détection : assertion sur le delta de prix avant/après application du code.
A7 — Délai de livraison en jours calendaires¶
Méthode : getDeliveryDelay(String shippingMethod, String userEmail)
Compteur : chaos.business.a7.livraison
Le délai de livraison annoncé est calculé en jours calendaires au lieu
de jours ouvrés. Une commande passée vendredi avec « 3 jours » est
annoncée pour lundi (week-end inclus dans le décompte). Le workingDays
du retour passe de true à false.
Symptômes : dates de livraison promises qui tombent un dimanche ou un jour férié, retards systématiques côté logistique.
Niveau 3 — Expert (A8 – A11)¶
A8 — Race condition stock¶
Méthode : checkStockAvailability(Long productId, int stock, int requested, String userEmail)
Compteur : chaos.business.a8.race
La vérification du stock ne pose plus de verrou pessimiste sur la ligne produit. Sous charge concurrente, deux commandes simultanées peuvent passer toutes les deux la vérification alors que le stock ne suffit que pour une — c'est la survente. À faible charge, l'anomalie est invisible.
Logs : [BusinessChaos][A8] Stock verifie SANS VERROU — productId={id}
stock={s} demande={q} (sévérité ERROR).
Détection : test de charge JMeter avec 50 threads en parallèle sur le
même produit dont le stock = 1.
A9 — Injection de log (log poisoning)¶
Méthode : processInputField(String value, String fieldName, String userEmail)
Compteur : chaos.business.a9.inject
Au niveau < 3, les caractères de contrôle (\n, \r, \t) sont
échappés. Au niveau 3+, la valeur est insérée brute dans les logs, ce qui
permet à un attaquant d'injecter de faux logs (log poisoning) ou des
expressions JNDI (pattern ${…}) — à l'origine de Log4Shell.
Symptômes : logs Loki contenant des sauts de ligne fabriqués par le client, fausses entrées qui semblent provenir d'une autre source.
A10 — Total historique commandes faux¶
Méthode : computeOrderHistoryTotal(List<BigDecimal> orders, String userEmail)
Compteur : chaos.business.a10.total
Le total cumulé de l'historique des commandes affiché à l'utilisateur est
calculé avec un arrondi par ligne (FLOOR scale 0) au lieu d'une
somme exacte. Sur 50 commandes, l'écart peut atteindre plusieurs dizaines
d'euros — toujours en défaveur du client.
Logs : [BusinessChaos][A10] Total historique faux: affiche={fake}
reel={real} ecart={delta}. Affichage côté UI ≠ valeur DB.
A11 — Token session non invalidé après logout¶
Méthode : getTokenGracePeriodMs(String userEmail)
Compteur : chaos.business.a11.token
Après un logout, l'ancien token de session reste valide pendant 30
secondes — la méthode retourne 30_000L au lieu de 0L. Une attaque
replay est possible : un attaquant qui capture le token avant le
logout peut continuer à appeler les APIs au nom de l'utilisateur pendant
30 s.
Détection : script de test qui se logge, capture le token, déclenche
le logout, puis ré-utilise le token sur /api/auth/me — devrait retourner
401, mais retourne 200 au niveau 3+.
Niveau 4 — Master (A12 – A16)¶
A12 — Remise fidélité silencieuse¶
Méthode : applyLoyaltyDiscount(BigDecimal total, long orderCount, String userEmail)
Compteur : chaos.business.a12.loyalty
Au niveau < 4, un client avec plus de 5 commandes reçoit automatiquement 5 % de réduction. Au niveau 4, la réduction n'est plus appliquée mais aucune erreur n'est retournée — le client perd silencieusement sa fidélité.
Logs : [BusinessChaos][A12] Remise fidélité non accordée: {n}
commandes > 5. Détection : test d'assertion comparant le total
calculé au total attendu pour un client fidèle.
A13 — Devise d'affichage incorrecte¶
Méthode : getCurrency(String context)
Compteur : chaos.business.a13.currency
Le champ currency retourné par les APIs produit passe de EUR à USD
au niveau 4, sans changer les prix. Un produit affiché 99,99 € est
soudainement étiqueté 99.99 USD — soit ≈ 10 % d'écart de valeur réelle
selon le taux de change.
Symptômes : devise affichée incohérente avec la zone géographique du client, plaintes immédiates sur les forums clients.
A14 — Frais de port doublés¶
Méthode : applyShippingCost(BigDecimal shippingCost, String orderNumber, String userEmail)
Compteur : chaos.business.a14.shipping
Les frais de port sont multipliés par 2 silencieusement avant intégration au total. Un produit avec 5 € de frais de port se retrouve facturé avec 10 € de port — surcharge invisible côté client jusqu'à réception du débit.
Logs : [BusinessChaos][A14] Frais de port doublés: 5.00 -> 10.00.
A15 — Corruption silencieuse historique (× 1.1)¶
Méthode : applyHistoryCorruption(BigDecimal amount, Object orderId, String userEmail)
Compteur : chaos.business.a15.history
L'API GET /api/orders retourne les totaux multipliés par 1.1 par
rapport aux valeurs réellement stockées en base. La modification est en
lecture seule — la base reste cohérente, mais l'utilisateur voit une
historique falsifiée. Cumulée avec A10, l'historique devient totalement
incohérent.
Détection : comparaison ligne à ligne entre les totaux affichés par
l'API et le résultat d'un SELECT total FROM orders direct.
A16 — Annulation sans recréditage stock¶
Méthode : shouldRestockOnCancel(String orderNumber, String userEmail)
Compteur : chaos.business.a16.cancel
Quand une commande est annulée (DELETE /api/orders/{id}), le stock
n'est pas recrédité. La méthode retourne false. Cumulée avec A3
(stock non décrémenté), elle produit un état incohérent dans les deux
sens : le stock dérive sans corrélation avec l'activité réelle.
Logs : [BusinessChaos][A16] Annulation commande SANS remboursement
stock — commande={orderNumber} (sévérité ERROR).
Anomalies de validation cumulées¶
BusinessChaosService expose en plus des flags de validation qui
modifient le comportement de ValidationService selon le niveau actif :
| Méthode flag | Niveau | Effet |
|---|---|---|
isPostalCodeLenient |
= 1 | Code postal accepté hors format |
isPostalCodeInvertedFR |
= 2 | Inverse les chiffres du code postal FR |
isPostalCodeFR4Digits |
≥ 3 | Accepte 4 chiffres au lieu de 5 |
isCvvLenient |
= 1 | CVV moins strict |
isCvv4Digits |
≥ 3 | Accepte 4 chiffres CVV |
isPhoneLenient |
= 1 | Téléphone moins strict |
isPhoneRejectMobileFR |
≥ 3 | Rejette les 06/07 |
isNameRejectAccents |
≥ 2 | Rejette noms accentués |
isCardRejectFarExpiry |
≥ 2 | Refuse les cartes valides très loin dans le temps |
isBirthDateAcceptFuture |
≥ 3 | Accepte des dates de naissance futures |
Ces flags sont consommés par ValidationService et UserController —
ils ne sont pas comptés dans les compteurs A1–A16 mais relèvent du
même chaos métier.
Injection SQL via champs profil¶
BusinessChaosService expose aussi deux méthodes d'injection SQL ciblées
sur les champs profil utilisateur — utilisées par UserController pour
provoquer des DataTruncation et des erreurs JDBC observables :
| Méthode | Niveau | Comportement |
|---|---|---|
getInjectedPostalCode |
≥ 2 | Injecte 999999 (6 ch.) ou 9999999999 (10 ch.) > VARCHAR(5) |
getInjectedCountry |
≥ 3 | Injecte FRANCE (6 ch.) > VARCHAR(2) |
Ces injections déclenchent des SQLException côté JDBC qui sont loggées
via logSqlError() et apparaissent dans le monitoring sous le tag
SQL_ERROR.
API — endpoints publics¶
| Endpoint | Description |
|---|---|
GET /api/chaos/public/business/status |
Niveau courant + 16 compteurs |
GET /api/chaos/public/business/logs |
Activity log (200 dernières entrées) |
GET /api/chaos/public/business/anomalies?level=N |
Catalogue pédagogique pour le niveau N |
API — endpoints admin¶
curl -X POST https://perfshop-api.perfshop.io/api/admin/chaos/business \
-H "X-Admin-Token: $TOKEN" -H "Content-Type: application/json" \
-d '{"level": 4}'
curl -X POST https://perfshop-api.perfshop.io/api/admin/chaos/business/reset \
-H "X-Admin-Token: $TOKEN"
Activation par l'étudiant¶
POST /api/chaos/student/business body {"level": N} — exige le mode
étudiant et une licence valide pour level > 0. Sans licence, retourne
HTTP 402 LICENSE_REQUIRED.