Проверка входа в систему и аутентификация?

Я разрабатываю приложение 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, должно быть зарегистрировано.