我正在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存储库。自述文件仍然提到"这个要点",对不起。
答案 0 :(得分:7)
您的问题主要是归结为在您的测试请求中提供会话令牌。如果您不提供令牌,则会话为空。
我假设您的实际应用程序正确发送会话令牌,因此似乎可以正常工作。
修复测试用例以正确传递并不需要太多。
在你的会话实施中:
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)
...但您在发出后续请求时没有使用新令牌
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])
如上所述,我已经发送了带有更改的拉取请求,您会发现现在测试的默认烧瓶会话和会话实施都已通过。