Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bad Request when posting JSON to Flask endpoint in unit test [duplicate]

I found a load of questions about people trying to post JSON to a Flask app, but none of those are quite the problem I have here.

I have a simple REST API, built using Flask and Flask-JWT, which is working fine through a browser, but I've hit a block with my unit tests. The app is an App Engine app and runs fine on the local dev server and on App Engine. I'm running my unit tests with python -m unittest <module>.

I have a basic Flask app created using a simple factory:

def create_app(name=__name__, cfg):
    app = Flask(name)
    app.config.update(cfg)

    CORS(app)
    JWT(app, auth_handler, identity_handler)

    @app.route('/api/v1/ping', methods=['GET'])
    def handle_ping():
        return jsonify({'message': 'pong'})

    # other routes ...

In the above, JWT expects an authentication_handler, which I have implemented:

def auth_handler(identity, secret):
    # validate identity and secret
    return User(...)

My client is Angular.js so I am posting JSON to this endpoint. This is all working fine in a browser, or with curl:

$ curl -H "content-type:application/json" -d '{"username":"[email protected]","password":"crm114"}'   http://localhost:8080/api/v1/auth

giving me a 200 OK and an access token in the response.

My simple test cases go something like this:

conf = {
     'DEBUG': False,
    'TESTING': True,
    'SECRET_KEY': 'MoveAlongTheresNothingToSeeHere',
    'JWT_AUTH_URL_RULE': '/api/v1/auth'
}

class FlaskRouteTests(unittest.TestCase):
    def setUp(self):
        api = factory.create_app(__name__, conf)
        self.app = api.test_client()

    def tearDown(self):
        pass

    def testRoute(self):
        # This test works just fine
        resp = self.app.get('/api/v1/ping')
        self.assertEqual(resp.status, "200 OK", "Status should be 200, was %s" % resp.status)

    def testAuth(self):
        # This test does not work
        resp = self.app.post('http://localhost:8080/api/v1/auth',
                         data="{'username': '[email protected]', 'password': 'crm114'}",
                         content_type='application/json', charset='UTF-8')
        self.assertEqual(resp.status, "200 OK", "Status should be 200, was %s" % resp.status)

The plain old GET test (testRoute()) works just fine but testAuth() gives me a 400 Bad Request and I can't figure out why.

If I look at the werkzeug environ immediately before it sends the request to my Flask app, I see this:

{
 'SERVER_PORT': '8080',
 'SERVER_PROTOCOL': 'HTTP/1.1',
 'SCRIPT_NAME': '',
 'wsgi.input': <_io.BytesIO object at 0x111fa8230>,
 'REQUEST_METHOD': 'POST',
 'HTTP_HOST': 'localhost:8080',
 'PATH_INFO': '/api/v1/auth',
 'wsgi.multithread': False,
 'QUERY_STRING': '',
 'HTTP_CONTENT_TYPE': 'application/json',
 'HTTP_CONTENT_LENGTH': '53',
 'CONTENT_LENGTH': '53',
 'wsgi.version': (1, 0),
 'SERVER_NAME': 'localhost',
 'wsgi.run_once': False,
 'wsgi.errors': <open file '<stderr>', mode 'w' at 0x10fd2d1e0>,
 'wsgi.multiprocess': False,
 'flask._preserve_context': False,
 'wsgi.url_scheme': 'http',
 'CONTENT_TYPE': u'application/json'
}

So, the content-type is correctly set and if I read the contents of the wsgi.input (BytesIO.getvalue()) I see {'username': '[email protected]', 'password': 'crm114'}

The request seems to be failing somewhere before it even hits my auth_handler, so somewhere in werkzeug or Flask.

  • The werkzeug EnvironBuilder interprets the data as a str and converts it to a BytesIO stream - I don't know if this is expected.
  • I've tried POSTing data both as a string: data="{'username': '[email protected]', 'password': 'password'}" and as a dict: data={'username': '[email protected]', 'password': 'password'}, but it seems to make no difference.

So, my question: How do I post JSON to an endpoint in a unit test with Flask.test_client()? Am I missing something obvious?

like image 961
tx802 Avatar asked Sep 25 '22 12:09

tx802


1 Answers

You are not posting valid JSON:

data="{'username': '[email protected]', 'password': 'crm114'}",

Note the single quotes. JSON uses double quotes; the following would be valid JSON:

data='{"username": "[email protected]", "password": "crm114"}',
like image 146
Martijn Pieters Avatar answered Sep 28 '22 06:09

Martijn Pieters