1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
from __future__ import annotations
import sys
import typing
import wsgiref.validate
from functools import partial
from io import StringIO
import pytest
import httpx
if typing.TYPE_CHECKING: # pragma: no cover
from _typeshed.wsgi import StartResponse, WSGIApplication, WSGIEnvironment
def application_factory(output: typing.Iterable[bytes]) -> WSGIApplication:
def application(environ, start_response):
status = "200 OK"
response_headers = [
("Content-type", "text/plain"),
]
start_response(status, response_headers)
for item in output:
yield item
return wsgiref.validate.validator(application)
def echo_body(
environ: WSGIEnvironment, start_response: StartResponse
) -> typing.Iterable[bytes]:
status = "200 OK"
output = environ["wsgi.input"].read()
response_headers = [
("Content-type", "text/plain"),
]
start_response(status, response_headers)
return [output]
def echo_body_with_response_stream(
environ: WSGIEnvironment, start_response: StartResponse
) -> typing.Iterable[bytes]:
status = "200 OK"
response_headers = [("Content-Type", "text/plain")]
start_response(status, response_headers)
def output_generator(f: typing.IO[bytes]) -> typing.Iterator[bytes]:
while True:
output = f.read(2)
if not output:
break
yield output
return output_generator(f=environ["wsgi.input"])
def raise_exc(
environ: WSGIEnvironment,
start_response: StartResponse,
exc: type[Exception] = ValueError,
) -> typing.Iterable[bytes]:
status = "500 Server Error"
output = b"Nope!"
response_headers = [
("Content-type", "text/plain"),
]
try:
raise exc()
except exc:
exc_info = sys.exc_info()
start_response(status, response_headers, exc_info)
return [output]
def log_to_wsgi_log_buffer(environ, start_response):
print("test1", file=environ["wsgi.errors"])
environ["wsgi.errors"].write("test2")
return echo_body(environ, start_response)
def test_wsgi():
transport = httpx.WSGITransport(app=application_factory([b"Hello, World!"]))
client = httpx.Client(transport=transport)
response = client.get("http://www.example.org/")
assert response.status_code == 200
assert response.text == "Hello, World!"
def test_wsgi_upload():
transport = httpx.WSGITransport(app=echo_body)
client = httpx.Client(transport=transport)
response = client.post("http://www.example.org/", content=b"example")
assert response.status_code == 200
assert response.text == "example"
def test_wsgi_upload_with_response_stream():
transport = httpx.WSGITransport(app=echo_body_with_response_stream)
client = httpx.Client(transport=transport)
response = client.post("http://www.example.org/", content=b"example")
assert response.status_code == 200
assert response.text == "example"
def test_wsgi_exc():
transport = httpx.WSGITransport(app=raise_exc)
client = httpx.Client(transport=transport)
with pytest.raises(ValueError):
client.get("http://www.example.org/")
def test_wsgi_http_error():
transport = httpx.WSGITransport(app=partial(raise_exc, exc=RuntimeError))
client = httpx.Client(transport=transport)
with pytest.raises(RuntimeError):
client.get("http://www.example.org/")
def test_wsgi_generator():
output = [b"", b"", b"Some content", b" and more content"]
transport = httpx.WSGITransport(app=application_factory(output))
client = httpx.Client(transport=transport)
response = client.get("http://www.example.org/")
assert response.status_code == 200
assert response.text == "Some content and more content"
def test_wsgi_generator_empty():
output = [b"", b"", b"", b""]
transport = httpx.WSGITransport(app=application_factory(output))
client = httpx.Client(transport=transport)
response = client.get("http://www.example.org/")
assert response.status_code == 200
assert response.text == ""
def test_logging():
buffer = StringIO()
transport = httpx.WSGITransport(app=log_to_wsgi_log_buffer, wsgi_errors=buffer)
client = httpx.Client(transport=transport)
response = client.post("http://www.example.org/", content=b"example")
assert response.status_code == 200 # no errors
buffer.seek(0)
assert buffer.read() == "test1\ntest2"
@pytest.mark.parametrize(
"url, expected_server_port",
[
pytest.param("http://www.example.org", "80", id="auto-http"),
pytest.param("https://www.example.org", "443", id="auto-https"),
pytest.param("http://www.example.org:8000", "8000", id="explicit-port"),
],
)
def test_wsgi_server_port(url: str, expected_server_port: str) -> None:
"""
SERVER_PORT is populated correctly from the requested URL.
"""
hello_world_app = application_factory([b"Hello, World!"])
server_port: str | None = None
def app(environ, start_response):
nonlocal server_port
server_port = environ["SERVER_PORT"]
return hello_world_app(environ, start_response)
transport = httpx.WSGITransport(app=app)
client = httpx.Client(transport=transport)
response = client.get(url)
assert response.status_code == 200
assert response.text == "Hello, World!"
assert server_port == expected_server_port
def test_wsgi_server_protocol():
server_protocol = None
def app(environ, start_response):
nonlocal server_protocol
server_protocol = environ["SERVER_PROTOCOL"]
start_response("200 OK", [("Content-Type", "text/plain")])
return [b"success"]
transport = httpx.WSGITransport(app=app)
with httpx.Client(transport=transport, base_url="http://testserver") as client:
response = client.get("/")
assert response.status_code == 200
assert response.text == "success"
assert server_protocol == "HTTP/1.1"
|