Bug Class 6: Bad Responses, Not Outages
The dependency didn’t fail - it answered. Wrong. A field changed type, an error arrived as a 200 with an HTML body, a payload came back truncated. Your parser meets input it has never seen, and the blast radius is yours, not theirs.
The incident
A partner API ships a change: a numeric field becomes a string, and
their load balancer starts serving errors as 200 OK with an HTML
error page in the body. Your JSON parser panics. The panic takes the
handler, the handler takes the goroutine pool, and a service that was
“up” the whole time - theirs - takes down yours.
Why your tests miss it
Your mocks are generated from the contract, so they are always well-formed - that’s the point of contracts, and exactly why they can’t test this. Reality is not contract-shaped: proxies inject HTML, CDNs truncate, partners deploy on Friday.
The spec that closes it
Response rewriting - the dependency (real or mock) answers normally, then the proxy corrupts the reply on the way back:
partner_garbage = fault_assumption("partner_garbage",
target = partner.main,
rules = [response(path = "/quote", status = 200,
body = '<html>502 Bad Gateway</html>')],
)
partner_wrong_type = fault_assumption("partner_wrong_type",
target = partner.main,
rules = [response(path = "/quote", status = 200,
body = '{"price": "not-a-number"}')],
)
fault_matrix(
scenarios = [checkout],
faults = [partner_garbage, partner_wrong_type],
overrides = {
(checkout, partner_garbage): lambda r: assert_eq(r.status, 502,
"a bad upstream reply is an upstream error - and nothing crashes"),
},
)
What to assert
- No panics, ever: malformed input at the boundary is a handled error, not a crash - check the service is still healthy after the bad response.
- The error is attributed correctly: a 502/424 pointing upstream, not a 500 that sends your own on-call hunting through your own code.
- Fallbacks engage where they exist: cached value, default, degraded answer.
Which level
Protocol only - this class is response rewriting. Works identically
on real dependencies and mock services: the
response() rule rewrites whatever the upstream answered. See
Choosing Fault Levels.
Learn the pieces
- Chapter 7: HTTP Protocol Faults -
response()rules - Chapter 17: Mock Services
- Chapter 19: OpenAPI Mocks - contract-generated stubs, then corrupt them