Skip to content

Commit 5ead2a8

Browse files
Replace pytest-httpbin with minimal Falcon version (#160)
httpbin is wildly out of date these days, and it causes issues for contributors on macOS. Using a simple hand-spun server to just echo status codes is all the pook test suite needs. Falcon might be overkill for that... but httpbin was certainly more anyway.
1 parent 4d1083e commit 5ead2a8

File tree

12 files changed

+214
-181
lines changed

12 files changed

+214
-181
lines changed

examples/regex.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
# Mock definition based
99
(
10-
pook.get(pook.regex("h[t]{2}pbin.*"))
10+
pook.get(pook.regex("h[t]{2}p*"))
1111
.path(pook.regex("/foo/[a-z]+/baz"))
1212
.header("Client", pook.regex("requests|pook"))
1313
.times(2)

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ dependencies = [
6060
"pytest~=8.3",
6161
"pytest-asyncio~=0.24",
6262
"pytest-pook==0.1.0b0",
63-
"pytest-httpbin==2.1.0",
63+
64+
"falcon~=4.0",
6465

6566
"requests~=2.20",
6667
"urllib3~=2.2",

tests/conftest.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from wsgiref.simple_server import make_server
2+
3+
import falcon
4+
5+
import threading
6+
7+
import pytest
8+
9+
10+
class HttpbinLikeResource:
11+
def on_get_status(self, req: falcon.Request, resp: falcon.Response, status: int):
12+
resp.set_header("x-pook-httpbinlike", "")
13+
resp.status = status
14+
15+
on_post_status = on_get_status
16+
17+
18+
class HttpbinLike:
19+
def __init__(self, schema: str, host: str):
20+
self.schema = schema
21+
self.host = host
22+
self.url = schema + host
23+
24+
def __add__(self, value):
25+
return self.url + value
26+
27+
28+
@pytest.fixture(scope="session")
29+
def local_responder():
30+
app = falcon.App()
31+
resource = HttpbinLikeResource()
32+
app.add_route("/status/{status:int}", resource, suffix="status")
33+
34+
with make_server("127.0.0.1", 8080, app) as httpd:
35+
36+
def run():
37+
httpd.serve_forever()
38+
39+
thread = threading.Thread(target=run)
40+
thread.start()
41+
yield HttpbinLike("http://", "127.0.0.1:8080")
42+
httpd.shutdown()
43+
thread.join(timeout=5)
44+
45+
46+
@pytest.fixture
47+
def url_404(local_responder):
48+
"""404 httpbin URL.
49+
50+
Useful in tests if pook is configured to reply 200, and the status is checked.
51+
If pook does not match the request (and if that was the intended behaviour)
52+
then the 404 status code makes that obvious!"""
53+
return local_responder + "/status/404"
54+
55+
56+
@pytest.fixture
57+
def url_401(local_responder):
58+
return local_responder + "/status/401"
59+
60+
61+
@pytest.fixture
62+
def url_500(local_responder):
63+
return local_responder + "/status/500"

tests/integration/engines/pytest_suite.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,39 @@
55

66

77
@pook.on
8-
def test_simple_pook_request():
9-
pook.get("httpbin.org/foo").reply(204)
10-
res = requests.get("http://httpbin.org/foo")
8+
def test_simple_pook_request(url_404):
9+
pook.get(url_404).reply(204)
10+
res = requests.get(url_404)
1111
assert res.status_code == 204
1212

1313

1414
@pook.on
15-
def test_enable_engine():
16-
pook.get("server.com/foo").reply(204)
17-
res = requests.get("http://server.com/foo")
15+
def test_enable_engine(url_404):
16+
pook.get(url_404).reply(204)
17+
res = requests.get(url_404)
1818
assert res.status_code == 204
1919
pook.disable()
2020

2121

22-
@pook.get("server.com/bar", reply=204)
23-
def test_decorator():
24-
res = requests.get("http://server.com/bar")
25-
assert res.status_code == 204
22+
def test_decorator(url_404):
23+
@pook.get(url_404, reply=204)
24+
def do():
25+
res = requests.get(url_404)
26+
assert res.status_code == 204
27+
return True
28+
29+
assert do()
2630

2731

28-
def test_context_manager():
32+
def test_context_manager(url_404):
2933
with pook.use():
30-
pook.get("server.com/baz", reply=204)
31-
res = requests.get("http://server.com/baz")
34+
pook.get(url_404, reply=204)
35+
res = requests.get(url_404)
3236
assert res.status_code == 204
3337

3438

3539
@pook.on
36-
def test_no_match_exception():
37-
pook.get("server.com/bar", reply=204)
40+
def test_no_match_exception(url_404, url_401):
41+
pook.get(url_404, reply=204)
3842
with pytest.raises(Exception):
39-
requests.get("http://server.com/baz")
43+
requests.get(url_401)

tests/integration/examples_test.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,32 @@
22
import subprocess
33
from pathlib import Path
44
import sys
5+
import re
56

67
import pytest
78

89
examples_dir = Path(__file__).parents[2] / "examples"
910

10-
examples = [f.name for f in examples_dir.glob("*.py")]
11+
examples = list(examples_dir.glob("*.py"))
1112

1213

1314
if platform.python_implementation() == "PyPy":
1415
# See pyproject.toml note on mocket dependency
15-
examples.remove("mocket_example.py")
16+
examples.remove(examples_dir / "mocket_example.py")
1617

1718

18-
@pytest.mark.parametrize("example", examples)
19-
def test_examples(example):
20-
result = subprocess.run([sys.executable, f"examples/{example}"], check=False)
19+
HTTPBIN_WITH_SCHEMA_REF = re.compile("https?://httpbin.org")
20+
21+
22+
@pytest.mark.parametrize(
23+
"example", (pytest.param(example, id=example.name) for example in examples)
24+
)
25+
def test_examples(example: Path, local_responder):
26+
code = example.read_text()
27+
with_local_responder = HTTPBIN_WITH_SCHEMA_REF.sub(
28+
local_responder.url, code
29+
).replace("httpbin.org", local_responder.host)
30+
assert "httpbin" not in with_local_responder
31+
result = subprocess.run([sys.executable, "-c", with_local_responder], check=False)
2132

2233
assert result.returncode == 0, result.stdout

tests/unit/interceptors/aiohttp_test.py

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,97 +20,92 @@ async def amake_request(self, method, url, content=None, headers=None):
2020
return response.status, response_content, response.headers
2121

2222

23-
def _pook_url(URL):
24-
return pook.head(URL).reply(200).mock
25-
26-
27-
@pytest.fixture
28-
def URL(httpbin):
29-
return f"{httpbin.url}/status/404"
23+
def _pook_url(url):
24+
return pook.head(url).reply(200).mock
3025

3126

3227
@pytest.mark.asyncio
33-
async def test_async_with_request(URL):
34-
mock = _pook_url(URL)
28+
async def test_async_with_request(url_404):
29+
mock = _pook_url(url_404)
3530
async with aiohttp.ClientSession() as session:
36-
async with session.head(URL) as req:
31+
async with session.head(url_404) as req:
3732
assert req.status == 200
3833

3934
assert len(mock.matches) == 1
4035

4136

4237
@pytest.mark.asyncio
43-
async def test_await_request(URL):
44-
mock = _pook_url(URL)
38+
async def test_await_request(url_404):
39+
mock = _pook_url(url_404)
4540
async with aiohttp.ClientSession() as session:
46-
req = await session.head(URL)
41+
req = await session.head(url_404)
4742
assert req.status == 200
4843

4944
assert len(mock.matches) == 1
5045

5146

5247
@pytest.mark.asyncio
53-
async def test_binary_body(URL):
54-
pook.get(URL).reply(200).body(BINARY_FILE)
48+
async def test_binary_body(url_404):
49+
pook.get(url_404).reply(200).body(BINARY_FILE)
5550
async with aiohttp.ClientSession() as session:
56-
req = await session.get(URL)
51+
req = await session.get(url_404)
5752
assert await req.read() == BINARY_FILE
5853

5954

6055
@pytest.mark.asyncio
61-
async def test_json_matcher_json_payload(URL):
56+
async def test_json_matcher_json_payload(url_404):
6257
payload = {"foo": "bar"}
63-
pook.post(URL).json(payload).reply(200).body(BINARY_FILE)
58+
pook.post(url_404).json(payload).reply(200).body(BINARY_FILE)
6459
async with aiohttp.ClientSession() as session:
65-
req = await session.post(URL, json=payload)
60+
req = await session.post(url_404, json=payload)
6661
assert await req.read() == BINARY_FILE
6762

6863

6964
@pytest.mark.asyncio
70-
async def test_client_base_url(httpbin):
65+
async def test_client_base_url(local_responder):
7166
"""Client base url should be matched."""
72-
pook.get(httpbin + "/status/404").reply(200).body("hello from pook")
73-
async with aiohttp.ClientSession(base_url=httpbin.url) as session:
67+
pook.get(local_responder + "/status/404").reply(200).body("hello from pook")
68+
async with aiohttp.ClientSession(base_url=local_responder.url) as session:
7469
res = await session.get("/status/404")
7570
assert res.status == 200
7671
assert await res.read() == b"hello from pook"
7772

7873

7974
@pytest.mark.asyncio
80-
async def test_client_headers(httpbin):
75+
async def test_client_headers(local_responder):
8176
"""Headers set on the client should be matched."""
82-
pook.get(httpbin + "/status/404").header("x-pook", "hello").reply(200).body(
77+
pook.get(local_responder + "/status/404").header("x-pook", "hello").reply(200).body(
8378
"hello from pook"
8479
)
8580
async with aiohttp.ClientSession(headers={"x-pook": "hello"}) as session:
86-
res = await session.get(httpbin + "/status/404")
81+
res = await session.get(local_responder + "/status/404")
8782
assert res.status == 200
8883
assert await res.read() == b"hello from pook"
8984

9085

9186
@pytest.mark.asyncio
92-
async def test_client_headers_merged(httpbin):
87+
async def test_client_headers_merged(local_responder):
9388
"""Headers set on the client should be matched even if request-specific headers are sent."""
94-
pook.get(httpbin + "/status/404").header("x-pook", "hello").reply(200).body(
89+
pook.get(local_responder + "/status/404").header("x-pook", "hello").reply(200).body(
9590
"hello from pook"
9691
)
9792
async with aiohttp.ClientSession(headers={"x-pook": "hello"}) as session:
9893
res = await session.get(
99-
httpbin + "/status/404", headers={"x-pook-secondary": "xyz"}
94+
local_responder + "/status/404", headers={"x-pook-secondary": "xyz"}
10095
)
10196
assert res.status == 200
10297
assert await res.read() == b"hello from pook"
10398

10499

105100
@pytest.mark.asyncio
106-
async def test_client_headers_both_session_and_request(httpbin):
101+
async def test_client_headers_both_session_and_request(local_responder):
107102
"""Headers should be matchable from both the session and request in the same matcher"""
108-
pook.get(httpbin + "/status/404").header("x-pook-session", "hello").header(
103+
pook.get(local_responder + "/status/404").header("x-pook-session", "hello").header(
109104
"x-pook-request", "hey"
110105
).reply(200).body("hello from pook")
111106
async with aiohttp.ClientSession(headers={"x-pook-session": "hello"}) as session:
112107
res = await session.get(
113-
httpbin + "/status/404", headers={"x-pook-request": "hey"}
108+
local_responder + "/status/404", headers={"x-pook-request": "hey"}
114109
)
115110
assert res.status == 200
116111
assert await res.read() == b"hello from pook"

tests/unit/interceptors/base.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,6 @@ def _loop(self, request):
4747
else:
4848
yield
4949

50-
@pytest.fixture
51-
def url_404(self, httpbin):
52-
"""404 httpbin URL.
53-
54-
Useful in tests if pook is configured to reply 200, and the status is checked.
55-
If pook does not match the request (and if that was the intended behaviour)
56-
then the 404 status code makes that obvious!"""
57-
return f"{httpbin.url}/status/404"
58-
59-
@pytest.fixture
60-
def url_500(self, httpbin):
61-
return f"{httpbin.url}/status/500"
62-
63-
@pytest.fixture
64-
def url_401(self, httpbin):
65-
return httpbin + "/status/401"
66-
6750
@pytest.mark.pook
6851
def test_activate_deactivate(self, url_404):
6952
"""Deactivating pook allows requests to go through."""

0 commit comments

Comments
 (0)