Skip to content

Commit b078e90

Browse files
committed
httpserver: add methods to query log
New methods added to query log for the given matcher. Fixes csernazs#293
1 parent 97a92d9 commit b078e90

5 files changed

Lines changed: 150 additions & 0 deletions

File tree

pytest_httpserver/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"WaitingSettings",
1212
"HeaderValueMatcher",
1313
"RequestHandler",
14+
"RequestMatcher",
1415
"URIPattern",
1516
"URI_DEFAULT",
1617
"METHOD_ALL",
@@ -28,5 +29,6 @@
2829
from .httpserver import HTTPServerError
2930
from .httpserver import NoHandlerError
3031
from .httpserver import RequestHandler
32+
from .httpserver import RequestMatcher
3133
from .httpserver import URIPattern
3234
from .httpserver import WaitingSettings

pytest_httpserver/httpserver.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,3 +1333,73 @@ def test_wait(httpserver):
13331333
)
13341334
if self._waiting_settings.raise_assertions and not waiting.result:
13351335
self.check_assertions()
1336+
1337+
def iter_matching_requests(self, matcher: RequestMatcher) -> Iterable[tuple[Request, Response]]:
1338+
"""
1339+
Queries log for matching requests.
1340+
1341+
1342+
:param matcher: the matcher object to match requests
1343+
:return: an iterator with request-response pair from the log
1344+
"""
1345+
1346+
for request, response in self.log:
1347+
if matcher.match(request):
1348+
yield (request, response)
1349+
1350+
def get_matching_requests_count(self, matcher: RequestMatcher) -> int:
1351+
"""
1352+
Queries the log for matching requests, returning the number of log
1353+
entries matching for the specified matcher.
1354+
1355+
:param matcher: the matcher object to match requests
1356+
:return: the number of log entries matching
1357+
"""
1358+
return len(list(self.iter_matching_requests(matcher)))
1359+
1360+
def assert_request_made(self, matcher: RequestMatcher, *, count: int = 1):
1361+
"""
1362+
Check the amount of log entries matching for the matcher specified. By
1363+
default it verifies that exactly one request matching for the matcher
1364+
specified. The expected count can be customized with the count kwarg
1365+
(including zero, which asserts that no requests made for the given
1366+
matcher).
1367+
1368+
:param matcher: the matcher object to match requests
1369+
:param count: the expected number of matches in the log
1370+
:return: ``None`` if the assert succeeded, raises
1371+
:py:class:`AssertionError` if not.
1372+
"""
1373+
1374+
matching_count = self.get_matching_requests_count(matcher)
1375+
if matching_count != count:
1376+
similar_requests: list[Request] = []
1377+
for request, _ in self.log:
1378+
if request.path == matcher.uri:
1379+
similar_requests.append(request)
1380+
1381+
assert_msg_lines = [
1382+
f"Matching request found {matching_count} times but expected {count} times.",
1383+
f"Expected request: {matcher}",
1384+
]
1385+
1386+
if similar_requests:
1387+
assert_msg_lines.append(f"Found {len(similar_requests)} similar request(s):")
1388+
for request in similar_requests:
1389+
assert_msg_lines.extend(
1390+
(
1391+
"--- Similar Request Start",
1392+
f"Path: {request.path}",
1393+
f"Method: {request.method}",
1394+
f"Body: {request.get_data()!r}",
1395+
f"Headers: {request.headers}",
1396+
f"Query String: {request.query_string.decode('utf-8')!r}",
1397+
"--- Similar Request End",
1398+
)
1399+
)
1400+
else:
1401+
assert_msg_lines.append("No similar requests found.")
1402+
1403+
assert_msg = "\n".join(assert_msg_lines) + "\n"
1404+
1405+
assert matching_count == count, assert_msg
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
features:
3+
- |
4+
New methods added to query for matching requests in the log.

tests/test_log_querying.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import pytest
2+
import requests
3+
4+
from pytest_httpserver import HTTPServer
5+
from pytest_httpserver import RequestMatcher
6+
7+
8+
def test_verify(httpserver: HTTPServer):
9+
httpserver.expect_request("/foo").respond_with_data("OK")
10+
httpserver.expect_request("/bar").respond_with_data("OKOK")
11+
12+
assert list(httpserver.iter_matching_requests(httpserver.create_matcher("/foo"))) == []
13+
assert requests.get(httpserver.url_for("/foo")).text == "OK"
14+
assert requests.get(httpserver.url_for("/bar")).text == "OKOK"
15+
16+
matching_log = list(httpserver.iter_matching_requests(httpserver.create_matcher("/foo")))
17+
assert len(matching_log) == 1
18+
19+
request, response = matching_log[0]
20+
21+
assert request.url == httpserver.url_for("/foo")
22+
assert response.get_data() == b"OK"
23+
24+
assert httpserver.get_matching_requests_count(httpserver.create_matcher("/foo")) == 1
25+
httpserver.assert_request_made(httpserver.create_matcher("/foo"))
26+
httpserver.assert_request_made(httpserver.create_matcher("/no_match"), count=0)
27+
28+
with pytest.raises(AssertionError):
29+
assert httpserver.assert_request_made(httpserver.create_matcher("/no_match"))
30+
31+
with pytest.raises(AssertionError):
32+
assert httpserver.assert_request_made(httpserver.create_matcher("/foo"), count=2)
33+
34+
35+
def test_verify_assert_msg(httpserver: HTTPServer):
36+
httpserver.no_handler_status_code = 404
37+
httpserver.expect_request("/foo", json={"foo": "bar"}, method="POST").respond_with_data("OK")
38+
assert requests.get(httpserver.url_for("/foo"), headers={"User-Agent": "requests"}).status_code == 404
39+
40+
with pytest.raises(AssertionError) as err:
41+
httpserver.assert_request_made(RequestMatcher("/foo", json={"foo": "bar"}, method="POST"))
42+
43+
expected_message = f"""Matching request found 0 times but expected 1 times.
44+
Expected request: <RequestMatcher uri='/foo' method='POST' query_string=None headers={{}} data=None json={{'foo': 'bar'}}>
45+
Found 1 similar request(s):
46+
--- Similar Request Start
47+
Path: /foo
48+
Method: GET
49+
Body: b''
50+
Headers: Host: localhost:{httpserver.port}\r
51+
User-Agent: requests\r
52+
Accept-Encoding: gzip, deflate\r
53+
Accept: */*\r
54+
Connection: keep-alive\r
55+
\r
56+
57+
Query String: ''
58+
--- Similar Request End
59+
""" # noqa: E501
60+
assert str(err.value) == expected_message
61+
62+
63+
def test_verify_assert_msg_no_similar_requests(httpserver: HTTPServer):
64+
httpserver.expect_request("/foo", json={"foo": "bar"}, method="POST").respond_with_data("OK")
65+
66+
with pytest.raises(AssertionError) as err:
67+
httpserver.assert_request_made(RequestMatcher("/foo", json={"foo": "bar"}, method="POST"))
68+
69+
expected_message = """Matching request found 0 times but expected 1 times.
70+
Expected request: <RequestMatcher uri='/foo' method='POST' query_string=None headers={} data=None json={'foo': 'bar'}>
71+
No similar requests found.
72+
"""
73+
assert str(err.value) == expected_message

tests/test_release.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ def test_sdist_contents(build: Build, version: str):
202202
"test_headers.py",
203203
"test_ip_protocols.py",
204204
"test_json_matcher.py",
205+
"test_log_querying.py",
205206
"test_mixed.py",
206207
"test_oneshot.py",
207208
"test_ordered.py",

0 commit comments

Comments
 (0)