feat: simulate timeouts and truncated responses - #31
Merged
Conversation
Blocking a request answers it, cleanly and at once. These do the opposite: they make a request fail the way a network fails, which is usually the least exercised path in an application under test. Simulate Timeout holds a request and then drops it, without contacting the server. That is what a client sees when a service accepts a connection and then says nothing, and it is a different test from a service that answers with an error - an application that handles a 504 correctly may still wait forever when nothing arrives at all. It is dropped after the hold so a client with a longer timeout than the hold does not wait indefinitely. Simulate Truncated Response cuts a response short while it still claims its full length. The mismatch is the fault: the body is replaced through raw_content because set_content would correct content-length and leave a merely shorter, perfectly valid response. A compressed body is cut in its compressed form, so what arrives is broken rather than a valid shorter document, which is the more realistic failure. A response with no body, and one already shorter than the cut, are left alone and say so rather than appearing to have done something. Connection reset is not a new keyword: Block Requests with mode=RESET already drops a connection, and a second way to do one thing is not worth adding to a surface about to be frozen. The failure simulation documentation points there. Bandwidth throttling stays unsupported, and the documentation says why rather than leaving it looking forgotten: mitmproxy hands a response body to a synchronous callback with no way to wait between chunks, so the only implementable version would delay the whole body and deliver it in one piece, which is Add Response Delay under a name promising more than it does. The request hook is now asynchronous, because a rule can wait in it. mitmproxy awaits an addon hook that returns a coroutine, and a held request does not stall the others - covered by a test that holds one request and requires another to pass through meanwhile, and by an acceptance case that does the same through a real proxy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MobyNL
force-pushed
the
feat/failure-simulation
branch
from
August 18, 2026 19:09
efaac9f to
d946dfc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR 9 of the 1.0.0 sequence (workstream #5). Branches from
main, which now includes recording.Why
Blocking a request answers it, cleanly and at once. These do the opposite: they make a request fail the way a network fails, which is usually the least exercised path in an application under test.
What it adds
Simulate Timeoutholds a request and then drops it, without contacting the server. That is what a client sees when a service accepts a connection and then says nothing — a different test from a service that answers with an error, since an application that handles a 504 correctly may still wait forever when nothing arrives at all. It is dropped after the hold so a client whose own timeout is longer does not wait indefinitely.Simulate Truncated Responsecuts a response short while it still claims its full length. The mismatch is the fault, which is why the body is replaced throughraw_content:set_contentwould helpfully correctcontent-lengthand leave a merely shorter, perfectly valid response. A test asserts the stale header directly, so a mitmproxy version that started normalising it would be reported here rather than silently disabling the feature.A compressed body is cut in its compressed form, so what arrives is broken rather than a valid shorter document — the more realistic failure. A response with no body, and one already shorter than the cut, are left alone and say so in the log rather than appearing to have done something.
Two deliberate omissions
No
Simulate Connection Reset.Block Requests ... mode=RESETalready drops a connection, and a second way to do one thing is not worth adding to a surface about to be frozen. The docs point there instead.Bandwidth throttling stays unsupported, and the documentation now says why rather than leaving it looking forgotten: mitmproxy hands a response body to a synchronous callback with no way to wait between chunks, so the only implementable version would delay the whole body and deliver it in one piece — which is
Add Response Delayunder a name that promises more than it delivers.One structural change
The request hook is now asynchronous, because a rule can wait in it. mitmproxy awaits an addon hook that returns a coroutine, and a held request does not stall the others — covered both by a unit test that holds one request while another passes through, and by an acceptance case doing the same through a real proxy.
Tests
19 unit tests in
test_failures.py. Timing is asserted with a patchedsleep, so nothing waits for real. Acceptance cases assert only that a request failed, never on a particular error — how a client reports a hang or a short body is decided by its HTTP stack, not by us.Verification
ruff check .,robocop check atestandmypyclean.pytest: 219 passed, 98.03% coverage. Acceptance: 43/43. libdoc reports 29 keywords.🤖 Generated with Claude Code