Jon Lasser
Jon Lasser

Reputation: 241

Flask callback not running via pytest

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

Answers (2)

Jon Lasser
Jon Lasser

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

Nick Frost
Nick Frost

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

Related Questions