正如 Matt解释的那样,这只是一个上下文问题。在他的解释下,我想出了两种不同的方法来在单元测试期间切换标识。
首先,让我们稍微修改一下应用程序创建:
def _on_principal_init(sender, identity):
"Sets the roles for the 'admin' and 'member' identities"
if identity.id:
if identity.id == 'admin':
identity.provides.add(RoleNeed('admin'))
identity.provides.add(RoleNeed('member'))
def create_app():
app = flask.Flask(__name__)
app.debug = True
app.config.update(SECRET_KEY='secret',
TESTING=True)
principal = Principal(app)
identity_loaded.connect(_on_principal_init)
@app.route('/')
def index():
return "OK"
@app.route('/member')
@roles_accepted('admin', 'member')
def role_needed():
return "OK"
@app.route('/admin')
@roles_required('admin')
def connect_admin():
return "OK"
@app.route('/admin_alt')
@admin_permission.require()
def connect_admin_alt():
return "OK"
return app
一种可能性是创建一个函数,在我们的测试中在每个请求之前加载一个身份。最简单的方法是在应用程序创建后,在测试套件的
setUpClass
中声明它,并使用
app.before_request
装饰器:
class WorkshopTestOne(unittest.TestCase):
@classmethod
def setUpClass(cls):
app = create_app()
cls.app = app
cls.client = app.test_client()
@app.before_request
def get_identity():
idname = flask.request.args.get('idname', '') or None
print "Notifying that we're using '%s'" % idname
identity_changed.send(current_app._get_current_object(),
identity=Identity(idname))
然后,测试变成了:
def test_admin(self):
r = self.client.get('/admin')
self.assertEqual(r.status_code, 403)
r = self.client.get('/admin', query_string={'idname': "member"})
self.assertEqual(r.status_code, 403)
r = self.client.get('/admin', query_string={'idname': "admin"})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "OK")
def test_admin_alt(self):
try:
r = self.client.get('/admin_alt')
except flask.ext.principal.PermissionDenied:
pass
try:
r = self.client.get('/admin_alt', query_string={'idname': "member"})
except flask.ext.principal.PermissionDenied:
pass
try:
r = self.client.get('/admin_alt', query_string={'idname': "admin"})
except flask.ext.principal.PermissionDenied:
raise
self.assertEqual(r.data, "OK")
(顺便说一下,最后一个测试表明Matt的装饰器要容易使用得多...)
第二种方法使用
test_request_context
函数和
with ...
创建临时上下文。不需要定义一个由
@app.before_request
装饰的函数,只需将要测试的路由作为
test_request_context
的参数传递,在上下文中发送
identity_changed
信号,并使用
.full_dispatch_request
方法。
class WorkshopTestTwo(unittest.TestCase):
@classmethod
def setUpClass(cls):
app = create_app()
cls.app = app
cls.client = app.test_client()
cls.testing = app.test_request_context
def test_admin(self):
with self.testing("/admin") as c:
r = c.app.full_dispatch_request()
self.assertEqual(r.status_code, 403)
with self.testing("/admin") as c:
identity_changed.send(c.app, identity=Identity("member"))
r = c.app.full_dispatch_request()
self.assertEqual(r.status_code, 403)
with self.testing("/admin") as c:
identity_changed.send(c.app, identity=Identity("admin"))
r = c.app.full_dispatch_request()
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "OK")
determine_identity
将在请求处理之前使用相同的上下文进行调用,对吗?因此,我需要在该上下文中声明一个标识,或从某个全局上下文中检索它,或者根据传递给请求的一些额外参数(例如query_string
)即时创建它... 我将尝试在另一个答案中发布一些解决方案,如果您能让我知道您的想法,我将不胜感激。 - Pierre GMdetermine_identity
函数将在每个请求上被调用,并与您的视图方法共享相同的上下文。确定身份完全取决于您计划如何验证用户。例如,如果您想要一个基于会话的身份验证机制,您应该将Flask-Principal与Flask-Login配对使用。如果您正在构建一个无状态的API,则应在标头中传递auth参数或使用基本的http auth,并从这些值中在determine_identity
中确定用户。 - Matt Wflask.login
进行身份验证和flask.principal
进行角色管理。我想在不修改当前逻辑的情况下添加缺失的测试。一旦用户登录,她的角色就被设置并且她的Identity
保存在会话中,正如你所指出的那样。在单元测试中,我需要切换身份,这就是你的determine_identity
起作用的地方... - Pierre GM