flask-restx icon indicating copy to clipboard operation
flask-restx copied to clipboard

Header X-Fields breaks on Schemathesis tests

Open GvandeSteeg opened this issue 3 years ago • 2 comments

Field masks should not be implicitly provided, but only explicitly set by the user

Code

from flask import Flask
from flask_restx import Api, Resource, fields

from werkzeug.middleware.proxy_fix import ProxyFix

app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
api = Api(
    app,
    version="1.0",
    title="TodoMVC API",
    description="A simple TodoMVC API",
)

ns = api.namespace("todos", description="TODO operations")

todo = api.model(
    "Todo",
    {
        "id": fields.Integer(readonly=True, description="The task unique identifier"),
        "task": fields.String(required=True, description="The task details"),
    },
)


class TodoDAO(object):
    def __init__(self):
        self.counter = 0
        self.todos = []

    def get(self, id):
        for todo in self.todos:
            if todo["id"] == id:
                return todo
        api.abort(404, "Todo {} doesn't exist".format(id))

    def create(self, data):
        if data["task"] == "":
            api.abort(400, "Task cannot be empty")
        todo = data
        todo["id"] = self.counter = self.counter + 1
        self.todos.append(todo)
        return todo

    def update(self, id, data):
        todo = self.get(id)
        todo.update(data)
        return todo

    def delete(self, id):
        todo = self.get(id)
        self.todos.remove(todo)


DAO = TodoDAO()
DAO.create({"task": "Build an API"})
DAO.create({"task": "?????"})
DAO.create({"task": "profit!"})


@ns.route("/")
class TodoList(Resource):
    """Shows a list of all todos, and lets you POST to add new tasks"""

    @ns.doc("list_todos")
    @ns.marshal_list_with(todo)
    def get(self):
        """List all tasks"""
        return DAO.todos

    @ns.doc("create_todo")
    @ns.expect(todo)
    @ns.marshal_with(todo, code=201)
    @ns.response(400, "Invalid body")
    def post(self):
        """Create a new task"""
        return DAO.create(api.payload), 201


# Have to set signed=True otherwise negative integers will throw a 404
# which breaks Schemathesis tests
@ns.route("/<int(signed=True):id>")
@ns.response(404, "Todo not found")
@ns.param("id", "The task identifier")
class Todo(Resource):
    """Show a single todo item and lets you delete them"""

    @ns.doc("get_todo")
    @ns.marshal_with(todo)
    def get(self, id):
        """Fetch a given resource"""
        return DAO.get(id)

    @ns.doc("delete_todo")
    @ns.response(204, "Todo deleted")
    def delete(self, id):
        """Delete a task given its identifier"""
        DAO.delete(id)
        return "", 204

    @ns.expect(todo)
    @ns.marshal_with(todo)
    def put(self, id):
        """Update a task given its identifier"""
        return DAO.update(id, api.payload)


if __name__ == "__main__":
    app.run(debug=True)

Repro Steps (if applicable)

In two separate terminals, run:

python api.py
schemathesis run -c response_schema_conformance -M GET http://localhost:5000/swagger.json --code-sample-style curl

Expected Behavior

X-fields set to a non-matching field should be ignored and instead return the full body

Actual Behavior

X-fields set to a non-matching field returns an empty body, which 'breaks' the swagger docs schema as "tasks" is required in the response.

Error Messages/Stack Trace

======================================================================================================================= Schemathesis test session starts =======================================================================================================================
platform Darwin -- Python 3.8.2, schemathesis-3.6.3, hypothesis-6.10.1, hypothesis_jsonschema-0.19.1, jsonschema-3.2.0
rootdir: /Users/gv4/PycharmProjects/scrap
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/Users/gv4/PycharmProjects/scrap/.hypothesis/examples')
Schema location: http://localhost:5000/swagger.json
Base URL: http://localhost:5000/
Specification version: Swagger 2.0
Workers: 1
Collected API operations: 2

GET /todos/ F                                                                                                                                                                                                                                                             [ 50%]
GET /todos/{id} F                                                                                                                                                                                                                                                         [100%]

=================================================================================================================================== FAILURES ===================================================================================================================================
_______________________________________________________________________________________________________________________________ GET /todos/ [P] ________________________________________________________________________________________________________________________________
1. The received response does not conform to the defined schema!

Details:

'task' is a required property

Failed validating 'required' in schema['items']

Headers         : {'X-Fields': '0', 'User-Agent': 'schemathesis/3.6.3', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}

----------

Response payload: `[
    {},
    {},
    {}
]
`

Run this cURL command to reproduce this failure:

    curl -X GET -H 'Accept: */*' -H 'Accept-Encoding: gzip, deflate' -H 'Connection: keep-alive' -H 'User-Agent: schemathesis/3.6.3' -H 'X-Fields: 0' http://localhost:5000/todos/

Or add this option to your command line parameters: --hypothesis-seed=236577416839207515169615362139774039480

Environment

Python 3.8.2 Flask 1.1.2 Werkzeug 1.0.1 Flask-restx 0.3.0

Additional Context

I'm trying to learn Schemathesis using the Quick Example and noticed that the created schema fails when the header "X-Fields: 0" is provided. I don't want to tell Schemathesis not to, as this is something a user could provide themselves. Ideally I just don't want X-fields to be used at all

GvandeSteeg avatar Apr 29 '21 15:04 GvandeSteeg

bug still not fixed :-(

Kariyma avatar Oct 22 '22 18:10 Kariyma

For now, I provided RESTX_MASK_HEADER = "X-Fields" as an environment variable, and it works without any issues.

sandeepmachiraju avatar Jan 13 '23 20:01 sandeepmachiraju