Skip to content

Commit 1e87902

Browse files
Check server errors when a request context exits
1 parent f44b8b7 commit 1e87902

4 files changed

Lines changed: 83 additions & 0 deletions

File tree

‎doc/howto.rst‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,24 @@ these.
237237
.. literalinclude :: ../tests/examples/test_howto_check.py
238238
:language: python
239239
240+
To check automatically when a block finishes, use
241+
:py:meth:`pytest_httpserver.HTTPServer.with_check`:
242+
243+
.. code-block:: python
244+
245+
httpserver.expect_ordered_request("/first").respond_with_data(status=204)
246+
httpserver.expect_ordered_request("/second").respond_with_data(status=204)
247+
with httpserver.with_check():
248+
requests.get(httpserver.url_for("/first"))
249+
requests.get(httpserver.url_for("/second"))
250+
251+
This checks unexpected requests even after all ordered handlers have been used.
252+
It also propagates errors raised by response handlers. Checks run only when the
253+
block exits normally; an exception raised in the block propagates unchanged.
254+
The context manager does not wait for background clients, so complete their
255+
work before leaving the block. Use ``wait()`` when you need to wait for handler
256+
completion.
257+
240258
.. note::
241259
The scope of the errors checked by the ``check()`` method may
242260
change in the future - it is added to check all possible errors happened in

‎pytest_httpserver/httpserver.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,20 @@ def check(self) -> None:
839839
self.check_assertions()
840840
self.check_handler_errors()
841841

842+
@contextmanager
843+
def with_check(self) -> Generator[Self, None, None]:
844+
"""
845+
Check server-side assertions and handler errors when the block exits normally.
846+
847+
Calls :py:meth:`check` after the body completes. Exceptions raised in the body
848+
propagate without running the checks, so a server-side error cannot replace them.
849+
This context manager does not wait for requests or stop the server.
850+
851+
:return: the server instance
852+
"""
853+
yield self
854+
self.check()
855+
842856
def check_assertions(self) -> None:
843857
"""
844858
Raise AssertionError when at least one assertion added
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
features:
2+
- |
3+
Add ``HTTPServer.with_check()``, a context manager that checks server-side
4+
assertions and handler errors when its body completes normally. It detects
5+
unexpected requests received after the expected ordered requests without
6+
changing ``wait()`` semantics. Exceptions raised in the body propagate
7+
unchanged.

‎tests/test_handler_errors.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,47 @@ def handler3(_):
9090

9191
with pytest.raises(ValueError): # noqa: PT011
9292
httpserver.check()
93+
94+
95+
def test_with_check_success(httpserver: HTTPServer):
96+
httpserver.expect_ordered_request("/first").respond_with_data(status=204)
97+
httpserver.expect_ordered_request("/second").respond_with_data(status=204)
98+
99+
with httpserver.with_check() as server:
100+
assert server is httpserver
101+
assert requests.get(server.url_for("/first")).status_code == 204
102+
assert requests.get(server.url_for("/second")).status_code == 204
103+
104+
105+
def test_with_check_unexpected_request_after_expected_requests(httpserver: HTTPServer):
106+
httpserver.expect_ordered_request("/first").respond_with_data(status=204)
107+
httpserver.expect_ordered_request("/second").respond_with_data(status=204)
108+
109+
with pytest.raises(AssertionError, match="No handler found for request"), httpserver.with_check():
110+
assert requests.get(httpserver.url_for("/first")).status_code == 204
111+
assert requests.get(httpserver.url_for("/second")).status_code == 204
112+
assert requests.get(httpserver.url_for("/unexpected")).status_code == 500
113+
114+
115+
def test_with_check_propagates_handler_error(httpserver: HTTPServer):
116+
handler_error = ValueError("handler failed")
117+
118+
def handler(_) -> werkzeug.Response:
119+
raise handler_error
120+
121+
httpserver.expect_request("/error").respond_with_handler(handler)
122+
123+
with pytest.raises(ValueError, match="handler failed") as error, httpserver.with_check():
124+
assert requests.get(httpserver.url_for("/error")).status_code == 500
125+
126+
assert error.value is handler_error
127+
128+
129+
def test_with_check_preserves_body_exception(httpserver: HTTPServer):
130+
body_error = RuntimeError("client failed")
131+
132+
with pytest.raises(RuntimeError, match="client failed") as error, httpserver.with_check():
133+
assert requests.get(httpserver.url_for("/unexpected")).status_code == 500
134+
raise body_error
135+
136+
assert error.value is body_error

0 commit comments

Comments
 (0)