Skip to content

utils.py: backward condition silently drops single-line docstring descriptions; HEAD/OPTIONS methods silently ignored #262

@pramodavansaber

Description

@pramodavansaber

Hi luolingchun — found three issues in flask_openapi/utils.py while running adversarial test generation via tailtest. All three are small fixes.

1. get_operation line 58 — backward condition swallows single-line docstring descriptions

doc_description = lines[0] if len(lines) == 0 else "<br/>".join(lines[1:])

doc.split("\n") always returns at least 1 element, so len(lines) == 0 is always False. When a function has a single-line docstring with no explicit summary, doc_description becomes "<br/>".join([]) = "". The description is silently lost from the generated OpenAPI spec.

Suspected intent: len(lines) <= 1 (use the line as description if there's only one).

2. parse_method silently drops HTTPMethod.HEAD

The if/elif ladder handles GET/POST/PUT/PATCH/DELETE. HEAD operations are valid HTTP and the PathItem model has a head field, but routes registered with methods=["HEAD"] are silently dropped — no PathItem entry, no warning.

Fix: add elif method == HTTPMethod.HEAD: paths.setdefault(uri, PathItem()).head = operation

3. parse_method silently drops HTTPMethod.OPTIONS

Same issue as #2 for OPTIONS — PathItem has the field but the parser doesn't populate it.

Reproduction

import pytest
from flask_openapi.models.api_doc import PathItem
from flask_openapi.models.operation import Operation
from flask_openapi.utils import get_operation, parse_method
from flask_openapi.types import HTTPMethod


# Bug 1
def test_single_line_docstring_description_should_not_vanish():
    """get_operation: single-line docstring should NOT lose description.

    Reproduction:
    - Define a function with a single-line docstring (no blank-line separator)
    - Call get_operation(func) without explicit summary kwarg
    - Expected: doc_description == the single docstring line
    - Observed: doc_description == "" (silently lost)
    """
    def my_endpoint():
        """A single-line description."""
        pass
    op = get_operation(my_endpoint)
    assert op.description == "A single-line description.", (
        f"Description silently lost; got: {op.description!r}"
    )


# Bug 2
def test_head_method_silently_ignored():
    """parse_method: HEAD operation should be added to PathItem."""
    paths = {}
    op = Operation(summary="test")
    parse_method("/test", HTTPMethod.HEAD, paths, op)
    assert "/test" in paths, "HEAD method silently ignored by parse_method"


# Bug 3
def test_options_method_silently_ignored():
    """parse_method: OPTIONS operation should be added to PathItem."""
    paths = {}
    op = Operation(summary="test")
    parse_method("/test", HTTPMethod.OPTIONS, paths, op)
    assert "/test" in paths, "OPTIONS method silently ignored by parse_method"

Happy to open a PR if useful. Found via tailtest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions