Business Chaos¶
Business Chaos injects 16 functional anomalies into the e-commerce journey. Unlike Performance Chaos which degrades infrastructure and Functional Chaos which triggers JVM exceptions, these anomalies touch the application logic: prices, stock, orders, shipping, loyalty, currency.
The pedagogical goal is aligned with the ISTQB Foundation and TMAP certifications: learning to detect and document business anomalies that do not show up in classic error logs.
Service and endpoint¶
Class: BusinessChaosService.java
Controller: BusinessChaosController.java
Admin endpoint: POST /api/admin/chaos/business body {"level": 0-4}
Public endpoint: GET /api/chaos/public/business/{status,logs,anomalies}
Levels¶
| Level | Label | Active anomalies | Cumulative |
|---|---|---|---|
| 0 | Disabled | none | 0 |
| 1 | Junior | A1 – A3 | 3 |
| 2 | Intermediate | A1 – A7 | 7 |
| 3 | Expert | A1 – A11 | 11 |
| 4 | Master | A1 – A16 | 16 |
Golden rule¶
With the right test data, an order must always be able to complete whatever the active business chaos level. No business anomaly raises a blocking exception — they introduce silent gaps in calculations, states, or API returns.
Level 1 — Junior (A1 – A3)¶
A1 — VAT 19.6 % instead of 20 %¶
Method: getTvaRate(String userEmail) / applyTva(BigDecimal, String)
Counter: chaos.business.a1.tva
The VAT rate applied to the VAT-inclusive calculation drops from 20 % to 19.6 % (the old pre-2014 French rate). The gap is small — 0.33 % on the VAT-inclusive amount — which makes the anomaly hard to spot by visual inspection. On a €120 VAT-inclusive order, the customer pays €119.60.
Symptoms: VAT-inclusive amounts slightly undervalued, gap visible only through manual recalculation or a VAT assertion test.
Logs: [BusinessChaos][A1] Incorrect VAT applied: 19.6% instead of 20%
A2 — Price rounded down to the lower integer¶
Method: applyPriceRounding(BigDecimal price, String productName, String userEmail)
Counter: chaos.business.a2.arrondi
Every product whose price is strictly above €10 is rounded down to the lower integer (RoundingMode.FLOOR, scale 0). A product displayed at €99.99 is billed at €99.00. The loss per item can reach €0.99, i.e. ≈ 1 % margin loss per product.
Symptoms: mismatch between catalog price and cart price, silent revenue loss. Logs: [BusinessChaos][A2] Floor price rounding: 99.99 -> 99.00 EUR (loss: 0.99 EUR)
A3 — Stock not decremented¶
Method: shouldDecrementStock(String productName, int quantity, String userEmail)
Counter: chaos.business.a3.stock
After a validated order, the method returns false — the product stock is never updated in the database. The product stays permanently displayed as available, regardless of the ordered quantity.
Symptoms: stock displayed identically before and after ordering, theoretically unlimited overselling. Logs: [BusinessChaos][A3] Stock NOT decremented: '{product}' x{quantity}
Level 2 — Intermediate (A4 – A7)¶
A4 — Confirmation email: missing shipping fees¶
Method: getEmailConfirmationAmount(BigDecimal totalWithShipping, BigDecimal shippingCost, String orderNumber, String userEmail)
Counter: chaos.business.a4.email
The confirmation email sent to the customer shows an amount excluding shipping fees — the customer sees a total lower than what will actually be charged. The amount in the database remains correct.
Symptoms: gap between the received email and the bank charge, customer complaints about a "hidden extra charge". Detection: assertion test comparing the total from the order JSON with the amount in the email.
A5 — Double order on double-click¶
Method: isOrderDuplicate(String userEmail, String cartHash)
Counter: chaos.business.a5.doublon
The idempotency protection by (userEmail, cartHash) is disabled — a double-click on the "Order" button creates two identical orders. At level 0, the method returns true if an identical order was created in the last 3 seconds; at level 2+, it always returns false.
Symptoms: two consecutive orders a few milliseconds apart for the same user with the same cart hash.
A6 — Invalid promo code silently accepted¶
Method: validatePromoCode(String code, String userEmail)
Counter: chaos.business.a6.promo
A non-existent promo code (e.g. BLABLA42) is accepted with a 0 % discount and a neutral message "Promo code applied". The API returns valid: true instead of valid: false. The customer believes a promotion has been applied while no discount is effective.
Symptoms: "code applied" message shown on the frontend but unchanged total. Detection: assertion on the price delta before/after applying the code.
A7 — Delivery delay in calendar days¶
Method: getDeliveryDelay(String shippingMethod, String userEmail)
Counter: chaos.business.a7.livraison
The announced delivery delay is computed in calendar days instead of business days. An order placed on Friday with "3 days" is announced for Monday (weekend included in the count). The workingDays flag in the response switches from true to false.
Symptoms: promised delivery dates falling on a Sunday or a public holiday, systematic logistical delays.
Level 3 — Expert (A8 – A11)¶
A8 — Stock race condition¶
Method: checkStockAvailability(Long productId, int stock, int requested, String userEmail)
Counter: chaos.business.a8.race
Stock verification no longer places a pessimistic lock on the product row. Under concurrent load, two simultaneous orders can both pass the check when the stock only covers one — this is overselling. Under low load, the anomaly is invisible.
Logs: [BusinessChaos][A8] Stock checked WITHOUT LOCK — productId={id} stock={s} requested={q} (severity ERROR).
Detection: JMeter load test with 50 parallel threads on the same product with stock = 1.
A9 — Log injection (log poisoning)¶
Method: processInputField(String value, String fieldName, String userEmail)
Counter: chaos.business.a9.inject
Below level 3, control characters (\n, \r, \t) are escaped. At level 3+, the value is inserted raw into the logs, which lets an attacker inject fake logs (log poisoning) or JNDI expressions (pattern ${…}) — the root cause of Log4Shell.
Symptoms: Loki logs containing line breaks fabricated by the client, fake entries that appear to come from another source.
A10 — Wrong order history total¶
Method: computeOrderHistoryTotal(List<BigDecimal> orders, String userEmail)
Counter: chaos.business.a10.total
The cumulative total of the order history displayed to the user is computed with per-line rounding (FLOOR scale 0) instead of an exact sum. Over 50 orders, the gap can reach several tens of euros — always against the customer.
Logs: [BusinessChaos][A10] Wrong history total: shown={fake} actual={real} delta={delta}. UI display ≠ DB value.
A11 — Session token not invalidated after logout¶
Method: getTokenGracePeriodMs(String userEmail)
Counter: chaos.business.a11.token
After a logout, the old session token remains valid for 30 seconds — the method returns 30_000L instead of 0L. A replay attack is possible: an attacker who captures the token before logout can keep calling the APIs on behalf of the user for 30 s.
Detection: a test script that logs in, captures the token, triggers the logout, then reuses the token on /api/auth/me — should return 401, but returns 200 at level 3+.
Level 4 — Master (A12 – A16)¶
A12 — Silent loyalty discount¶
Method: applyLoyaltyDiscount(BigDecimal total, long orderCount, String userEmail)
Counter: chaos.business.a12.loyalty
Below level 4, a customer with more than 5 orders automatically gets a 5 % discount. At level 4, the discount is no longer applied but no error is returned — the customer silently loses their loyalty benefit.
Logs: [BusinessChaos][A12] Loyalty discount not granted: {n} orders > 5. Detection: assertion test comparing the computed total to the expected total for a loyal customer.
A13 — Wrong display currency¶
Method: getCurrency(String context)
Counter: chaos.business.a13.currency
The currency field returned by the product APIs switches from EUR to USD at level 4, without changing the prices. A product displayed at €99.99 is suddenly labeled 99.99 USD — i.e. ≈ 10 % of real value gap depending on the exchange rate.
Symptoms: currency shown inconsistent with the customer's geographic zone, immediate complaints on customer forums.
A14 — Doubled shipping fees¶
Method: applyShippingCost(BigDecimal shippingCost, String orderNumber, String userEmail)
Counter: chaos.business.a14.shipping
Shipping fees are silently multiplied by 2 before being added to the total. A product with €5 shipping fees ends up billed with €10 shipping — invisible overcharge on the customer side until the bank charge.
Logs: [BusinessChaos][A14] Shipping fees doubled: 5.00 -> 10.00.
A15 — Silent history corruption (× 1.1)¶
Method: applyHistoryCorruption(BigDecimal amount, Object orderId, String userEmail)
Counter: chaos.business.a15.history
The GET /api/orders API returns the totals multiplied by 1.1 relative to the values actually stored in the database. The modification is read-only — the database stays consistent, but the user sees a falsified history. Cumulated with A10, the history becomes entirely inconsistent.
Detection: line-by-line comparison between the totals displayed by the API and the result of a direct SELECT total FROM orders.
A16 — Cancellation without stock restock¶
Method: shouldRestockOnCancel(String orderNumber, String userEmail)
Counter: chaos.business.a16.cancel
When an order is canceled (DELETE /api/orders/{id}), the stock is not credited back. The method returns false. Cumulated with A3 (stock not decremented), it produces an inconsistent state in both directions: stock drifts without any correlation with real activity.
Logs: [BusinessChaos][A16] Order cancellation WITHOUT stock refund — order={orderNumber} (severity ERROR).
Cumulative validation anomalies¶
BusinessChaosService additionally exposes validation flags that modify the behavior of ValidationService depending on the active level:
| Flag method | Level | Effect |
|---|---|---|
isPostalCodeLenient |
= 1 | Postal code accepted out of format |
isPostalCodeInvertedFR |
= 2 | Inverts the digits of a French postal code |
isPostalCodeFR4Digits |
≥ 3 | Accepts 4 digits instead of 5 |
isCvvLenient |
= 1 | Less strict CVV |
isCvv4Digits |
≥ 3 | Accepts 4-digit CVV |
isPhoneLenient |
= 1 | Less strict phone |
isPhoneRejectMobileFR |
≥ 3 | Rejects 06/07 mobile numbers |
isNameRejectAccents |
≥ 2 | Rejects accented names |
isCardRejectFarExpiry |
≥ 2 | Rejects cards valid far in the future |
isBirthDateAcceptFuture |
≥ 3 | Accepts future birth dates |
These flags are consumed by ValidationService and UserController — they are not counted in the A1–A16 counters but belong to the same business chaos.
SQL injection through profile fields¶
BusinessChaosService also exposes two SQL injection methods targeted at user profile fields — used by UserController to trigger observable DataTruncation and JDBC errors:
| Method | Level | Behavior |
|---|---|---|
getInjectedPostalCode |
≥ 2 | Injects 999999 (6 chars) or 9999999999 (10 chars) > VARCHAR(5) |
getInjectedCountry |
≥ 3 | Injects FRANCE (6 chars) > VARCHAR(2) |
These injections trigger JDBC SQLExceptions that are logged through logSqlError() and appear in monitoring under the SQL_ERROR tag.
API — public endpoints¶
| Endpoint | Description |
|---|---|
GET /api/chaos/public/business/status |
Current level + 16 counters |
GET /api/chaos/public/business/logs |
Activity log (last 200 entries) |
GET /api/chaos/public/business/anomalies?level=N |
Pedagogical catalog for level N |
API — admin endpoints¶
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"
Student activation¶
POST /api/chaos/student/business body {"level": N} — requires student mode and a valid license for level > 0. Without a license, it returns HTTP 402 LICENSE_REQUIRED.