pythonunit-testinggoogle-app-engineflaskflask-jwt

Bad Request when posting JSON to Flask endpoint in unit test


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":"jackripper@example.com","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': 'jackripper@example.com', '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': 'jackripper@example.com', 'password': 'crm114'}

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

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


Solution

  • You are not posting valid JSON:

    data="{'username': 'jackripper@example.com', 'password': 'crm114'}",
    

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

    data='{"username": "jackripper@example.com", "password": "crm114"}',