flask-restx
flask-restx copied to clipboard
Header X-Fields breaks on Schemathesis tests
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
bug still not fixed :-(
For now, I provided RESTX_MASK_HEADER = "X-Fields"
as an environment variable, and it works without any issues.