How to avoid "ClientRedirectError: loop detected" error during testing Flask application (and how is it triggered)?

How to avoid "ClientRedirectError: loop detected" error during testing Flask application (and how is it triggered)?

环境:


在为我的 Flask 应用程序编写测试时,我发现了以下特性:如果在 Flask 应用程序的测试中,您将被重定向到同一个 url 两次 'in a row' ClientRedirectError: loop detected 将被抛出,即使它在第二次重定向后停止重定向(即实际上没有发生循环)。

考虑以下简化示例:

app.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 app 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/ — 它会毫无问题地将我带到索引(即 /),并且不会发生循环。

我在 index() 中需要 if not session.get('test', False): 的原因是因为在我的应用程序中我用它来设置 session 中的一些东西,以防用户访问 [=16= 】 第一次。正如代码中的评论所建议的那样,在我的 'real' 应用程序中 redirection() 是一个让用户登录的功能。


我的问题是:

  1. 在类似情况下是否有 'right' 方法来克服抛出 ClientRedirectError: loop detected 的问题(即是否有可能使测试 运行 并通过)?[​​=67=]
  2. 是否有更好/'more correct' 的方法来为 'first-time' 用户设置 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')).

之类的内容,可能会更有意义

看来,我现在已经准备好了(发布后差不多有 1.5 个酵母)到 answer my own questions:

  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/') 行。 但是,如果对 /redirection/ 中的 session['test'] 进行了一些更改,例如:

    @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. 据我所知,这种行为是 "by design",即:当重定向到相同的 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 测试和实际浏览是不同的。如果我们将 运行 最后一个应用程序,并在浏览器中转到 / - 它会很容易 return Hello 给我们。允许的重定向数量由每个浏览器的开发人员定义(例如,在 FireFox 中,它由 about:confignetwork.http.redirection-limit 首选项定义,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