Skip to content

Commit

Permalink
Replace pytest-httpbin with minimal Falcon version (#160)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sarayourfriend authored Dec 23, 2024
1 parent 4d1083e commit 5ead2a8
Show file tree
Hide file tree
Showing 12 changed files with 214 additions and 181 deletions.
2 changes: 1 addition & 1 deletion examples/regex.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# Mock definition based
(
pook.get(pook.regex("h[t]{2}pbin.*"))
pook.get(pook.regex("h[t]{2}p*"))
.path(pook.regex("/foo/[a-z]+/baz"))
.header("Client", pook.regex("requests|pook"))
.times(2)
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ dependencies = [
"pytest~=8.3",
"pytest-asyncio~=0.24",
"pytest-pook==0.1.0b0",
"pytest-httpbin==2.1.0",

"falcon~=4.0",

"requests~=2.20",
"urllib3~=2.2",
Expand Down
63 changes: 63 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from wsgiref.simple_server import make_server

import falcon

import threading

import pytest


class HttpbinLikeResource:
def on_get_status(self, req: falcon.Request, resp: falcon.Response, status: int):
resp.set_header("x-pook-httpbinlike", "")
resp.status = status

on_post_status = on_get_status


class HttpbinLike:
def __init__(self, schema: str, host: str):
self.schema = schema
self.host = host
self.url = schema + host

def __add__(self, value):
return self.url + value


@pytest.fixture(scope="session")
def local_responder():
app = falcon.App()
resource = HttpbinLikeResource()
app.add_route("/status/{status:int}", resource, suffix="status")

with make_server("127.0.0.1", 8080, app) as httpd:

def run():
httpd.serve_forever()

thread = threading.Thread(target=run)
thread.start()
yield HttpbinLike("http://", "127.0.0.1:8080")
httpd.shutdown()
thread.join(timeout=5)


@pytest.fixture
def url_404(local_responder):
"""404 httpbin URL.
Useful in tests if pook is configured to reply 200, and the status is checked.
If pook does not match the request (and if that was the intended behaviour)
then the 404 status code makes that obvious!"""
return local_responder + "/status/404"


@pytest.fixture
def url_401(local_responder):
return local_responder + "/status/401"


@pytest.fixture
def url_500(local_responder):
return local_responder + "/status/500"
36 changes: 20 additions & 16 deletions tests/integration/engines/pytest_suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,39 @@


@pook.on
def test_simple_pook_request():
pook.get("httpbin.org/foo").reply(204)
res = requests.get("http://httpbin.org/foo")
def test_simple_pook_request(url_404):
pook.get(url_404).reply(204)
res = requests.get(url_404)
assert res.status_code == 204


@pook.on
def test_enable_engine():
pook.get("server.com/foo").reply(204)
res = requests.get("http://server.com/foo")
def test_enable_engine(url_404):
pook.get(url_404).reply(204)
res = requests.get(url_404)
assert res.status_code == 204
pook.disable()


@pook.get("server.com/bar", reply=204)
def test_decorator():
res = requests.get("http://server.com/bar")
assert res.status_code == 204
def test_decorator(url_404):
@pook.get(url_404, reply=204)
def do():
res = requests.get(url_404)
assert res.status_code == 204
return True

assert do()


def test_context_manager():
def test_context_manager(url_404):
with pook.use():
pook.get("server.com/baz", reply=204)
res = requests.get("http://server.com/baz")
pook.get(url_404, reply=204)
res = requests.get(url_404)
assert res.status_code == 204


@pook.on
def test_no_match_exception():
pook.get("server.com/bar", reply=204)
def test_no_match_exception(url_404, url_401):
pook.get(url_404, reply=204)
with pytest.raises(Exception):
requests.get("http://server.com/baz")
requests.get(url_401)
21 changes: 16 additions & 5 deletions tests/integration/examples_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,32 @@
import subprocess
from pathlib import Path
import sys
import re

import pytest

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

examples = [f.name for f in examples_dir.glob("*.py")]
examples = list(examples_dir.glob("*.py"))


if platform.python_implementation() == "PyPy":
# See pyproject.toml note on mocket dependency
examples.remove("mocket_example.py")
examples.remove(examples_dir / "mocket_example.py")


@pytest.mark.parametrize("example", examples)
def test_examples(example):
result = subprocess.run([sys.executable, f"examples/{example}"], check=False)
HTTPBIN_WITH_SCHEMA_REF = re.compile("https?://httpbin.org")


@pytest.mark.parametrize(
"example", (pytest.param(example, id=example.name) for example in examples)
)
def test_examples(example: Path, local_responder):
code = example.read_text()
with_local_responder = HTTPBIN_WITH_SCHEMA_REF.sub(
local_responder.url, code
).replace("httpbin.org", local_responder.host)
assert "httpbin" not in with_local_responder
result = subprocess.run([sys.executable, "-c", with_local_responder], check=False)

assert result.returncode == 0, result.stdout
57 changes: 26 additions & 31 deletions tests/unit/interceptors/aiohttp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,97 +20,92 @@ async def amake_request(self, method, url, content=None, headers=None):
return response.status, response_content, response.headers


def _pook_url(URL):
return pook.head(URL).reply(200).mock


@pytest.fixture
def URL(httpbin):
return f"{httpbin.url}/status/404"
def _pook_url(url):
return pook.head(url).reply(200).mock


@pytest.mark.asyncio
async def test_async_with_request(URL):
mock = _pook_url(URL)
async def test_async_with_request(url_404):
mock = _pook_url(url_404)
async with aiohttp.ClientSession() as session:
async with session.head(URL) as req:
async with session.head(url_404) as req:
assert req.status == 200

assert len(mock.matches) == 1


@pytest.mark.asyncio
async def test_await_request(URL):
mock = _pook_url(URL)
async def test_await_request(url_404):
mock = _pook_url(url_404)
async with aiohttp.ClientSession() as session:
req = await session.head(URL)
req = await session.head(url_404)
assert req.status == 200

assert len(mock.matches) == 1


@pytest.mark.asyncio
async def test_binary_body(URL):
pook.get(URL).reply(200).body(BINARY_FILE)
async def test_binary_body(url_404):
pook.get(url_404).reply(200).body(BINARY_FILE)
async with aiohttp.ClientSession() as session:
req = await session.get(URL)
req = await session.get(url_404)
assert await req.read() == BINARY_FILE


@pytest.mark.asyncio
async def test_json_matcher_json_payload(URL):
async def test_json_matcher_json_payload(url_404):
payload = {"foo": "bar"}
pook.post(URL).json(payload).reply(200).body(BINARY_FILE)
pook.post(url_404).json(payload).reply(200).body(BINARY_FILE)
async with aiohttp.ClientSession() as session:
req = await session.post(URL, json=payload)
req = await session.post(url_404, json=payload)
assert await req.read() == BINARY_FILE


@pytest.mark.asyncio
async def test_client_base_url(httpbin):
async def test_client_base_url(local_responder):
"""Client base url should be matched."""
pook.get(httpbin + "/status/404").reply(200).body("hello from pook")
async with aiohttp.ClientSession(base_url=httpbin.url) as session:
pook.get(local_responder + "/status/404").reply(200).body("hello from pook")
async with aiohttp.ClientSession(base_url=local_responder.url) as session:
res = await session.get("/status/404")
assert res.status == 200
assert await res.read() == b"hello from pook"


@pytest.mark.asyncio
async def test_client_headers(httpbin):
async def test_client_headers(local_responder):
"""Headers set on the client should be matched."""
pook.get(httpbin + "/status/404").header("x-pook", "hello").reply(200).body(
pook.get(local_responder + "/status/404").header("x-pook", "hello").reply(200).body(
"hello from pook"
)
async with aiohttp.ClientSession(headers={"x-pook": "hello"}) as session:
res = await session.get(httpbin + "/status/404")
res = await session.get(local_responder + "/status/404")
assert res.status == 200
assert await res.read() == b"hello from pook"


@pytest.mark.asyncio
async def test_client_headers_merged(httpbin):
async def test_client_headers_merged(local_responder):
"""Headers set on the client should be matched even if request-specific headers are sent."""
pook.get(httpbin + "/status/404").header("x-pook", "hello").reply(200).body(
pook.get(local_responder + "/status/404").header("x-pook", "hello").reply(200).body(
"hello from pook"
)
async with aiohttp.ClientSession(headers={"x-pook": "hello"}) as session:
res = await session.get(
httpbin + "/status/404", headers={"x-pook-secondary": "xyz"}
local_responder + "/status/404", headers={"x-pook-secondary": "xyz"}
)
assert res.status == 200
assert await res.read() == b"hello from pook"


@pytest.mark.asyncio
async def test_client_headers_both_session_and_request(httpbin):
async def test_client_headers_both_session_and_request(local_responder):
"""Headers should be matchable from both the session and request in the same matcher"""
pook.get(httpbin + "/status/404").header("x-pook-session", "hello").header(
pook.get(local_responder + "/status/404").header("x-pook-session", "hello").header(
"x-pook-request", "hey"
).reply(200).body("hello from pook")
async with aiohttp.ClientSession(headers={"x-pook-session": "hello"}) as session:
res = await session.get(
httpbin + "/status/404", headers={"x-pook-request": "hey"}
local_responder + "/status/404", headers={"x-pook-request": "hey"}
)
assert res.status == 200
assert await res.read() == b"hello from pook"
17 changes: 0 additions & 17 deletions tests/unit/interceptors/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,6 @@ def _loop(self, request):
else:
yield

@pytest.fixture
def url_404(self, httpbin):
"""404 httpbin URL.
Useful in tests if pook is configured to reply 200, and the status is checked.
If pook does not match the request (and if that was the intended behaviour)
then the 404 status code makes that obvious!"""
return f"{httpbin.url}/status/404"

@pytest.fixture
def url_500(self, httpbin):
return f"{httpbin.url}/status/500"

@pytest.fixture
def url_401(self, httpbin):
return httpbin + "/status/401"

@pytest.mark.pook
def test_activate_deactivate(self, url_404):
"""Deactivating pook allows requests to go through."""
Expand Down
Loading

0 comments on commit 5ead2a8

Please sign in to comment.