Проверка входа в систему и аутентификация?
Я разрабатываю приложение Flask и использую Flask-security для аутентификации пользователей (что в свою очередь использует подставку Flask под ней).
У меня есть маршрут, который требует аутентификации, /user
. Я пытаюсь написать unit test, который проверяет, что для аутентифицированного пользователя это возвращает соответствующий ответ.
В моем unittest я создаю пользователя и регистрирую его как этого пользователя:
from unittest import TestCase
from app import app, db
from models import User
from flask_security.utils import login_user
class UserTest(TestCase):
def setUp(self):
self.app = app
self.client = self.app.test_client()
self._ctx = self.app.test_request_context()
self._ctx.push()
db.create_all()
def tearDown(self):
if self._ctx is not None:
self._ctx.pop()
db.session.remove()
db.drop_all()
def test_user_authentication():
# (the test case is within a test request context)
user = User(active=True)
db.session.add(user)
db.session.commit()
login_user(user)
# current_user here is the user
print(current_user)
# current_user within this request is an anonymous user
r = test_client.get('/user')
В рамках теста current_user
возвращается правильный пользователь. Однако запрошенный вид всегда возвращает AnonymousUser
как current_user
.
Маршрут /user
определяется как:
class CurrentUser(Resource):
def get(self):
return current_user # returns an AnonymousUser
Я уверен, что я просто не совсем понимаю, как работают проверки контекста запроса флэков. Я прочитал эту флешку запросить контекстную документацию, но я до сих пор не понимаю, как подойти к этому конкретному unit test.
Ответы
Ответ 1
Проблема заключается в том, что вызов test_client.get()
вызывает перенаправление нового контекста запроса, поэтому тот, который вы нажали в свой метод setUp()
вашего тестового примера, не тот, который видит обработчик /user
.
Я думаю, что подход, показанный в Вход в систему и выход и Test Добавление сообщений в документации - лучший подход для тестирования логинов. Идея состоит в том, чтобы отправить запрос на вход через приложение, как это сделал бы обычный клиент. Это позаботится о регистрации зарегистрированного пользователя в сеансе пользователя тестового клиента.
Ответ 2
Проблема заключается в разных контекстах запросов.
В вашем обычном приложении Flask каждый запрос создает новый контекст, который будет использоваться повторно по всей цепочке до создания окончательного ответа и отправки его обратно в браузер.
Когда вы создаете и запускаете тесты Flask и выполняете запрос (например, self.client.post(...)
), контекст отбрасывается после получения ответа. Следовательно, current_user
всегда является AnonymousUser
.
Чтобы исправить это, мы должны сказать Flask, чтобы повторно использовать тот же контекст для всего теста. Вы можете сделать это, просто обернув свой код:
with self.client:
Вы можете узнать больше об этой теме в следующей замечательной статье:
https://realpython.com/blog/python/python-web-applications-with-flask-part-iii/
Пример
До:
def test_that_something_works():
response = self.client.post('login', { username: 'James', password: '007' })
# this will fail, because current_user is an AnonymousUser
assertEquals(current_user.username, 'James')
После:
def test_that_something_works():
with self.client:
response = self.client.post('login', { username: 'James', password: '007' })
# success
assertEquals(current_user.username, 'James')
Ответ 3
Мне не понравилось другое показанное решение, в основном потому, что вам нужно сохранить свой пароль в файле unit test (и я использую Flask-LDAP-Login, так что он неочевиден для добавления фиктивного пользователя и т.д.)..), поэтому я взломал его:
В том месте, где я установил тестовое приложение, я добавил:
@app.route('/auto_login')
def auto_login():
user = ( models.User
.query
.filter_by(username="Test User")
.first() )
login_user(user, remember=True)
return "ok"
Однако, я делаю довольно много изменений в тестовом экземпляре флеш-приложения, например, используя другой БД, где я его создаю, поэтому добавление маршрута не делает код заметно беспорядочным. Obv этот маршрут не существует в реальном приложении.
Тогда я делаю:
def login(self):
response = self.app.test_client.get("/auto_login")
Все, что было сделано после этого с test_client
, должно быть зарегистрировано.