jaybee
jaybee

Reputation: 1925

Testing a flask-WTForms app that uses csrf token (pytest)

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

Upvotes: 0

Views: 17

Answers (0)

Related Questions