How a missing negative test caused a production incident
The feature did exactly what it was tested to do. The incident came from the input nobody tested — a negative number where only positive ones were expected.
This is a case study, details blurred, of an incident that came not from a broken feature but from an absent test. Every positive case worked perfectly. The hole was in the negative space — the inputs the team never thought to try because the UI would "never send them."
Context
An inventory adjustment feature in a warehouse tool. Staff entered a quantity to add or remove stock; the endpoint updated the count. The form had a number input, the happy-path tests covered adding and removing reasonable quantities, and it shipped.
Symptoms
A week later, a product's stock count was wildly wrong — showing tens of thousands of units that didn't exist — and downstream, the storefront started overselling an item that was actually out of stock. Customer orders for phantom inventory. A real incident, with refunds and apologies attached.
Investigation
The audit log told the story fast: a "remove stock" adjustment had been submitted with a negative quantity. Removing −5,000 units is, arithmetically, adding 5,000. The endpoint did exactly that — subtracted a negative, inflated the count, and the storefront trusted it. How did a negative reach a "remove" action? The web form blocked it (min="0"), but the adjustment had come through a bulk-import path that hit the same endpoint without that client-side guard. The server never validated the sign.
Root cause
A validation gap: the endpoint accepted any number and never checked that a quantity was positive (or that the resulting stock was ≥ 0). The web UI's min="0" created a false sense of safety — the rule lived only in the browser, not on the server, so any path that bypassed the UI (import, API, a retry) bypassed the rule. This is the hidden-button-is-not-access-control pattern applied to validation: the UI is not the enforcement layer.
What the tests missed
The tests were entirely positive — valid quantities, expected ranges. There was no negative test: no "what if the quantity is negative", "zero", "absurdly large", "a string", or "the resulting stock would go below zero". And every test went through the UI, where the client guard hid the gap. Nobody tested the endpoint directly with the input the UI would "never" allow — which is precisely the input that caused the incident, because another code path did allow it. It's the same lesson as testing the API before/around the UI: the UI sends a narrow, polite set of inputs; the endpoint must defend against the rest.
The reusable lesson
For every input, test the negative space deliberately: invalid values, out-of-range, wrong type, and the values that violate a business rule (negative quantity, total below zero, end-date before start-date). Test the endpoint, not just the UI that politely constrains it — because imports, integrations, and retries don't. And assert the resulting-state invariant (stock ≥ 0), not just that the operation "worked." Negative testing isn't pessimism; it's the part of test design that catches incidents.
Negative-space testing
- For each input, test invalid/out-of-range/wrong-type/rule-violating values, not just valid ones
- Test the endpoint directly — bypass the UI's client-side guards
- Assert resulting-state invariants (stock ≥ 0, balance ≥ 0), not just "it worked"
- Remember other paths (import, API, integrations, retries) skip the UI's validation
- Validation must live server-side; a
min="0"in the browser is not enforcement - Add the boundary and absurd cases: zero, negative, huge, empty, the wrong sign
// RELATED QA.CODES RESOURCES
Checklist
Common Bug
// related
The checkout bug that passed every happy-path test
Every checkout test was green, but combining two discounts and a gift card drove the total negative — and issued credit. A case study in testing invariants, not just features.
The bug that only happened after daylight saving time changed
A case study: a scheduling bug that stayed invisible until the clocks changed — and the test scenarios that would have caught it.