Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 49 additions & 7 deletions src/pook/interceptors/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from aiohttp.helpers import TimerNoop
from aiohttp.streams import EmptyStreamReader

from pook.headers import HTTPHeaderDict
from pook.request import Request # type: ignore
from pook.interceptors.base import BaseInterceptor

Expand Down Expand Up @@ -58,7 +59,8 @@ def set_headers(self, req, headers) -> None:
# ``pook.request`` only allows a dict, so we need to map the iterable to the matchable interface
if headers:
if isinstance(headers, Mapping):
req.headers.update(**headers)
for key, val in headers.items():
req.headers.add(key, val)
else:
# If it isn't a mapping, then its an Iterable[Tuple[Union[str, istr], str]]
for req_header, req_header_value in headers:
Expand All @@ -81,22 +83,20 @@ async def _on_request(
# Create request contract based on incoming params
req = Request(method)

self.set_headers(req, headers)
self.set_headers(req, session.headers)

req.body = data
req.body = data # XXX take from real request?

# Expose extra variadic arguments
req.extra = kw

full_url = session._build_url(url)
original_params = kw.get("params")

# Compose URL
if not kw.get("params"):
if not original_params:
req.url = str(full_url)
else:
# Transform params as a list of tuple
params = kw["params"]
params = original_params
if isinstance(params, dict):
params = [(x, y) for x, y in kw["params"].items()]
req.url = str(full_url) + "?" + urlencode(params)
Expand All @@ -107,6 +107,48 @@ async def _on_request(
if "Content-Type" not in req.headers:
req.headers["Content-Type"] = "application/json"

# Lifted from the ClientSession._request method we're mocking:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aiohttp is licenced under Apache 2.0. It is probably best to put this portion of code into a separate directory with its own LICENSE file (and heading) to clarify that this code is not MIT licenced with the rest of pook.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's see how this goes....

auth = kw.get('auth')
if (
auth is None
and session._default_auth
and (
not session._base_url or session._base_url_origin == full_url.origin()
)
):
auth = session._default_auth

# Lifted from the ClientSession._request method we're mocking:
headers = session._prepare_headers(headers)

aiohttp_req = session._request_class(
method,
full_url,
params=original_params,
headers=headers,
skip_auto_headers=None, # XXX
data=data,
cookies=None, # XXX
auth=auth,
version=session._version,
compress=kw.get('compress'),
chunked=kw.get('chunked'),
expect100=kw.get('expect100', False),
loop=session._loop,
response_class=session._response_class,
proxy=None, # XXX
proxy_auth=kw.get('proxy_auth'),
timer=None, # XXX,
session=session,
ssl=None, # XXX,
server_hostname=kw.get('server_hostname'),
proxy_headers=None, # XXX
traces=None, # XXX
trust_env=session.trust_env,
)

self.set_headers(req, aiohttp_req.headers)

# Match the request against the registered mocks in pook
mock = self.engine.match(req)

Expand Down
16 changes: 16 additions & 0 deletions tests/unit/interceptors/aiohttp_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import aiohttp
import pytest
from aiohttp import BasicAuth

import pook
from tests.unit.fixtures import BINARY_FILE
Expand Down Expand Up @@ -97,6 +98,21 @@ async def test_client_headers_merged(local_responder):
assert await res.read() == b"hello from pook"


@pytest.mark.asyncio
async def test_client_auth_merged(local_responder):
"""Auth headers set on the client should be matched"""
pook \
.get(local_responder + "/status/404") \
.header("Authorization", "Basic dXNlcjpwYXNzd29yZA==") \
.reply(200).body("hello from pook")
async with aiohttp.ClientSession(auth=BasicAuth('user', 'password')) as session:
res = await session.get(
local_responder + "/status/404", headers={"x-pook-secondary": "xyz"}
)
assert res.status == 200
assert await res.read() == b"hello from pook"
Comment on lines +102 to +113

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth adding a basic auth test to the standard interceptor tests 🤔

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there actually is already, the problem here is that aiohttp.ClientSession(auth=BasicAuth('user', 'password')) is an aiohttp-specific way of setting session-wide basic auth, and pook's interceptor currently doesn't handle that.



@pytest.mark.asyncio
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"""
Expand Down
Loading