From b901c4003c69fb6b935e2fe464c44ea21526107e Mon Sep 17 00:00:00 2001 From: p1c2u Date: Thu, 2 Feb 2023 06:21:48 +0000 Subject: [PATCH] request response binary format integration tests --- openapi_core/contrib/django/responses.py | 19 +++++-- tests/integration/conftest.py | 14 ++++++ .../data/v3.0/djangoproject/pets/views.py | 42 ++++++++++++++++ .../contrib/django/test_django_project.py | 29 +++++++++++ tests/integration/data/v3.0/petstore.yaml | 50 +++++++++++++++++++ tests/integration/schema/test_spec.py | 10 ++-- 6 files changed, 156 insertions(+), 8 deletions(-) diff --git a/openapi_core/contrib/django/responses.py b/openapi_core/contrib/django/responses.py index c1c09256..19e2a8d0 100644 --- a/openapi_core/contrib/django/responses.py +++ b/openapi_core/contrib/django/responses.py @@ -1,18 +1,31 @@ """OpenAPI core contrib django responses module""" +from itertools import tee +from typing import Union + from django.http.response import HttpResponse +from django.http.response import StreamingHttpResponse from werkzeug.datastructures import Headers +DjangoResponse = Union[HttpResponse, StreamingHttpResponse] + class DjangoOpenAPIResponse: - def __init__(self, response: HttpResponse): - if not isinstance(response, HttpResponse): + def __init__(self, response: DjangoResponse): + if not isinstance(response, (HttpResponse, StreamingHttpResponse)): raise TypeError( - f"'response' argument is not type of {HttpResponse}" + f"'response' argument is not type of (Streaming)HttpResponse" ) self.response = response @property def data(self) -> str: + if isinstance(self.response, StreamingHttpResponse): + _, response_iterator = tee(self.response._iterator) + content = b"".join( + map(self.response.make_bytes, response_iterator) + ) + return content.decode("utf-8") + assert isinstance(self.response.content, bytes) return self.response.content.decode("utf-8") diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 0fe4a4ba..fc0b3f05 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,3 +1,4 @@ +from base64 import b64decode from os import path from urllib import request @@ -25,6 +26,19 @@ def spec_from_url(spec_url): return Spec.from_dict(spec_dict, spec_url=spec_url) +@pytest.fixture(scope="session") +def data_gif(): + return b64decode( + """ +R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d +3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA +AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg +EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD +Fzk0lpcjIQA7 +""" + ) + + class Factory(dict): __getattr__ = dict.__getitem__ __setattr__ = dict.__setitem__ diff --git a/tests/integration/contrib/django/data/v3.0/djangoproject/pets/views.py b/tests/integration/contrib/django/data/v3.0/djangoproject/pets/views.py index 8e4b38fd..5144341e 100644 --- a/tests/integration/contrib/django/data/v3.0/djangoproject/pets/views.py +++ b/tests/integration/contrib/django/data/v3.0/djangoproject/pets/views.py @@ -1,3 +1,7 @@ +from base64 import b64decode + +from django.conf import settings +from django.http import FileResponse from django.http import HttpResponse from django.http import JsonResponse from rest_framework.views import APIView @@ -76,6 +80,44 @@ def get(self, request, petId): } django_response = JsonResponse(response_dict) django_response["X-Rate-Limit"] = "12" + return django_response + + @staticmethod + def get_extra_actions(): + return [] + + +class PetPhotoView(APIView): + + OPENID_LOGO = b64decode( + """ +R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d +3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA +AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg +EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD +Fzk0lpcjIQA7 +""" + ) + + def get(self, request, petId): + assert request.openapi + assert not request.openapi.errors + assert request.openapi.parameters.path == { + "petId": 12, + } + django_response = FileResponse( + [self.OPENID_LOGO], + content_type="image/gif", + ) + return django_response + + def post(self, request): + assert request.openapi + assert not request.openapi.errors + + # implement file upload here + + django_response = HttpResponse(status=201) return django_response diff --git a/tests/integration/contrib/django/test_django_project.py b/tests/integration/contrib/django/test_django_project.py index f66779ee..07181613 100644 --- a/tests/integration/contrib/django/test_django_project.py +++ b/tests/integration/contrib/django/test_django_project.py @@ -373,3 +373,32 @@ def test_post_valid(self, api_client): assert response.status_code == 201 assert not response.content + + +class TestPetPhotoView(BaseTestDjangoProject): + @pytest.mark.xfail(reason="response binary format not supported") + def test_get_valid(self, client): + headers = { + "HTTP_AUTHORIZATION": "Basic testuser", + "HTTP_HOST": "petstore.swagger.io", + } + response = client.get("/v1/pets/12/photo", **headers) + + assert response.status_code == 200 + assert response.content + + @pytest.mark.xfail(reason="request binary format not supported") + def test_post_valid(self, client, data_gif): + client.cookies.load({"user": 1}) + content_type = "image/gif" + headers = { + "HTTP_AUTHORIZATION": "Basic testuser", + "HTTP_HOST": "petstore.swagger.io", + "HTTP_API_KEY": self.api_key_encoded, + } + response = client.post( + "/v1/pets/12/photo", data_gif, content_type, secure=True, **headers + ) + + assert response.status_code == 201 + assert not response.content diff --git a/tests/integration/data/v3.0/petstore.yaml b/tests/integration/data/v3.0/petstore.yaml index 9abcd791..282b880d 100644 --- a/tests/integration/data/v3.0/petstore.yaml +++ b/tests/integration/data/v3.0/petstore.yaml @@ -173,6 +173,56 @@ paths: format: binary default: $ref: "#/components/responses/ErrorResponse" + /pets/{petId}/photo: + get: + summary: Photo for a specific pet + operationId: showPetPhotoById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: integer + format: int64 + responses: + '200': + description: Expected response to a valid request + content: + image/*: + schema: + type: string + format: binary + default: + $ref: "#/components/responses/ErrorResponse" + post: + summary: Create a pet photo + description: Creates new pet photo entry + operationId: createPetPhotoById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: integer + format: int64 + requestBody: + required: true + content: + image/*: + schema: + type: string + format: binary + responses: + '201': + description: Null response + default: + $ref: "#/components/responses/ErrorResponse" /tags: get: summary: List all tags diff --git a/tests/integration/schema/test_spec.py b/tests/integration/schema/test_spec.py index dbfe7966..ec2e4595 100644 --- a/tests/integration/schema/test_spec.py +++ b/tests/integration/schema/test_spec.py @@ -283,13 +283,13 @@ def test_spec(self, spec, spec_dict): if "$ref" in schema_spec: continue - schema = content.get("schema") + schema = media_type.get("schema") assert bool(schema_spec) == bool(schema) - assert schema.type.value == schema_spec["type"] - assert schema.format == schema_spec.get("format") - assert schema.required == schema_spec.get( - "required", False + assert schema["type"] == schema_spec["type"] + assert schema.getkey("format") == schema_spec.get("format") + assert schema.getkey("required") == schema_spec.get( + "required" ) components = spec.get("components")