Skip to content

Commit cbbe084

Browse files
authored
Merge pull request #496 from python-openapi/feature/request-response-binary-format-integration-tests
request response binary format integration tests
2 parents 3d6237c + e2a9e30 commit cbbe084

File tree

32 files changed

+751
-10
lines changed

32 files changed

+751
-10
lines changed

Diff for: openapi_core/contrib/starlette/requests.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def body(self) -> Optional[str]:
4545

4646
@property
4747
def mimetype(self) -> str:
48-
content_type = self.request.headers["Content-Type"]
48+
content_type = self.request.headers.get("Content-Type")
4949
if content_type:
5050
return content_type.partition(";")[0]
5151

Diff for: tests/integration/conftest.py

+14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from base64 import b64decode
12
from os import path
23
from urllib import request
34

@@ -25,6 +26,19 @@ def spec_from_url(base_uri):
2526
return Spec.from_dict(spec_dict, base_uri=base_uri)
2627

2728

29+
@pytest.fixture(scope="session")
30+
def data_gif():
31+
return b64decode(
32+
"""
33+
R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d
34+
3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA
35+
AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg
36+
EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD
37+
Fzk0lpcjIQA7
38+
"""
39+
)
40+
41+
2842
class Factory(dict):
2943
__getattr__ = dict.__getitem__
3044
__setattr__ = dict.__setitem__

Diff for: tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from aiohttp import web
2+
from aiohttpproject.pets.views import PetPhotoView
3+
4+
routes = [
5+
web.view("/v1/pets/{petId}/photo", PetPhotoView),
6+
]
7+
8+
9+
def get_app(loop=None):
10+
app = web.Application(loop=loop)
11+
app.add_routes(routes)
12+
return app
13+
14+
15+
app = get_app()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from pathlib import Path
2+
3+
import yaml
4+
5+
from openapi_core import Spec
6+
7+
openapi_spec_path = Path("tests/integration/data/v3.0/petstore.yaml")
8+
spec_dict = yaml.load(openapi_spec_path.read_text(), yaml.Loader)
9+
spec = Spec.from_dict(spec_dict)

Diff for: tests/integration/contrib/aiohttp/data/v3.0/aiohttpproject/pets/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from base64 import b64decode
2+
from io import BytesIO
3+
4+
from aiohttp import web
5+
from aiohttpproject.openapi import spec
6+
from multidict import MultiDict
7+
8+
from openapi_core import unmarshal_request
9+
from openapi_core import unmarshal_response
10+
from openapi_core.contrib.aiohttp import AIOHTTPOpenAPIWebRequest
11+
from openapi_core.contrib.aiohttp import AIOHTTPOpenAPIWebResponse
12+
13+
14+
class PetPhotoView(web.View):
15+
OPENID_LOGO = b64decode(
16+
"""
17+
R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d
18+
3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA
19+
AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg
20+
EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD
21+
Fzk0lpcjIQA7
22+
"""
23+
)
24+
25+
async def get(self):
26+
request_body = await self.request.text()
27+
openapi_request = AIOHTTPOpenAPIWebRequest(
28+
self.request, body=request_body
29+
)
30+
request_unmarshalled = unmarshal_request(openapi_request, spec=spec)
31+
response = web.Response(
32+
body=self.OPENID_LOGO,
33+
content_type="image/gif",
34+
)
35+
openapi_response = AIOHTTPOpenAPIWebResponse(response)
36+
response_unmarshalled = unmarshal_response(
37+
openapi_request, openapi_response, spec=spec
38+
)
39+
return response
40+
41+
async def post(self):
42+
request_body = await self.request.read()
43+
openapi_request = AIOHTTPOpenAPIWebRequest(
44+
self.request, body=request_body
45+
)
46+
request_unmarshalled = unmarshal_request(openapi_request, spec=spec)
47+
response = web.Response(status=201)
48+
openapi_response = AIOHTTPOpenAPIWebResponse(response)
49+
response_unmarshalled = unmarshal_response(
50+
openapi_request, openapi_response, spec=spec
51+
)
52+
return response
+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import os
2+
import sys
3+
from base64 import b64encode
4+
5+
import pytest
6+
from starlette.testclient import TestClient
7+
8+
9+
@pytest.fixture(autouse=True, scope="session")
10+
def project_setup():
11+
directory = os.path.abspath(os.path.dirname(__file__))
12+
project_dir = os.path.join(directory, "data/v3.0")
13+
sys.path.insert(0, project_dir)
14+
yield
15+
sys.path.remove(project_dir)
16+
17+
18+
@pytest.fixture
19+
def app(project_setup, loop):
20+
from aiohttpproject.__main__ import get_app
21+
22+
return get_app(loop=loop)
23+
24+
25+
@pytest.fixture
26+
async def client(app, aiohttp_client):
27+
return await aiohttp_client(app)
28+
29+
30+
class BaseTestPetstore:
31+
api_key = "12345"
32+
33+
@property
34+
def api_key_encoded(self):
35+
api_key_bytes = self.api_key.encode("utf8")
36+
api_key_bytes_enc = b64encode(api_key_bytes)
37+
return str(api_key_bytes_enc, "utf8")
38+
39+
40+
class TestPetPhotoView(BaseTestPetstore):
41+
@pytest.mark.xfail(
42+
reason="response binary format not supported",
43+
strict=True,
44+
)
45+
async def test_get_valid(self, client, data_gif):
46+
headers = {
47+
"Authorization": "Basic testuser",
48+
"Api-Key": self.api_key_encoded,
49+
"Host": "petstore.swagger.io",
50+
}
51+
52+
cookies = {"user": "1"}
53+
response = await client.get(
54+
"/v1/pets/1/photo",
55+
headers=headers,
56+
cookies=cookies,
57+
)
58+
59+
assert await response.content.read() == data_gif
60+
assert response.status == 200
61+
62+
async def test_post_valid(self, client, data_gif):
63+
content_type = "image/gif"
64+
headers = {
65+
"Authorization": "Basic testuser",
66+
"Api-Key": self.api_key_encoded,
67+
"Content-Type": content_type,
68+
"Host": "petstore.swagger.io",
69+
}
70+
data = {
71+
"file": data_gif,
72+
}
73+
74+
cookies = {"user": "1"}
75+
response = await client.post(
76+
"/v1/pets/1/photo",
77+
headers=headers,
78+
data=data,
79+
cookies=cookies,
80+
)
81+
82+
assert not await response.text()
83+
assert response.status == 201

Diff for: tests/integration/contrib/django/data/v3.0/djangoproject/pets/views.py

+41
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from base64 import b64decode
2+
3+
from django.conf import settings
4+
from django.http import FileResponse
15
from django.http import HttpResponse
26
from django.http import JsonResponse
37
from rest_framework.views import APIView
@@ -76,6 +80,43 @@ def get(self, request, petId):
7680
}
7781
django_response = JsonResponse(response_dict)
7882
django_response["X-Rate-Limit"] = "12"
83+
return django_response
84+
85+
@staticmethod
86+
def get_extra_actions():
87+
return []
88+
89+
90+
class PetPhotoView(APIView):
91+
OPENID_LOGO = b64decode(
92+
"""
93+
R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d
94+
3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA
95+
AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg
96+
EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD
97+
Fzk0lpcjIQA7
98+
"""
99+
)
100+
101+
def get(self, request, petId):
102+
assert request.openapi
103+
assert not request.openapi.errors
104+
assert request.openapi.parameters.path == {
105+
"petId": 12,
106+
}
107+
django_response = FileResponse(
108+
[self.OPENID_LOGO],
109+
content_type="image/gif",
110+
)
111+
return django_response
112+
113+
def post(self, request):
114+
assert request.openapi
115+
assert not request.openapi.errors
116+
117+
# implement file upload here
118+
119+
django_response = HttpResponse(status=201)
79120

80121
return django_response
81122

Diff for: tests/integration/contrib/django/data/v3.0/djangoproject/urls.py

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from django.urls import path
1919
from djangoproject.pets.views import PetDetailView
2020
from djangoproject.pets.views import PetListView
21+
from djangoproject.pets.views import PetPhotoView
2122
from djangoproject.tags.views import TagListView
2223

2324
urlpatterns = [
@@ -36,6 +37,11 @@
3637
PetDetailView.as_view(),
3738
name="pet_detail_view",
3839
),
40+
path(
41+
"v1/pets/<int:petId>/photo",
42+
PetPhotoView.as_view(),
43+
name="pet_photo_view",
44+
),
3945
path(
4046
"v1/tags",
4147
TagListView.as_view(),

Diff for: tests/integration/contrib/django/test_django_project.py

+35
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,38 @@ def test_get_skip_response_validation(self, client):
395395

396396
assert response.status_code == 200
397397
assert response.content == b"success"
398+
399+
400+
class TestPetPhotoView(BaseTestDjangoProject):
401+
@pytest.mark.xfail(
402+
reason="response binary format not supported",
403+
strict=True,
404+
)
405+
def test_get_valid(self, client, data_gif):
406+
headers = {
407+
"HTTP_AUTHORIZATION": "Basic testuser",
408+
"HTTP_HOST": "petstore.swagger.io",
409+
}
410+
response = client.get("/v1/pets/12/photo", **headers)
411+
412+
assert response.status_code == 200
413+
assert b"".join(list(response.streaming_content)) == data_gif
414+
415+
@pytest.mark.xfail(
416+
reason="request binary format not supported",
417+
strict=True,
418+
)
419+
def test_post_valid(self, client, data_gif):
420+
client.cookies.load({"user": 1})
421+
content_type = "image/gif"
422+
headers = {
423+
"HTTP_AUTHORIZATION": "Basic testuser",
424+
"HTTP_HOST": "petstore.swagger.io",
425+
"HTTP_API_KEY": self.api_key_encoded,
426+
}
427+
response = client.post(
428+
"/v1/pets/12/photo", data_gif, content_type, secure=True, **headers
429+
)
430+
431+
assert response.status_code == 201
432+
assert not response.content

Diff for: tests/integration/contrib/falcon/data/v3.0/falconproject/__main__.py

+3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
from falconproject.openapi import openapi_middleware
33
from falconproject.pets.resources import PetDetailResource
44
from falconproject.pets.resources import PetListResource
5+
from falconproject.pets.resources import PetPhotoResource
56

67
app = App(middleware=[openapi_middleware])
78

89
pet_list_resource = PetListResource()
910
pet_detail_resource = PetDetailResource()
11+
pet_photo_resource = PetPhotoResource()
1012

1113
app.add_route("/v1/pets", pet_list_resource)
1214
app.add_route("/v1/pets/{petId}", pet_detail_resource)
15+
app.add_route("/v1/pets/{petId}/photo", pet_photo_resource)

Diff for: tests/integration/contrib/falcon/data/v3.0/falconproject/pets/resources.py

+22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
from base64 import b64decode
12
from json import dumps
23

4+
from falcon.constants import MEDIA_JPEG
35
from falcon.constants import MEDIA_JSON
46
from falcon.status_codes import HTTP_200
57
from falcon.status_codes import HTTP_201
@@ -74,3 +76,23 @@ def on_get(self, request, response, petId=None):
7476
response.content_type = MEDIA_JSON
7577
response.text = dumps({"data": data})
7678
response.set_header("X-Rate-Limit", "12")
79+
80+
81+
class PetPhotoResource:
82+
OPENID_LOGO = b64decode(
83+
"""
84+
R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d
85+
3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA
86+
AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg
87+
EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD
88+
Fzk0lpcjIQA7
89+
"""
90+
)
91+
92+
def on_get(self, request, response, petId=None):
93+
response.content_type = MEDIA_JPEG
94+
response.stream = [self.OPENID_LOGO]
95+
96+
def on_post(self, request, response, petId=None):
97+
data = request.stream.read()
98+
response.status = HTTP_201

0 commit comments

Comments
 (0)