From 6e7dd39ee7fc8ce5f714442984672aa5a30623e7 Mon Sep 17 00:00:00 2001 From: Julien Schuermans Date: Mon, 9 Dec 2024 10:11:39 +0100 Subject: [PATCH] Return NoContent when mocking HTTP 204 operations (#2000) There's never a need to return a response for HTTP 204 - and these code are unlikely to have any "example" response documented in the spec. Avoids a uvicorn RuntimeError `"Response content longer than Content-Length"` when mocking a (3.0) spec containing: ``` ... responses: '204': description: No Content '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ProblemDetails' ... ``` --- connexion/operations/openapi.py | 7 ++++++- connexion/operations/swagger2.py | 6 ++++++ tests/test_mock.py | 23 +++++++++++++++++++++++ tests/test_mock3.py | 20 ++++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/connexion/operations/openapi.py b/connexion/operations/openapi.py index 88af973c9..edcb9bf1f 100644 --- a/connexion/operations/openapi.py +++ b/connexion/operations/openapi.py @@ -4,8 +4,9 @@ import logging import typing as t +from http import HTTPStatus -from connexion.datastructures import MediaTypeDict +from connexion.datastructures import MediaTypeDict, NoContent from connexion.operations.abstract import AbstractOperation from connexion.uri_parsing import OpenAPIURIParser from connexion.utils import build_example_from_schema, deep_get @@ -170,6 +171,10 @@ def example_response(self, status_code=None, content_type=None): status_code = int(status_code) except ValueError: status_code = 200 + + if status_code == HTTPStatus.NO_CONTENT: + return NoContent, status_code + try: # TODO also use example header? return ( diff --git a/connexion/operations/swagger2.py b/connexion/operations/swagger2.py index 680e29370..7450119e1 100644 --- a/connexion/operations/swagger2.py +++ b/connexion/operations/swagger2.py @@ -4,7 +4,9 @@ import logging import typing as t +from http import HTTPStatus +from connexion.datastructures import NoContent from connexion.exceptions import InvalidSpecification from connexion.operations.abstract import AbstractOperation from connexion.uri_parsing import Swagger2URIParser @@ -196,6 +198,10 @@ def example_response(self, status_code=None, *args, **kwargs): status_code = int(status_code) except ValueError: status_code = 200 + + if status_code == HTTPStatus.NO_CONTENT: + return NoContent, status_code + try: return ( list(deep_get(self._responses, examples_path).values())[0], diff --git a/tests/test_mock.py b/tests/test_mock.py index 2b0492925..8eabcd4fc 100644 --- a/tests/test_mock.py +++ b/tests/test_mock.py @@ -1,3 +1,4 @@ +from connexion.datastructures import NoContent from connexion.mock import MockResolver from connexion.operations import OpenAPIOperation, Swagger2Operation @@ -261,6 +262,28 @@ def test_mock_resolver_no_example_nested_in_list_openapi(): assert all(isinstance(c, str) for c in response) +def test_mock_resolver_no_content(): + resolver = MockResolver(mock_all=True) + + responses = {"204": {}} + + operation = Swagger2Operation( + method="GET", + path="endpoint", + path_parameters=[], + operation={"responses": responses}, + app_produces=["application/json"], + app_consumes=["application/json"], + definitions={}, + resolver=resolver, + ) + assert operation.operation_id == "mock-1" + + response, status_code = resolver.mock_operation(operation) + assert status_code == 204 + assert response == NoContent + + def test_mock_resolver_no_examples(): resolver = MockResolver(mock_all=True) diff --git a/tests/test_mock3.py b/tests/test_mock3.py index 4dc405767..803a51d75 100644 --- a/tests/test_mock3.py +++ b/tests/test_mock3.py @@ -1,3 +1,4 @@ +from connexion.datastructures import NoContent from connexion.mock import MockResolver from connexion.operations import OpenAPIOperation @@ -87,6 +88,25 @@ def test_mock_resolver_inline_schema_example(): assert response == {"foo": "bar"} +def test_mock_resolver_no_content(): + resolver = MockResolver(mock_all=True) + + responses = {"204": {}} + + operation = OpenAPIOperation( + method="GET", + path="endpoint", + path_parameters=[], + operation={"responses": responses}, + resolver=resolver, + ) + assert operation.operation_id == "mock-1" + + response, status_code = resolver.mock_operation(operation) + assert status_code == 204 + assert response == NoContent + + def test_mock_resolver_no_examples(): resolver = MockResolver(mock_all=True)