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:
Josep Cugat 2020-06-02 11:24:45 +02:00 committed by GitHub
parent 8c84210555
commit 620b0670db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 261 additions and 115 deletions

View File

@ -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.

View File

@ -1539,5 +1539,3 @@ class StreamContextManager:
) -> None:
assert isinstance(self.client, AsyncClient)
await self.response.aclose()
if self.close_client:
await self.client.aclose()

View File

@ -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"

View File

@ -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

View File

@ -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"}'}

View File

@ -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")

View File

@ -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.'

View File

@ -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)

View File

@ -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

View File

@ -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",

View File

@ -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)

View File

@ -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",
(

View File

@ -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))