Skip to content

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.