Skip to content

feat: simulate timeouts and truncated responses - #31

Merged
MobyNL merged 1 commit into
mainfrom
feat/failure-simulation
Aug 18, 2026
Merged

feat: simulate timeouts and truncated responses#31
MobyNL merged 1 commit into
mainfrom
feat/failure-simulation

Conversation

@MobyNL

@MobyNL MobyNL commented Aug 18, 2026

Copy link
Copy Markdown
Owner

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 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 — 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 Response cuts a response short while it still claims its full length. The mismatch is the fault, which is why the body is replaced through raw_content: set_content would helpfully correct content-length and 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=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 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 Delay under 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 patched sleep, 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 atest and mypy clean. pytest: 219 passed, 98.03% coverage. Acceptance: 43/43. libdoc reports 29 keywords.

🤖 Generated with Claude Code

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
MobyNL force-pushed the feat/failure-simulation branch from efaac9f to d946dfc Compare August 18, 2026 19:09
@MobyNL
MobyNL merged commit 32d87aa into main Aug 18, 2026
15 checks passed
@MobyNL
MobyNL deleted the feat/failure-simulation branch August 18, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant