Reputation: 1661
I would like to test that all routes from a blueprint are protected with a login required decorator.
The point is : If a developer add a new route and forget to add this decorator, I would like my test to automatically spot that lack.
In order to do that, I would like to loop trough all routes and methods
for rule in app.url_map.iter_rules():
if rule.endpoint.startswith("my_blueprint"):
response = app.test_client().get(rule)
assert response.status_code == 401
As you can see, I have to specify the method (get, post..) like this app.test_client().get(rule)
.
Is there a more dynamic way to to invoke the methods ?
Upvotes: 0
Views: 621
Reputation: 761
Discovery function
def blueprint_site_map(app, blueprint, all_methods=False):
'''
utilizes Flask's built-in rule mapper to generate a
site-map of the application, returning a list of dicts, ex.
{
'endpoint' : repr(Blueprint)
'methods' : list
'rule' : /route
{
'''
reply = []
rules = list(app.url_map.iter_rules())
ignored_methods = set(() if all_methods else ('HEAD', 'OPTIONS'))
rule_methods = [','.join(sorted(rule.methods - ignored_methods)) for rule in rules]
for rule, methods in zip(rules, rule_methods):
if (rule.endpoint != 'static') and (rule.endpoint.startswith(blueprint)):
reply.append(dict(endpoint=rule.endpoint, methods=methods.split(','), rule=rule.rule))
return reply
Sample output
>>> blueprint_site_map(app, 'my_blueprint')
[
{
'endpoint': 'my_blueprint.foo',
'methods': ['GET', 'POST'],
'rule': '/auth/foo'
},
{
'endpoint': 'my_blueprint.bar',
'methods': ['DELETE', 'GET', 'POST'],
'rule': '/auth/bar'
}
]
Usage
def test_my_blueprint_is_protected(client):
from flask import current_app as app
obj = blueprint_site_map(app, 'my_blueprint')
for each in obj:
for method in each['methods']:
func = getattr(client, method)
url = each['rule'] # *see note
kwargs = {} # inject headers, etc if needed
response = func(url, **kwargs)
assert response.status_code == 401
It should be noted that if you are using any parameterized URL rules, such as allowing both /foo
and /foo/<string:s>
then you will need to manually template or filter these out. The blueprint_site_map
function will include separate list elements for /foo
and /foo/<string:s>
, which when taken literally will cause problems either from the test client itself, or with your route logic.
The discovery function is crafted in such a way that you can use this convention for as many different blueprints as necessary, which by very nature of your using the Blueprint convention means that you can keep your unit tests as modular as the app.
Cheers!
Upvotes: 3