为什么这些自定义Flask会话界面的测试失败?

时间:2015-09-09 15:00:25

标签: python session flask customization

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

我创建了一个带有缩减测试用例的GitHub repository。它本身仍然是一个相当大的项目,但自述文件应该可以帮助您快速找到自己的方式。 repo包含七个测试,当使用Flask的默认基于cookie的会话界面时,所有测试都成功,并且所有测试都失败了我的自定义会话界面。主要问题似乎是数据有时不会保留在会话对象上,但这很神秘,因为会话对象继承自Python的内置dict,它不应该自发地忘记数据。此外,会话界面很简单,与Flask's example Redis session snippet相比,似乎没有任何明显的错误。

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

非常感谢帮助。

编辑: Gist不接受缩减的测试用例,因为它包含目录。我现在将它移动到一个完整的GitHub存储库。完成后我会再次更新这篇文章。

新编辑:将缩减的测试用例移动到正确的GitHub存储库。自述文件仍然提到"这个要点",对不起。

1 个答案:

答案 0 :(得分:7)

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

我假设您的实际应用程序正确发送会话令牌,因此似乎可以正常工作。

修复测试用例以正确传递并不需要太多。

每个请求都尝试根据post param

加载会话

在你的会话实施中:

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

    return s

这意味着每个请求都不是POST(或PUT)且没有t已发送的请求 有一个空白会议。

基于cookie的实现始终具有会话令牌 并且将能够加载先前请求的会话。

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

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)

您尚未为t的帖子提供/test值。因此它变得空白 会话中没有captcha-expires密钥和KeyError会话。

您的会话需要一个“令牌”键才能保存

在你的会话实施中:

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_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的帖子已经生成了新内容 令牌并保存它,因此关键键last-reply不在 由abcdef标识的会话。如果这是基于cookie的会话,则last-reply将可用于下一个请求。

所以要修复此测试...使用新令牌

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={

        ...

重定向将丢失会话令牌

在测试中test_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的帖子会返回重定向。

您正在检查是否存在Flash消息。和flash消息 存储在会话中。

使用基于cookie的会话,会话ID再次与随后的重定向请求一起发送。

由于您的会话ID被指定为帖子值,因此不会再次发送,会话和Flash消息将丢失。

解决这个问题的方法不是遵循重定向,而是检查会话以查看使用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])

那就是

如上所述,我已经发送了带有更改的拉取请求,您会发现现在测试的默认烧瓶会话和会话实施都已通过。