Как избежать ошибки "ClientRedirectError: цикл обнаружен" во время тестирования приложения Flask (и как он запускается)?

При написании тестов для моего приложения Flask я обнаружил следующую особенность: если в тестах для приложения Flask вы будете перенаправлены на один и тот же URL два раза "подряд". ClientRedirectError: loop detected будет выброшено, даже если оно прекратит перенаправление после второго перенаправления (т. е. цикл, на самом деле, не происходит).

Рассмотрим следующий упрощенный пример:

engine.py

from flask import (
    Flask,
    redirect,
    url_for,
    session
)

app = Flask(__name__)
app.secret_key = 'improper secret key'

@app.route('/')
def index():
    if not session.get('test', False):
        session['test'] = True
        return redirect(url_for('index'))
    else:
        return "Hello"

@app.route('/redirection/')
def redirection():
    # do something — login user, for example
    return redirect(url_for('index'))

test_redirect.py

from unittest import TestCase
from engine import app

class RedirectTestCase(TestCase):

def setUp(self):
    self.test_client = app.test_client()

def testLoop(self):
    response = self.test_client.get('/redirection/', follow_redirects=True)
    self.assertTrue('Hello'.encode('ascii') in response.data)

Теперь, если я буду запускать тест - он бросит ClientRedirectError: loop detected (хотя из кода видно, что второе перенаправление произойдет только один раз).

Если я просто запустить приложение и перейти к /redirection/ - это приводит меня к указателю (т.е. /) без проблем и зацикливания не происходит.

Причина мне нужна if not session.get('test', False): в index(), потому что в моем приложении я использую его, чтобы установить некоторые вещи в sessionв случае доступа пользователя / в первый раз. Как следует из комментария, в моем "реальном" приложении redirection() это функция, которая регистрирует пользователя


Мои вопросы:

  1. Есть ли "правильный" способ преодолеть метание ClientRedirectError: loop detected в подобных случаях (т. е. возможно ли выполнить тестовый прогон и пройти)?
  2. Есть ли лучший / более правильный способ настройки вещей в session для первого пользователя?
  3. Может ли упомянутое поведение считаться ошибкой в werkzeug (то есть фактическое зацикливание не происходит, но ClientRedirectError: loop detected брошен, все еще)?


Обходной путь, который я придумал (который все еще не отвечает на мои вопросы):

    def testLoop(self):
        self.test_client.get('/redirection/') # removed follow_redirects=True
        response = self.test_client.get('/', follow_redirects=True) # navigating to '/', directly 
        self.assertTrue('Hello'.encode('ascii') in response.data)

Это может показаться излишним, но это просто упрощенный пример (возможно, это будет иметь смысл, если self.test_client.get('/redirection/') будет заменен чем-то вроде self.test_client.post('/login/', data=dict(username='user', password='pass')),

0 ответов

Кажется, я готов сейчас (почти 1,5 дрожжей после публикации) ответить на мои собственные вопросы:

  1. Можно сделать тестовый прогон и пройти.
    Учитывая код, как и выше, обходной путь, представленный в конце вопроса, подойдет, хотя его можно сократить до:

    def testLoop(self):
        response = self.test_client.get('/', follow_redirects=True) # navigating to '/', directly 
        self.assertTrue('Hello'.encode('ascii') in response.data)
    

    в этом конкретном случае нет необходимости self.test_client.get('/redirection/'), линия. Хотя, если некоторые изменения были внесены в session['test'] внутри /redirection/, вот так:

    @app.route('/redirection/')
    def redirection():
        session['test'] = True
        return redirect(url_for('index'))
    

    затем, self.test_client.get('/redirection/'), было бы необходимо, чтобы тест стал:

    from unittest import TestCase
    from app import app
    
    class RedirectTestCase(TestCase):
    
    def setUp(self):
        self.test_client = app.test_client()
    
    def testLoop(self):
        self.test_client.get('/redirection/')
        response = self.test_client.get('/', follow_redirects=True)
    
        self.assertTrue('Hello'.encode('ascii') in response.data)
    

    Этот, так называемый, " обходной путь " просто мешает тесту выполнить два перенаправления на / подряд (что именно и вызывает ClientRedirectError: loop detected - см. Ответ на пункт 3 ниже).

  2. Если есть лучший способ или нет - будет зависеть от точных деталей реализации приложения. В примере из вопроса перенаправление внутри index на самом деле ненужен и может быть удален:

    @app.route('/')
    def index():
        session['test'] = session.get('test', True)
        return "Hello"
    

    Такое изменение, кстати, также сделает начальный тест (из вопроса) успешным.

  3. Насколько я могу судить, такое поведение "нарочно", а именно: ClientRedirectError: loop detected повышается, когда перенаправление на один и тот же URL-адрес происходит (как минимум) два раза подряд. Ниже приведены некоторые примеры.

    Этот тест не встретится ClientRedirectError (так как перенаправление происходит только один раз):

    isolated_app.py

    from flask import (
        Flask,
        redirect,
        url_for,
        session
        )
    
    app = Flask(__name__)
    app.secret_key = 'improper secret key'
    
    @app.route('/')
    def index():
        session['test'] = session.get('test', 0)
        if session['test'] < 1:
            session['test'] += 1
            return redirect(url_for('index'))
        else:
            return "Hello"
    

    test.py

    from unittest import TestCase
    from isolated_app import app
    
    class RedirectTestCase(TestCase):
    
    def setUp(self):
        self.test_client = app.test_client()
    
    def testLoop(self):
        response = self.test_client.get('/', follow_redirects=True)
        self.assertTrue('Hello'.encode('ascii') in response.data)
    

    Хотя, если код приложения будет изменен на:

    from flask import (
        Flask,
        redirect,
        url_for,
        session
        )
    
    app = Flask(__name__)
    app.secret_key = 'improper secret key'
    
    @app.route('/')
    def index():
        session['test'] = session.get('test', 0)
        if session['test'] < 1:
            session['test'] += 1
            return redirect(url_for('index'))
        elif session['test'] < 2:
            session['test'] += 1
            return redirect(url_for('index'))
        else:
            return "Hello"
    

    тогда тест провалится, бросая werkzeug.test.ClientRedirectError: loop detected,

    Стоит также отметить, что запуск ошибок цикла перенаправления отличается для тестов Flask и для просмотра в реальном времени. Если мы запустим последнее приложение и перейдем к / в браузере - он легко вернется Hello нам. Количество разрешенных перенаправлений определяется разработчиком каждого браузера (например, в FireFox оно определяется network.http.redirection-limit предпочтение от about:config и 20 является значением по умолчанию). Вот иллюстрация:

    from flask import (
        Flask,
        redirect,
        url_for,
        session
        )
    
    app = Flask(__name__)
    app.secret_key = 'improper secret key'
    
    @app.route('/')
    def index():
        session['test'] = session.get('test', 0)
        while True:
            session['test'] = session.get('test', 0)
            session['test'] += 1
            print(session['test'])
            return redirect(url_for('index'))
    

    Это напечатает 1 2, а затем потерпите неудачу с werkzeug.test.ClientRedirectError: loop detected, если мы попытаемся запустить тест для него. С другой стороны, если мы запустим это приложение и перейдем к / в FireFox - браузер покажет нам The page isn’t redirecting properly сообщение, и приложение напечатает 1 2 3 4 5 6 ... 21 в консоли.

Другие вопросы по тегам