Skip to content

Commit

Permalink
Return NoContent when mocking HTTP 204 operations (#2000)
Browse files Browse the repository at this point in the history
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'
...
```
  • Loading branch information
julienschuermans authored Dec 9, 2024
1 parent 087f05f commit 6e7dd39
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 1 deletion.
7 changes: 6 additions & 1 deletion connexion/operations/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 (
Expand Down
6 changes: 6 additions & 0 deletions connexion/operations/swagger2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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],
Expand Down
23 changes: 23 additions & 0 deletions tests/test_mock.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from connexion.datastructures import NoContent
from connexion.mock import MockResolver
from connexion.operations import OpenAPIOperation, Swagger2Operation

Expand Down Expand Up @@ -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)

Expand Down
20 changes: 20 additions & 0 deletions tests/test_mock3.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from connexion.datastructures import NoContent
from connexion.mock import MockResolver
from connexion.operations import OpenAPIOperation

Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit 6e7dd39

Please sign in to comment.