Как объясняется 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"
# Using `flask.ext.principal` `Permission.require`...
# ... instead of Matt's decorators
@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")
(кстати, самый последний тест показывает, что декоратор Мэтта гораздо проще в использовании ....)
Второй подход использует функцию 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")
Вот что я боялся: я заблудился в контекстах. AFAIU, ваш 'define_identity' будет вызван до обработки запроса, используя тот же контекст, правильно? Итак, мне нужно объявить личность где-то в этом контексте или получить ее из какого-либо глобального контекста или создать ее «на лету» из некоторых дополнительных аргументов, переданных запросу (например, 'query_string') ... Я попробую чтобы опубликовать некоторые решения в другом ответе, я был бы очень благодарен, если бы вы могли сообщить мне, что вы думаете. –
Исправить. Но я не уверен, почему ты боишься этого. И да, функция 'define_identity' будет вызываться по каждому запросу и совместно использовать один и тот же контекст с вашими методами просмотра. Определение личности зависит от того, как вы планируете аутентифицировать пользователей. Например, если вы хотите использовать механизм проверки подлинности на основе сеанса, вам необходимо установить флажок-флажок с флаконом-входом. Если вы создаете API, который не имеет состояния, вы должны передать параметры auth в заголовках или использовать базовый http auth и определить пользователя в 'define_identity' из этих значений. –
Одна вещь, о которой я не упоминал, заключается в том, что Flask-Principal по умолчанию сохраняет идентификатор в сеансе, поэтому при первом вызове метода identity_changed.send он будет хранить идентификатор в сеансе и загружать его для каждого запроса за исключением статических конечных точек. –