tornado-openapi3/tests/test_requests.py

197 lines
6.6 KiB
Python
Raw Normal View History

2024-10-18 21:04:45 +00:00
import dataclasses
import http.cookies
import string
import typing
2020-09-17 03:04:11 +00:00
import unittest
2024-10-18 21:04:45 +00:00
import urllib.parse
from hypothesis import given, provisional
import hypothesis.strategies as s
import openapi_core.datatypes
import openapi_core.protocols
from openapi_core.validation.request.datatypes import RequestParameters
import tornado.httpclient
import tornado.httputil
2020-09-17 03:04:11 +00:00
from werkzeug.datastructures import ImmutableMultiDict
2024-10-18 21:04:45 +00:00
import tornado_openapi3.requests
2020-09-17 03:04:11 +00:00
2024-10-18 21:04:45 +00:00
from tests import common
2020-09-17 03:04:11 +00:00
2024-10-18 21:04:45 +00:00
methods = s.sampled_from(
["get", "head", "post", "put", "delete", "connect", "options", "trace", "patch"]
)
2020-09-18 04:01:10 +00:00
2024-10-18 21:04:45 +00:00
queries: s.SearchStrategy[ImmutableMultiDict[str, str]] = s.builds(
ImmutableMultiDict,
s.lists(
s.tuples(common.field_names, common.field_values),
),
)
2024-10-18 21:04:45 +00:00
cookies: s.SearchStrategy[ImmutableMultiDict[str, str]] = s.builds(
ImmutableMultiDict,
s.dictionaries(
s.text(
alphabet=string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~:",
min_size=1,
),
common.field_values,
),
)
2024-10-18 21:04:45 +00:00
request_parameters = s.builds(
RequestParameters,
query=queries,
header=common.headers,
cookie=cookies,
)
2024-10-18 21:04:45 +00:00
@dataclasses.dataclass
class TestOpenAPIRequest:
parameters: openapi_core.datatypes.RequestParameters
method: str
body: typing.Optional[bytes]
content_type: str
host_url: str
path: str
2020-09-18 03:39:35 +00:00
@s.composite
2024-10-18 21:04:45 +00:00
def openapi_requests(
draw: typing.Callable[[typing.Any], typing.Any]
) -> openapi_core.protocols.Request:
url = draw(provisional.urls())
parts = urllib.parse.urlparse(url)
content_type = draw(common.field_values)
parameters = draw(request_parameters)
parameters.header["Content-Type"] = content_type
if parameters.cookie:
cookie = http.cookies.SimpleCookie()
for key, value in parameters.cookie.items():
cookie[key] = value
for header in cookie.output(header="").splitlines():
parameters.header.add_header("Cookie", header.strip())
return TestOpenAPIRequest(
parameters=parameters,
method=draw(methods),
body=draw(s.one_of(s.none(), s.binary())),
content_type=content_type,
host_url="{}://{}".format(parts.scheme, parts.netloc),
path=parts.path,
2020-09-18 04:01:10 +00:00
)
2020-09-18 03:39:35 +00:00
2024-10-18 21:04:45 +00:00
class RequestTests(unittest.TestCase):
def assertOpenAPIRequestsEqual(
self,
value: openapi_core.protocols.Request,
expected: openapi_core.protocols.Request,
) -> None:
self.assertEqual(
value.parameters.query,
expected.parameters.query,
"Query parameters are equal",
)
2024-10-18 21:04:45 +00:00
self.assertEqual(
value.parameters.header, expected.parameters.header, "Headers are equal"
)
2024-10-18 21:04:45 +00:00
self.assertEqual(
value.parameters.cookie, expected.parameters.cookie, "Cookies are equal"
2020-09-17 03:04:11 +00:00
)
2024-10-18 21:04:45 +00:00
self.assertEqual(value.method, expected.method, "HTTP methods are equal")
self.assertEqual(value.body, expected.body, "Bodies are equal")
self.assertEqual(
value.content_type, expected.content_type, "Content types are equal"
2020-12-04 18:37:25 +00:00
)
2024-10-18 21:04:45 +00:00
self.assertEqual(value.host_url, expected.host_url, "Host URLs are equal")
self.assertEqual(value.path, expected.path, "Paths are equal")
def url_from_openapi_request(self, request: TestOpenAPIRequest) -> str:
scheme, netloc = request.host_url.split("://")
params = ""
# Preserves multiple values if the parameters are a multidict. This
# whole dance is because ImmutableMultiDict's .items() does not return
# more than one pair per key. Curiously, the Headers structure from the
# same library does.
qsl: typing.List[typing.Tuple[str, str]] = []
query_parameters = ImmutableMultiDict(request.parameters.query)
for key in query_parameters.keys():
for value in query_parameters.getlist(key):
qsl.append((key, value))
query = urllib.parse.urlencode(qsl)
fragment = ""
return urllib.parse.urlunparse(
(
scheme,
netloc,
request.path,
params,
query,
fragment,
)
2020-09-17 03:04:11 +00:00
)
2020-09-18 03:39:35 +00:00
2024-10-18 21:04:45 +00:00
def tornado_headers_from_openapi_request(
self, request: TestOpenAPIRequest
) -> tornado.httputil.HTTPHeaders:
headers = tornado.httputil.HTTPHeaders()
for key, value in request.parameters.header.items():
headers.add(key, value)
headers["Content-Type"] = request.content_type
if request.parameters.cookie:
cookie = http.cookies.SimpleCookie()
for key, value in request.parameters.cookie.items():
cookie[key] = value
for header in cookie.output(header="").splitlines():
headers.add("Cookie", header.strip())
return headers
def openapi_to_tornado_request(
self, request: TestOpenAPIRequest
) -> tornado.httpclient.HTTPRequest:
url = self.url_from_openapi_request(request)
headers = self.tornado_headers_from_openapi_request(request)
return tornado.httpclient.HTTPRequest(
url,
method=request.method.upper(),
headers=headers,
body=request.body,
2020-09-18 03:39:35 +00:00
)
2020-09-18 04:01:10 +00:00
2024-10-18 21:04:45 +00:00
def openapi_to_tornado_server_request(
self, request: TestOpenAPIRequest
) -> tornado.httputil.HTTPServerRequest:
url = self.url_from_openapi_request(request)
headers = self.tornado_headers_from_openapi_request(request)
uri = url.removeprefix(request.host_url)
server_request = tornado.httputil.HTTPServerRequest(
method=request.method.upper(), uri=uri, headers=headers, body=request.body
2020-09-18 04:01:10 +00:00
)
2024-10-18 21:04:45 +00:00
scheme, netloc = request.host_url.split("://")
server_request.protocol = scheme
server_request.host = netloc
return server_request
@given(openapi_requests())
def test_http_request_round_trip_conversion(
self, request: TestOpenAPIRequest
) -> None:
converted = tornado_openapi3.requests.TornadoOpenAPIRequest(
self.openapi_to_tornado_request(request)
2020-09-20 00:38:08 +00:00
)
2024-10-18 21:04:45 +00:00
self.assertOpenAPIRequestsEqual(converted, request)
2020-09-20 00:38:08 +00:00
2024-10-18 21:04:45 +00:00
@given(openapi_requests())
def test_http_server_request_round_trip_conversion(
self, request: TestOpenAPIRequest
) -> None:
# HTTP Server request bodies are not optional
request.body = request.body or b""
converted = tornado_openapi3.requests.TornadoOpenAPIRequest(
self.openapi_to_tornado_server_request(request)
2020-09-20 00:38:08 +00:00
)
2024-10-18 21:04:45 +00:00
self.assertOpenAPIRequestsEqual(converted, request)