Increase test coverage - take 2 (#1012)
* Fix HttpError -> HTTPError typo * Increased test coverage * Increase coverage threshold * Reuse sync/async transport code in test_auth.py * Removed close_client check inside StreamContextManager It's never set as True when used in async * Reuse sync/async transport code in test_redirects.py
This commit is contained in:
parent
8c84210555
commit
620b0670db
@ -249,8 +249,8 @@ We can raise an exception for any Client or Server error responses (4xx or 5xx s
|
||||
>>> not_found.raise_for_status()
|
||||
Traceback (most recent call last):
|
||||
File "/Users/tomchristie/GitHub/encode/httpcore/httpx/models.py", line 776, in raise_for_status
|
||||
raise HttpError(message)
|
||||
httpx.exceptions.HttpError: 404 Not Found
|
||||
raise HTTPError(message)
|
||||
httpx.HTTPError: 404 Not Found
|
||||
```
|
||||
|
||||
Any successful response codes will simply return `None` rather than raising an exception.
|
||||
|
||||
@ -1539,5 +1539,3 @@ class StreamContextManager:
|
||||
) -> None:
|
||||
assert isinstance(self.client, AsyncClient)
|
||||
await self.response.aclose()
|
||||
if self.close_client:
|
||||
await self.client.aclose()
|
||||
|
||||
@ -826,7 +826,7 @@ class Response:
|
||||
|
||||
def raise_for_status(self) -> None:
|
||||
"""
|
||||
Raise the `HttpError` if one occurred.
|
||||
Raise the `HTTPError` if one occurred.
|
||||
"""
|
||||
message = (
|
||||
"{0.status_code} {error_type}: {0.reason_phrase} for url: {0.url}\n"
|
||||
|
||||
@ -8,4 +8,4 @@ export SOURCE_FILES="httpx tests"
|
||||
|
||||
set -x
|
||||
|
||||
${PREFIX}coverage report --show-missing --skip-covered --fail-under=97
|
||||
${PREFIX}coverage report --show-missing --skip-covered --fail-under=99
|
||||
|
||||
@ -9,6 +9,7 @@ from httpx import (
|
||||
URL,
|
||||
AsyncClient,
|
||||
Auth,
|
||||
Client,
|
||||
DigestAuth,
|
||||
Headers,
|
||||
ProtocolError,
|
||||
@ -27,12 +28,12 @@ def get_header_value(headers, key, default=None):
|
||||
return default
|
||||
|
||||
|
||||
class MockTransport(httpcore.AsyncHTTPTransport):
|
||||
class MockTransport:
|
||||
def __init__(self, auth_header: bytes = b"", status_code: int = 200) -> None:
|
||||
self.auth_header = auth_header
|
||||
self.status_code = status_code
|
||||
|
||||
async def request(
|
||||
def _request(
|
||||
self,
|
||||
method: bytes,
|
||||
url: typing.Tuple[bytes, bytes, int, bytes],
|
||||
@ -50,6 +51,24 @@ class MockTransport(httpcore.AsyncHTTPTransport):
|
||||
return b"HTTP/1.1", self.status_code, b"", response_headers, response_stream
|
||||
|
||||
|
||||
class AsyncMockTransport(MockTransport, httpcore.AsyncHTTPTransport):
|
||||
async def request(
|
||||
self, *args, **kwargs
|
||||
) -> typing.Tuple[
|
||||
bytes, int, bytes, typing.List[typing.Tuple[bytes, bytes]], ContentStream
|
||||
]:
|
||||
return self._request(*args, **kwargs)
|
||||
|
||||
|
||||
class SyncMockTransport(MockTransport, httpcore.SyncHTTPTransport):
|
||||
def request(
|
||||
self, *args, **kwargs
|
||||
) -> typing.Tuple[
|
||||
bytes, int, bytes, typing.List[typing.Tuple[bytes, bytes]], ContentStream
|
||||
]:
|
||||
return self._request(*args, **kwargs)
|
||||
|
||||
|
||||
class MockDigestAuthTransport(httpcore.AsyncHTTPTransport):
|
||||
def __init__(
|
||||
self,
|
||||
@ -114,12 +133,58 @@ class MockDigestAuthTransport(httpcore.AsyncHTTPTransport):
|
||||
return b"HTTP/1.1", 401, b"", headers, ContentStream()
|
||||
|
||||
|
||||
class RepeatAuth(Auth):
|
||||
"""
|
||||
A mock authentication scheme that requires clients to send
|
||||
the request a fixed number of times, and then send a last request containing
|
||||
an aggregation of nonces that the server sent in 'WWW-Authenticate' headers
|
||||
of intermediate responses.
|
||||
"""
|
||||
|
||||
requires_request_body = True
|
||||
|
||||
def __init__(self, repeat: int):
|
||||
self.repeat = repeat
|
||||
|
||||
def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
|
||||
nonces = []
|
||||
|
||||
for index in range(self.repeat):
|
||||
request.headers["Authorization"] = f"Repeat {index}"
|
||||
response = yield request
|
||||
nonces.append(response.headers["www-authenticate"])
|
||||
|
||||
key = ".".join(nonces)
|
||||
request.headers["Authorization"] = f"Repeat {key}"
|
||||
yield request
|
||||
|
||||
|
||||
class ResponseBodyAuth(Auth):
|
||||
"""
|
||||
A mock authentication scheme that requires clients to send an 'Authorization'
|
||||
header, then send back the contents of the response in the 'Authorization'
|
||||
header.
|
||||
"""
|
||||
|
||||
requires_response_body = True
|
||||
|
||||
def __init__(self, token):
|
||||
self.token = token
|
||||
|
||||
def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
|
||||
request.headers["Authorization"] = self.token
|
||||
response = yield request
|
||||
data = response.text
|
||||
request.headers["Authorization"] = data
|
||||
yield request
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_auth() -> None:
|
||||
url = "https://example.org/"
|
||||
auth = ("tomchristie", "password123")
|
||||
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -130,7 +195,7 @@ async def test_basic_auth() -> None:
|
||||
async def test_basic_auth_in_url() -> None:
|
||||
url = "https://tomchristie:password123@example.org/"
|
||||
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -142,7 +207,7 @@ async def test_basic_auth_on_session() -> None:
|
||||
url = "https://example.org/"
|
||||
auth = ("tomchristie", "password123")
|
||||
|
||||
client = AsyncClient(transport=MockTransport(), auth=auth)
|
||||
client = AsyncClient(transport=AsyncMockTransport(), auth=auth)
|
||||
response = await client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -157,7 +222,7 @@ async def test_custom_auth() -> None:
|
||||
request.headers["Authorization"] = "Token 123"
|
||||
return request
|
||||
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -169,7 +234,7 @@ async def test_netrc_auth() -> None:
|
||||
os.environ["NETRC"] = "tests/.netrc"
|
||||
url = "http://netrcexample.org"
|
||||
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -183,7 +248,7 @@ async def test_auth_header_has_priority_over_netrc() -> None:
|
||||
os.environ["NETRC"] = "tests/.netrc"
|
||||
url = "http://netrcexample.org"
|
||||
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get(url, headers={"Authorization": "Override"})
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -195,13 +260,13 @@ async def test_trust_env_auth() -> None:
|
||||
os.environ["NETRC"] = "tests/.netrc"
|
||||
url = "http://netrcexample.org"
|
||||
|
||||
client = AsyncClient(transport=MockTransport(), trust_env=False)
|
||||
client = AsyncClient(transport=AsyncMockTransport(), trust_env=False)
|
||||
response = await client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"auth": None}
|
||||
|
||||
client = AsyncClient(transport=MockTransport(), trust_env=True)
|
||||
client = AsyncClient(transport=AsyncMockTransport(), trust_env=True)
|
||||
response = await client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -222,7 +287,7 @@ async def test_auth_hidden_header() -> None:
|
||||
url = "https://example.org/"
|
||||
auth = ("example-username", "example-password")
|
||||
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert "'authorization': '[secure]'" in str(response.request.headers)
|
||||
@ -232,7 +297,7 @@ async def test_auth_hidden_header() -> None:
|
||||
async def test_auth_invalid_type() -> None:
|
||||
url = "https://example.org/"
|
||||
client = AsyncClient(
|
||||
transport=MockTransport(), auth="not a tuple, not a callable", # type: ignore
|
||||
transport=AsyncMockTransport(), auth="not a tuple, not a callable",
|
||||
)
|
||||
with pytest.raises(TypeError):
|
||||
await client.get(url)
|
||||
@ -243,7 +308,7 @@ async def test_digest_auth_returns_no_auth_if_no_digest_header_in_response() ->
|
||||
url = "https://example.org/"
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 200
|
||||
@ -258,7 +323,7 @@ async def test_digest_auth_200_response_including_digest_auth_header() -> None:
|
||||
auth_header = b'Digest realm="realm@host.com",qop="auth",nonce="abc",opaque="xyz"'
|
||||
|
||||
client = AsyncClient(
|
||||
transport=MockTransport(auth_header=auth_header, status_code=200)
|
||||
transport=AsyncMockTransport(auth_header=auth_header, status_code=200)
|
||||
)
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
@ -272,7 +337,7 @@ async def test_digest_auth_401_response_without_digest_auth_header() -> None:
|
||||
url = "https://example.org/"
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
|
||||
client = AsyncClient(transport=MockTransport(auth_header=b"", status_code=401))
|
||||
client = AsyncClient(transport=AsyncMockTransport(auth_header=b"", status_code=401))
|
||||
response = await client.get(url, auth=auth)
|
||||
|
||||
assert response.status_code == 401
|
||||
@ -413,56 +478,51 @@ async def test_digest_auth_incorrect_credentials() -> None:
|
||||
],
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_digest_auth_raises_protocol_error_on_malformed_header(
|
||||
async def test_async_digest_auth_raises_protocol_error_on_malformed_header(
|
||||
auth_header: bytes,
|
||||
) -> None:
|
||||
url = "https://example.org/"
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
client = AsyncClient(
|
||||
transport=MockTransport(auth_header=auth_header, status_code=401)
|
||||
transport=AsyncMockTransport(auth_header=auth_header, status_code=401)
|
||||
)
|
||||
|
||||
with pytest.raises(ProtocolError):
|
||||
await client.get(url, auth=auth)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"auth_header",
|
||||
[
|
||||
b'Digest realm="httpx@example.org", qop="auth"', # missing fields
|
||||
b'realm="httpx@example.org", qop="auth"', # not starting with Digest
|
||||
b'DigestZ realm="httpx@example.org", qop="auth"'
|
||||
b'qop="auth,auth-int",nonce="abc",opaque="xyz"',
|
||||
b'Digest realm="httpx@example.org", qop="auth,au', # malformed fields list
|
||||
],
|
||||
)
|
||||
def test_sync_digest_auth_raises_protocol_error_on_malformed_header(
|
||||
auth_header: bytes,
|
||||
) -> None:
|
||||
url = "https://example.org/"
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
client = Client(
|
||||
transport=SyncMockTransport(auth_header=auth_header, status_code=401)
|
||||
)
|
||||
|
||||
with pytest.raises(ProtocolError):
|
||||
client.get(url, auth=auth)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auth_history() -> None:
|
||||
async def test_async_auth_history() -> None:
|
||||
"""
|
||||
Test that intermediate requests sent as part of an authentication flow
|
||||
are recorded in the response history.
|
||||
"""
|
||||
|
||||
class RepeatAuth(Auth):
|
||||
"""
|
||||
A mock authentication scheme that requires clients to send
|
||||
the request a fixed number of times, and then send a last request containing
|
||||
an aggregation of nonces that the server sent in 'WWW-Authenticate' headers
|
||||
of intermediate responses.
|
||||
"""
|
||||
|
||||
requires_request_body = True
|
||||
|
||||
def __init__(self, repeat: int):
|
||||
self.repeat = repeat
|
||||
|
||||
def auth_flow(
|
||||
self, request: Request
|
||||
) -> typing.Generator[Request, Response, None]:
|
||||
nonces = []
|
||||
|
||||
for index in range(self.repeat):
|
||||
request.headers["Authorization"] = f"Repeat {index}"
|
||||
response = yield request
|
||||
nonces.append(response.headers["www-authenticate"])
|
||||
|
||||
key = ".".join(nonces)
|
||||
request.headers["Authorization"] = f"Repeat {key}"
|
||||
yield request
|
||||
|
||||
url = "https://example.org/"
|
||||
auth = RepeatAuth(repeat=2)
|
||||
client = AsyncClient(transport=MockTransport(auth_header=b"abc"))
|
||||
client = AsyncClient(transport=AsyncMockTransport(auth_header=b"abc"))
|
||||
|
||||
response = await client.get(url, auth=auth)
|
||||
assert response.status_code == 200
|
||||
@ -479,11 +539,35 @@ async def test_auth_history() -> None:
|
||||
assert len(resp1.history) == 0
|
||||
|
||||
|
||||
def test_sync_auth_history() -> None:
|
||||
"""
|
||||
Test that intermediate requests sent as part of an authentication flow
|
||||
are recorded in the response history.
|
||||
"""
|
||||
url = "https://example.org/"
|
||||
auth = RepeatAuth(repeat=2)
|
||||
client = Client(transport=SyncMockTransport(auth_header=b"abc"))
|
||||
|
||||
response = client.get(url, auth=auth)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"auth": "Repeat abc.abc"}
|
||||
|
||||
assert len(response.history) == 2
|
||||
resp1, resp2 = response.history
|
||||
assert resp1.json() == {"auth": "Repeat 0"}
|
||||
assert resp2.json() == {"auth": "Repeat 1"}
|
||||
|
||||
assert len(resp2.history) == 1
|
||||
assert resp2.history == [resp1]
|
||||
|
||||
assert len(resp1.history) == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_digest_auth_unavailable_streaming_body():
|
||||
url = "https://example.org/"
|
||||
auth = DigestAuth(username="tomchristie", password="password123")
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
|
||||
async def streaming_body():
|
||||
yield b"Example request body" # pragma: nocover
|
||||
@ -493,37 +577,29 @@ async def test_digest_auth_unavailable_streaming_body():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auth_reads_response_body() -> None:
|
||||
async def test_async_auth_reads_response_body() -> None:
|
||||
"""
|
||||
Test that we can read the response body in an auth flow if `requires_response_body`
|
||||
is set.
|
||||
"""
|
||||
|
||||
class ResponseBodyAuth(Auth):
|
||||
"""
|
||||
A mock authentication scheme that requires clients to send an 'Authorization'
|
||||
header, then send back the contents of the response in the 'Authorization'
|
||||
header.
|
||||
"""
|
||||
|
||||
requires_response_body = True
|
||||
|
||||
def __init__(self, token):
|
||||
self.token = token
|
||||
|
||||
def auth_flow(
|
||||
self, request: Request
|
||||
) -> typing.Generator[Request, Response, None]:
|
||||
request.headers["Authorization"] = self.token
|
||||
response = yield request
|
||||
data = response.text
|
||||
request.headers["Authorization"] = data
|
||||
yield request
|
||||
|
||||
url = "https://example.org/"
|
||||
auth = ResponseBodyAuth("xyz")
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
|
||||
response = await client.get(url, auth=auth)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"auth": '{"auth": "xyz"}'}
|
||||
|
||||
|
||||
def test_sync_auth_reads_response_body() -> None:
|
||||
"""
|
||||
Test that we can read the response body in an auth flow if `requires_response_body`
|
||||
is set.
|
||||
"""
|
||||
url = "https://example.org/"
|
||||
auth = ResponseBodyAuth("xyz")
|
||||
client = Client(transport=SyncMockTransport())
|
||||
|
||||
response = client.get(url, auth=auth)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"auth": '{"auth": "xyz"}'}
|
||||
|
||||
@ -84,6 +84,17 @@ def test_transport_for_request(url, proxies, expected):
|
||||
assert transport.proxy_origin == httpx.URL(expected).raw[:3]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_proxy_close():
|
||||
client = httpx.AsyncClient(proxies={"all": PROXY_URL})
|
||||
await client.aclose()
|
||||
|
||||
|
||||
def test_sync_proxy_close():
|
||||
client = httpx.Client(proxies={"all": PROXY_URL})
|
||||
client.close()
|
||||
|
||||
|
||||
def test_unsupported_proxy_scheme():
|
||||
with pytest.raises(ValueError):
|
||||
httpx.AsyncClient(proxies="ftp://127.0.0.1")
|
||||
|
||||
@ -8,6 +8,7 @@ import pytest
|
||||
from httpx import (
|
||||
URL,
|
||||
AsyncClient,
|
||||
Client,
|
||||
InvalidURL,
|
||||
NotRedirectResponse,
|
||||
RequestBodyUnavailable,
|
||||
@ -25,8 +26,8 @@ def get_header_value(headers, key, default=None):
|
||||
return default
|
||||
|
||||
|
||||
class MockTransport(httpcore.AsyncHTTPTransport):
|
||||
async def request(
|
||||
class MockTransport:
|
||||
def _request(
|
||||
self,
|
||||
method: bytes,
|
||||
url: typing.Tuple[bytes, bytes, int, bytes],
|
||||
@ -106,19 +107,17 @@ class MockTransport(httpcore.AsyncHTTPTransport):
|
||||
return b"HTTP/1.1", 200, b"OK", [], content
|
||||
|
||||
elif path == b"/redirect_body":
|
||||
_ = b"".join([part async for part in stream])
|
||||
code = codes.PERMANENT_REDIRECT
|
||||
headers = [(b"location", b"/redirect_body_target")]
|
||||
return b"HTTP/1.1", code, b"Permanent Redirect", headers, ByteStream(b"")
|
||||
|
||||
elif path == b"/redirect_no_body":
|
||||
_ = b"".join([part async for part in stream])
|
||||
code = codes.SEE_OTHER
|
||||
headers = [(b"location", b"/redirect_body_target")]
|
||||
return b"HTTP/1.1", code, b"See Other", headers, ByteStream(b"")
|
||||
|
||||
elif path == b"/redirect_body_target":
|
||||
content = b"".join([part async for part in stream])
|
||||
content = b"".join(stream)
|
||||
headers_dict = dict(
|
||||
[(key.decode("ascii"), value.decode("ascii")) for key, value in headers]
|
||||
)
|
||||
@ -155,9 +154,27 @@ class MockTransport(httpcore.AsyncHTTPTransport):
|
||||
return b"HTTP/1.1", 200, b"OK", [], ByteStream(b"Hello, world!")
|
||||
|
||||
|
||||
class AsyncMockTransport(MockTransport, httpcore.AsyncHTTPTransport):
|
||||
async def request(
|
||||
self, *args, **kwargs
|
||||
) -> typing.Tuple[
|
||||
bytes, int, bytes, typing.List[typing.Tuple[bytes, bytes]], ContentStream
|
||||
]:
|
||||
return self._request(*args, **kwargs)
|
||||
|
||||
|
||||
class SyncMockTransport(MockTransport, httpcore.SyncHTTPTransport):
|
||||
def request(
|
||||
self, *args, **kwargs
|
||||
) -> typing.Tuple[
|
||||
bytes, int, bytes, typing.List[typing.Tuple[bytes, bytes]], ContentStream
|
||||
]:
|
||||
return self._request(*args, **kwargs)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_no_redirect():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
url = "https://example.com/no_redirect"
|
||||
response = await client.get(url)
|
||||
assert response.status_code == 200
|
||||
@ -167,7 +184,7 @@ async def test_no_redirect():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_redirect_301():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.post("https://example.org/redirect_301")
|
||||
assert response.status_code == codes.OK
|
||||
assert response.url == URL("https://example.org/")
|
||||
@ -176,7 +193,7 @@ async def test_redirect_301():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_redirect_302():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.post("https://example.org/redirect_302")
|
||||
assert response.status_code == codes.OK
|
||||
assert response.url == URL("https://example.org/")
|
||||
@ -185,7 +202,7 @@ async def test_redirect_302():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_redirect_303():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get("https://example.org/redirect_303")
|
||||
assert response.status_code == codes.OK
|
||||
assert response.url == URL("https://example.org/")
|
||||
@ -194,7 +211,7 @@ async def test_redirect_303():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_disallow_redirects():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.post(
|
||||
"https://example.org/redirect_303", allow_redirects=False
|
||||
)
|
||||
@ -212,7 +229,7 @@ async def test_disallow_redirects():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_relative_redirect():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get("https://example.org/relative_redirect")
|
||||
assert response.status_code == codes.OK
|
||||
assert response.url == URL("https://example.org/")
|
||||
@ -222,7 +239,7 @@ async def test_relative_redirect():
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_malformed_redirect():
|
||||
# https://github.com/encode/httpx/issues/771
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get("http://example.org/malformed_redirect")
|
||||
assert response.status_code == codes.OK
|
||||
assert response.url == URL("https://example.org/")
|
||||
@ -231,7 +248,7 @@ async def test_malformed_redirect():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_no_scheme_redirect():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get("https://example.org/no_scheme_redirect")
|
||||
assert response.status_code == codes.OK
|
||||
assert response.url == URL("https://example.org/")
|
||||
@ -240,7 +257,7 @@ async def test_no_scheme_redirect():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_fragment_redirect():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get("https://example.org/relative_redirect#fragment")
|
||||
assert response.status_code == codes.OK
|
||||
assert response.url == URL("https://example.org/#fragment")
|
||||
@ -249,7 +266,7 @@ async def test_fragment_redirect():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_multiple_redirects():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
response = await client.get("https://example.org/multiple_redirects?count=20")
|
||||
assert response.status_code == codes.OK
|
||||
assert response.url == URL("https://example.org/multiple_redirects")
|
||||
@ -265,15 +282,15 @@ async def test_multiple_redirects():
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_too_many_redirects():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
async def test_async_too_many_redirects():
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
with pytest.raises(TooManyRedirects):
|
||||
await client.get("https://example.org/multiple_redirects?count=21")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_too_many_redirects_calling_next():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
async def test_async_too_many_redirects_calling_next():
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
url = "https://example.org/multiple_redirects?count=21"
|
||||
response = await client.get(url, allow_redirects=False)
|
||||
with pytest.raises(TooManyRedirects):
|
||||
@ -281,16 +298,31 @@ async def test_too_many_redirects_calling_next():
|
||||
response = await response.anext()
|
||||
|
||||
|
||||
def test_sync_too_many_redirects():
|
||||
client = Client(transport=SyncMockTransport())
|
||||
with pytest.raises(TooManyRedirects):
|
||||
client.get("https://example.org/multiple_redirects?count=21")
|
||||
|
||||
|
||||
def test_sync_too_many_redirects_calling_next():
|
||||
client = Client(transport=SyncMockTransport())
|
||||
url = "https://example.org/multiple_redirects?count=21"
|
||||
response = client.get(url, allow_redirects=False)
|
||||
with pytest.raises(TooManyRedirects):
|
||||
while response.is_redirect:
|
||||
response = response.call_next()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_redirect_loop():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
with pytest.raises(TooManyRedirects):
|
||||
await client.get("https://example.org/redirect_loop")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_cross_domain_redirect():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
url = "https://example.com/cross_domain"
|
||||
headers = {"Authorization": "abc"}
|
||||
response = await client.get(url, headers=headers)
|
||||
@ -300,7 +332,7 @@ async def test_cross_domain_redirect():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_same_domain_redirect():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
url = "https://example.org/cross_domain"
|
||||
headers = {"Authorization": "abc"}
|
||||
response = await client.get(url, headers=headers)
|
||||
@ -313,7 +345,7 @@ async def test_body_redirect():
|
||||
"""
|
||||
A 308 redirect should preserve the request body.
|
||||
"""
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
url = "https://example.org/redirect_body"
|
||||
data = b"Example request body"
|
||||
response = await client.post(url, data=data)
|
||||
@ -327,7 +359,7 @@ async def test_no_body_redirect():
|
||||
"""
|
||||
A 303 redirect should remove the request body.
|
||||
"""
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
url = "https://example.org/redirect_no_body"
|
||||
data = b"Example request body"
|
||||
response = await client.post(url, data=data)
|
||||
@ -338,7 +370,7 @@ async def test_no_body_redirect():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_can_stream_if_no_redirect():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
url = "https://example.org/redirect_301"
|
||||
async with client.stream("GET", url, allow_redirects=False) as response:
|
||||
assert not response.is_closed
|
||||
@ -348,11 +380,11 @@ async def test_can_stream_if_no_redirect():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_cannot_redirect_streaming_body():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
url = "https://example.org/redirect_body"
|
||||
|
||||
async def streaming_body():
|
||||
yield b"Example request body"
|
||||
yield b"Example request body" # pragma: nocover
|
||||
|
||||
with pytest.raises(RequestBodyUnavailable):
|
||||
await client.post(url, data=streaming_body())
|
||||
@ -360,7 +392,7 @@ async def test_cannot_redirect_streaming_body():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_cross_subdomain_redirect():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
url = "https://example.com/cross_subdomain"
|
||||
response = await client.get(url)
|
||||
assert response.url == URL("https://www.example.org/cross_subdomain")
|
||||
@ -447,7 +479,7 @@ async def test_redirect_cookie_behavior():
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_redirect_custom_scheme():
|
||||
client = AsyncClient(transport=MockTransport())
|
||||
client = AsyncClient(transport=AsyncMockTransport())
|
||||
with pytest.raises(InvalidURL) as e:
|
||||
await client.post("https://example.org/redirect_custom_scheme")
|
||||
assert str(e.value) == 'Scheme "market" not supported.'
|
||||
|
||||
@ -10,6 +10,6 @@ import trio
|
||||
|
||||
async def sleep(seconds: float) -> None:
|
||||
if sniffio.current_async_library() == "trio":
|
||||
await trio.sleep(seconds)
|
||||
await trio.sleep(seconds) # pragma: nocover
|
||||
else:
|
||||
await asyncio.sleep(seconds)
|
||||
|
||||
@ -233,7 +233,7 @@ class TestServer(Server):
|
||||
}
|
||||
await asyncio.wait(tasks)
|
||||
|
||||
async def restart(self) -> None:
|
||||
async def restart(self) -> None: # pragma: nocover
|
||||
# This coroutine may be called from a different thread than the one the
|
||||
# server is running on, and from an async environment that's not asyncio.
|
||||
# For this reason, we use an event to coordinate with the server
|
||||
@ -244,7 +244,7 @@ class TestServer(Server):
|
||||
while not self.started:
|
||||
await sleep(0.2)
|
||||
|
||||
async def watch_restarts(self):
|
||||
async def watch_restarts(self): # pragma: nocover
|
||||
while True:
|
||||
if self.should_exit:
|
||||
return
|
||||
|
||||
@ -197,6 +197,14 @@ def test_origin_repr():
|
||||
assert str(origin) == "Origin(scheme='https' host='example.com' port=8080)"
|
||||
|
||||
|
||||
def test_origin_equal():
|
||||
origin1 = Origin("https://example.com")
|
||||
origin2 = Origin("https://example.com")
|
||||
assert origin1 is not origin2
|
||||
assert origin1 == origin2
|
||||
assert len({origin1, origin2}) == 1
|
||||
|
||||
|
||||
def test_url_copywith_for_authority():
|
||||
copy_with_kwargs = {
|
||||
"username": "username",
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
from functools import partial
|
||||
|
||||
import pytest
|
||||
|
||||
import httpx
|
||||
@ -25,8 +27,8 @@ async def echo_body(scope, receive, send):
|
||||
await send({"type": "http.response.body", "body": body, "more_body": more_body})
|
||||
|
||||
|
||||
async def raise_exc(scope, receive, send):
|
||||
raise ValueError()
|
||||
async def raise_exc(scope, receive, send, exc=ValueError):
|
||||
raise exc()
|
||||
|
||||
|
||||
async def raise_exc_after_response(scope, receive, send):
|
||||
@ -62,6 +64,13 @@ async def test_asgi_exc():
|
||||
await client.get("http://www.example.org/")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_asgi_http_error():
|
||||
client = httpx.AsyncClient(app=partial(raise_exc, exc=httpx.HTTPError))
|
||||
with pytest.raises(httpx.HTTPError):
|
||||
await client.get("http://www.example.org/")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_asgi_exc_after_response():
|
||||
client = httpx.AsyncClient(app=raise_exc_after_response)
|
||||
|
||||
@ -67,6 +67,11 @@ def test_get_netrc_login():
|
||||
assert netrc_info.get_credentials("netrcexample.org") == expected_credentials
|
||||
|
||||
|
||||
def test_get_netrc_unknown():
|
||||
netrc_info = NetRCInfo(["tests/.netrc"])
|
||||
assert netrc_info.get_credentials("nonexistant.org") is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value, expected",
|
||||
(
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
import pytest
|
||||
|
||||
@ -51,7 +52,7 @@ def echo_body_with_response_stream(environ, start_response):
|
||||
return output_generator(f=environ["wsgi.input"])
|
||||
|
||||
|
||||
def raise_exc(environ, start_response):
|
||||
def raise_exc(environ, start_response, exc=ValueError):
|
||||
status = "500 Server Error"
|
||||
output = b"Nope!"
|
||||
|
||||
@ -60,8 +61,8 @@ def raise_exc(environ, start_response):
|
||||
]
|
||||
|
||||
try:
|
||||
raise ValueError()
|
||||
except ValueError:
|
||||
raise exc()
|
||||
except exc:
|
||||
exc_info = sys.exc_info()
|
||||
start_response(status, response_headers, exc_info=exc_info)
|
||||
|
||||
@ -95,6 +96,12 @@ def test_wsgi_exc():
|
||||
client.get("http://www.example.org/")
|
||||
|
||||
|
||||
def test_wsgi_http_error():
|
||||
client = httpx.Client(app=partial(raise_exc, exc=httpx.HTTPError))
|
||||
with pytest.raises(httpx.HTTPError):
|
||||
client.get("http://www.example.org/")
|
||||
|
||||
|
||||
def test_wsgi_generator():
|
||||
output = [b"", b"", b"Some content", b" and more content"]
|
||||
client = httpx.Client(app=application_factory(output))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user