Python 为什么这些测试在此自定义Flask会话接口中失败?

Python 为什么这些测试在此自定义Flask会话接口中失败?,python,session,flask,customization,Python,Session,Flask,Customization,我正在Flask中编写一个混合的单页web/PhoneGap应用程序。由于PhoneGap应用程序中的cookie基本上不可用,因此我实现了一个完全避免cookie的自定义。它将会话数据存储在应用程序数据库中,并在HTTP请求和响应主体中显式传递会话ID 我创建了一个具有简化测试用例的。它本身仍然是一个相当大的项目,但自述文件应该可以帮助您快速找到自己的方法。repo包括七个测试,当使用Flask默认的基于cookie的会话接口时,所有测试都成功,而当使用我的自定义会话接口时,所有测试都失败。主

我正在Flask中编写一个混合的单页web/PhoneGap应用程序。由于PhoneGap应用程序中的cookie基本上不可用,因此我实现了一个完全避免cookie的自定义。它将会话数据存储在应用程序数据库中,并在HTTP请求和响应主体中显式传递会话ID

我创建了一个具有简化测试用例的。它本身仍然是一个相当大的项目,但自述文件应该可以帮助您快速找到自己的方法。repo包括七个测试,当使用Flask默认的基于cookie的会话接口时,所有测试都成功,而当使用我的自定义会话接口时,所有测试都失败。主要的问题似乎是数据有时不会保留在会话对象上,但这很神秘,因为会话对象继承了Python的内置dict,它不应该自动忘记数据。此外,会话接口非常简单,与其他接口相比似乎没有任何明显的错误

更令人沮丧的是,自定义会话接口在实际应用程序中似乎工作正常。只有单元测试失败了。但是,由于这个原因,假设会话接口在所有情况下都能正常工作是不安全的

非常感谢您的帮助

Edit:Gist不接受简化的测试用例,因为它包含目录。我现在将它转移到一个成熟的GitHub存储库中。完成后,我将再次更新此帖子

新建编辑:将简化的测试用例移动到适当的GitHub repo。自述文件仍然提到“这一要点”,对不起。

您的问题主要归结为在测试请求中提供会话令牌。如果不提供令牌,则会话为空

我假设您的实际应用程序正确地发送了会话令牌,因此看起来工作正常

修复测试用例以正确通过并不需要太多

每个请求都尝试根据post参数加载会话 在会话实现中:

def open_session(self, app, request):
    s = Session()
    if 't' in request.form:
        ....

    return s
def save_session(self, app, session, response):
    if session.modified and 'token' in session:
        ...
        # save session to database
        ...
这意味着每个不是
POST
(或
PUT
)且未发送
t
的请求将 有一个空白会议

而基于cookies的实现将始终具有可用的会话令牌 并且能够加载以前请求的会话

以下是您的一个示例测试:

def test_authorize_captcha_expired(self):
    with self.client as c:
        with c.session_transaction() as s:
            s['captcha-answer'] = u'one two three'.split()
            s['captcha-expires'] = datetime.today() - timedelta(minutes=1)
        self.assertEqual(c.post('/test', data={
            'ca': 'one two three',
        }).status_code, 400)
您尚未为post to
/test
提供
t
值。因此它得到了一个空白 没有
验证码的会话将过期
密钥,并引发
密钥错误

您的会话需要一个“令牌”密钥才能保存 在会话实现中:

def open_session(self, app, request):
    s = Session()
    if 't' in request.form:
        ....

    return s
def save_session(self, app, session, response):
    if session.modified and 'token' in session:
        ...
        # save session to database
        ...
因此,当您有:

with c.session_transaction() as s:
    s['captcha-answer'] = u'one two three'.split()
    s['captcha-expires'] = datetime.today() - timedelta(minutes=1)
实际上没有会话写入数据库。对于任何后续请求 使用。请注意,它确实需要写入数据库,因为每次请求时,
open\u session
都会尝试从数据库加载内容

要解决大多数情况,您需要在创建会话时提供一个“令牌”,并为使用该令牌的任何请求提供一个带有该令牌的“t”

因此,我上面使用的样本测试结果如下:

def test_authorize_captcha_expired(self):
    with self.client as c:
        token = generate_key(SystemRandom())
        with c.session_transaction() as s:
            s['token'] = token
            s['captcha-answer'] = u'one two three'.split()
            s['captcha-expires'] = datetime.today() - timedelta(minutes=1)
        self.assertEqual(c.post('/test', data={
            'ca': 'one two three',
            't': token
        }).status_code, 400)
当您使用json响应时,您可以更改令牌 …但您在发出后续请求时未使用新令牌

def test_reply_to_reflection_passthrough(self):
    with self.client as c:
        token = 'abcdef'

        ...

        response2 = c.post('/reflection/1/reply', data={
            'p': 'test4',
            'r': 'testmessage',
            't': token,
        }, ...
至此,发到
/reflection/1/reply
的帖子生成了一个新的 令牌并将其保存,因此关键密钥
最后一次回复
不在 会话由
abcdef
标识。如果这是一个基于cookies的会话,那么
最后一次回复
将可用于下一个请求

所以要修正这个测试。。。使用新令牌

def test_reply_to_reflection_passthrough(self):
    with self.client as c:

        ...

        response2 = c.post('/reflection/1/reply', data={

        ...

        token = session['token']
        with c.session_transaction(method="POST", data={'t':token}) as s:
            s['token'] = token
            s['last-request'] = datetime.now() - timedelta(milliseconds=1001)

        response3 = c.post('/reflection/1/reply', data={

        ...
重定向将丢失会话令牌 在测试
测试\u bump
中:

def test_bump(self):
    response = self.client.post(
        '/admin/tip/action/',
        data = {'action': 'Bump', 'rowid': '1',},
        follow_redirects=True )
    self.assertIn(' tips have been bumped.', response.data)
发送到
/admin/tip/action
的帖子返回重定向

这里您正在检查是否存在闪存消息。和即时消息 存储在会话中

对于基于cookie的会话,会话id将与随后的重定向请求一起再次发送

由于会话id被指定为post值,因此不会再次发送,会话和闪存消息将丢失

解决此问题的方法不是遵循重定向,而是通过flasks flash方法检查会话中的数据集

def test_bump(self):
    with self.client as c:
        token = generate_key(SystemRandom())
        with c.session_transaction() as s:
            s['token'] = token
        c.post('/admin/tip/action/',
               data={'action': 'Bump', 'rowid': '1', 't': token})

        with c.session_transaction(method="POST", data={'t': token}) as s:
            self.assertIn(' tips have been bumped.', s['_flashes'][0][1])
就这些
我已经发送了一个包含上述更改的pull请求,您会发现默认flask会话和会话实现的测试现在都通过了。

简单浏览一下您的测试,可以发现您的帖子中没有包含
t
,但您的主应用程序是。不是100%确定,因为有很多东西要看。@SeanVieira那篇略读可能有点太简短了。:-)有时,测试桩确实不包括
t
字段,但目标是确认它产生了40倍的错误。当使用Flask的默认会话实现时,所有的测试都通过了。我看到了它期望的成功响应。我遗漏了什么吗?啊,对了,很抱歉搞混了。这实际上可能是特定测试中的问题(这是一个管理视图,其工作方式与其他视图略有不同)!但是,在其他六个失败的测试中,还有其他一些事情正在进行。如果您可以将项目简化为一个片段,这将是很有帮助的:-)无论如何,哪些数据不会保留在
会话
对象中?我想知道这是否是因为您继承了
dict
\uuuuu setitem\uuuuu
\uuuuuu setattr\uuuuu
存在一些问题。您知道Flask是如何与您的
会话
对象交互的吗?这看起来已经是一个很好的答案了。谢谢你的辛勤工作。我会详细审查你的拉拽请求。我已经接受了你的回答,并将在我可以的时候奖励赏金(此时我仍需等待三个小时)。@Julian Great!很高兴我能帮上忙。干得好——特别是