Reputation: 241
I'm using the Deferred Callback Pattern snippet to set a cookie in Flask. (Currently using 11.1 but tried 12.0 also with no change in behavior.)
I've added print statements to be certain, but can confirm by other means (checking the cookie) that the callback gets run as expected when my application runs either in gunicorn or Flask's debug server.
When running in pytest (3.0.5), I can confirm that the request executes in the test because the command output is checked by the test, and matches.
However, while the callback is added it does not run. (Confirmed both via Print statements and checking the test client's cookie jar.)
The test code is:
def test_get_new_auth_happy_path_basic_auth_preloaded(monkeypatch, client):
'''
Test that, when a client requests a token, and that client already
has the 'basic auth' credentials preloaded with valid values, the
client response includes the cookie setting with the matching token.
'''
monkeypatch.setattr('powersvc.auth.get_public_key',
mock.Mock(return_value=SECRET))
monkeypatch.setattr('powersvc.auth.get_token', mock_get_token)
basic_auth = "Basic {user}".format(user=b64encode(
b"{username}:{password}".format(username=USERNAME,
password=PASSWORD)))
res = client.get("/v1.1/token", headers={
"Authorization": basic_auth,
})
assert res.status_code == 200
assert res.get_data() == str(TOKEN)
assert auth.TOKEN_HEADER in client.cookie_jar
The first two asserts pass, and the third fails.
[EDIT: the third test above is wrong, but running pytest with --pdb I can also see that the cookie isn't set:
(Pdb) print res.headers
Content-Type: text/html; charset=utf-8
Content-Length: 109
(Pdb)
Thanks to Nick Frost for pointing this out.]
I've tried rewriting the test two different ways--using the pytest-flask live_server fixture (Currently on pytest-flask 0.10.0), and using only basic pytest with none of the pytest-flask fixtures--and they all fail the same way: callback added, callback not executed, and cookie correspondingly not there.
Again, the code works when executing normally, only not when executed via pytest.
For completeness, here's the /v1.1/token function that adds the callback. (Callback code is identical to the snippet above except for logging statements.):
def get_new_auth():
'''
Attempt to get a new token with a username and password via Basic Auth
and put this new token in a cookie.
If we get a valid token, return its decrypted form.
If no valid token is possible, return None.
'''
LOGGER.debug('In get_new_auth.')
auth = flask.request.authorization
if auth:
token = get_token(auth.username, auth.password)
LOGGER.debug('Auth found. New token is {}'.format(token))
# Set a callback to ensure the token cookie gets set
@server.after_this_request
def add_token(response):
response.set_cookie(TOKEN_HEADER, token)
return str(decrypt_token(token))
else: # No basic auth in request
LOGGER.debug('No auth information available - demanding new auth')
return demand_authentication()
Upvotes: 2
Views: 511
Reputation: 241
Whoops. More interactive investigation uncovered the problem:
The deferred callback snippet uses the @app.after_request decorator -- but that 'app' variable isn't referring to the factory-generated app I'm using in my pytest fixture.
Ouch.
Upvotes: 1
Reputation: 490
Using your code with the Flask snippet code, it does indeed look like the cookie gets set. However, it looks like there's a problem with your test code.
It turns out that 'cookie_name' in client.cookie_jar
won't tell you whether the cookie is in the cookie jar. You could instead check the Set-Cookie
header directly to see whether the cookie was set:
assert 'Set-Cookie' in res.headers
assert res.headers['Set-Cookie'].startswith(auth.TOKEN_HEADER + '=')
Alternatively, you can use the CookieJar internals directly to check whether client
has the token in its cookie jar:
assert auth.TOKEN_HEADER in client.cookie_jar._cookies['localhost.local']['/']
EDIT: you could also create a flask.Response
object and return that instead of going through a callback:
token = get_token(auth.username, auth.password)
LOGGER.debug('Auth found. New token is {}'.format(token))
res = Response(str(decrypt_token(token)))
res.set_cookie(TOKEN_HEADER, token)
return res
Upvotes: 1