0

I have a flask app based on the tutorial here https://flask.palletsprojects.com/en/stable/tutorial/. The tutorial comes with a test suite but having adapted the app to use WTForms the tests that post form data stopped working. I can see that's because none of the forms validate as they don't include a CSRF token. How can I add the token to the post data being sent? I came across this rather old gist showing how to do it but it appears to be out of date for the version of Werkzeug I'm using, as it refers to self.cookie_jar which has been removed -- https://gist.github.com/singingwolfboy/2fca1de64950d5dfed72.

I'm using Flask==3.1.0 flask_wtf==1.2.2 pytest==8.3.4 selenium==4.29.0 Werkzeug==3.1.3 WTForms==3.2.1

tests/conftest.py

import os
import tempfile

import pytest
from flaskr import create_app
from flaskr.db import get_db, init_db

with open(os.path.join(os.path.dirname(__file__), 'data.sql'), 'rb') as f:
    _data_sql = f.read().decode('utf8')


@pytest.fixture
def app():
    db_fd, db_path = tempfile.mkstemp()

    app = create_app({
        'TESTING': True,
        'DATABASE': db_path,
    })

    with app.app_context():
        init_db()
        get_db().executescript(_data_sql)

    yield app

    os.close(db_fd)
    os.unlink(db_path)


@pytest.fixture
def client(app):
    return app.test_client()


@pytest.fixture
def runner(app):
    return app.test_cli_runner()

tests/test_auth.py

import pytest
from flask import g, session
from flaskr.db import get_db


def test_register(client, app):
    assert client.get('/auth/register').status_code == 200
    response = client.post_csrf(
        '/auth/register', data={'username': 'a', 'password': 'a'}
        , follow_redirects=True)
    print(f'{response.status=}')
    print(f'{response.response=}')
    for x in response.response:
        print(x)
    assert response.headers["Location"] == "/auth/login"

    with app.app_context():
        assert get_db().execute(
            "SELECT * FROM user WHERE username = 'a'",
        ).fetchone() is not None

Output from the test:
$ pytest tests/test_auth.py

=========================================== FAILURES 
============================================
_________________________________________ test_register 
_________________________________________

client = <FlaskClient <Flask 'flaskr'>>, app = <Flask 'flaskr'>

def test_register(client, app):
    assert client.get('/auth/register').status_code == 200
    response = client.post(
        '/auth/register', data={'username': 'a', 'password': 'a'}
        , follow_redirects=True)
    print(f'{response.status=}')
    print(f'{response.response=}')
    for x in response.response:
        print(x)
>       assert response.headers["Location"] == "/auth/login"

tests/test_auth.py:15:
_ _ _ _ _ _ _ _ _ _ _ _ _ _
 
venv/lib/python3.11/site-
packages/werkzeug/datastructures/headers.py:83: in __getitem__
    return self._get_key(key)
_ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Headers([('Content-Type', 'text/html; charset=utf-8'), 
('Content-Length', '960'), ('Vary', 'Cookie')])
key = 'Location'

def _get_key(self, key: str) -> str:
    ikey = key.lower()

    for k, v in self._list:
        if k.lower() == ikey:
            return v

>       raise BadRequestKeyError(key)
E       werkzeug.exceptions.BadRequestKeyError: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.

venv/lib/python3.11/site-packages/werkzeug/datastructures/headers.py:97: BadRequestKeyError
2
  • 1
    The error has nothing to do with auth problems. You're trying to access a header Location that doesn't exist in that response. Location headers are used for redirects, but you enabled follow_redirects=True so this is a response from a subsequent request after a redirect. Does the CSRF problem persist after fixing the header check? Commented Mar 6 at 15:07
  • @amon yes I switched to checking response.request.path, but the redirect wasn't happening because the form was never valid without the token. I found that I can set WTF_CSRF_ENABLED = False in the config, and the tests work without it. That's OK but I would rather be able to test with it enabled. Commented Mar 7 at 0:35

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.