Как избежать ошибки "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()
это функция, которая регистрирует пользователя
Мои вопросы:
- Есть ли "правильный" способ преодолеть метание
ClientRedirectError: loop detected
в подобных случаях (т. е. возможно ли выполнить тестовый прогон и пройти)? - Есть ли лучший / более правильный способ настройки вещей в
session
для первого пользователя? - Может ли упомянутое поведение считаться ошибкой в 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 дрожжей после публикации) ответить на мои собственные вопросы:
Можно сделать тестовый прогон и пройти.
Учитывая код, как и выше, обходной путь, представленный в конце вопроса, подойдет, хотя его можно сократить до: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 ниже).Если есть лучший способ или нет - будет зависеть от точных деталей реализации приложения. В примере из вопроса перенаправление внутри
index
на самом деле ненужен и может быть удален:@app.route('/') def index(): session['test'] = session.get('test', True) return "Hello"
Такое изменение, кстати, также сделает начальный тест (из вопроса) успешным.
Насколько я могу судить, такое поведение "нарочно", а именно:
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
в консоли.