You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

820 lines
22 KiB

# -*- coding: utf-8 -*-
"""
tests.blueprints
~~~~~~~~~~~~~~~~
Blueprints (and currently modules)
:copyright: (c) 2015 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import pytest
import flask
from flask._compat import text_type
from werkzeug.http import parse_cache_control_header
from jinja2 import TemplateNotFound
def test_blueprint_specific_error_handling(app, client):
frontend = flask.Blueprint('frontend', __name__)
backend = flask.Blueprint('backend', __name__)
sideend = flask.Blueprint('sideend', __name__)
@frontend.errorhandler(403)
def frontend_forbidden(e):
return 'frontend says no', 403
@frontend.route('/frontend-no')
def frontend_no():
flask.abort(403)
@backend.errorhandler(403)
def backend_forbidden(e):
return 'backend says no', 403
@backend.route('/backend-no')
def backend_no():
flask.abort(403)
@sideend.route('/what-is-a-sideend')
def sideend_no():
flask.abort(403)
app.register_blueprint(frontend)
app.register_blueprint(backend)
app.register_blueprint(sideend)
@app.errorhandler(403)
def app_forbidden(e):
return 'application itself says no', 403
assert client.get('/frontend-no').data == b'frontend says no'
assert client.get('/backend-no').data == b'backend says no'
assert client.get('/what-is-a-sideend').data == b'application itself says no'
def test_blueprint_specific_user_error_handling(app, client):
class MyDecoratorException(Exception):
pass
class MyFunctionException(Exception):
pass
blue = flask.Blueprint('blue', __name__)
@blue.errorhandler(MyDecoratorException)
def my_decorator_exception_handler(e):
assert isinstance(e, MyDecoratorException)
return 'boom'
def my_function_exception_handler(e):
assert isinstance(e, MyFunctionException)
return 'bam'
blue.register_error_handler(MyFunctionException, my_function_exception_handler)
@blue.route('/decorator')
def blue_deco_test():
raise MyDecoratorException()
@blue.route('/function')
def blue_func_test():
raise MyFunctionException()
app.register_blueprint(blue)
assert client.get('/decorator').data == b'boom'
assert client.get('/function').data == b'bam'
def test_blueprint_app_error_handling(app, client):
errors = flask.Blueprint('errors', __name__)
@errors.app_errorhandler(403)
def forbidden_handler(e):
return 'you shall not pass', 403
@app.route('/forbidden')
def app_forbidden():
flask.abort(403)
forbidden_bp = flask.Blueprint('forbidden_bp', __name__)
@forbidden_bp.route('/nope')
def bp_forbidden():
flask.abort(403)
app.register_blueprint(errors)
app.register_blueprint(forbidden_bp)
assert client.get('/forbidden').data == b'you shall not pass'
assert client.get('/nope').data == b'you shall not pass'
def test_blueprint_url_definitions(app, client):
bp = flask.Blueprint('test', __name__)
@bp.route('/foo', defaults={'baz': 42})
def foo(bar, baz):
return '%s/%d' % (bar, baz)
@bp.route('/bar')
def bar(bar):
return text_type(bar)
app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23})
app.register_blueprint(bp, url_prefix='/2', url_defaults={'bar': 19})
assert client.get('/1/foo').data == b'23/42'
assert client.get('/2/foo').data == b'19/42'
assert client.get('/1/bar').data == b'23'
assert client.get('/2/bar').data == b'19'
def test_blueprint_url_processors(app, client):
bp = flask.Blueprint('frontend', __name__, url_prefix='/<lang_code>')
@bp.url_defaults
def add_language_code(endpoint, values):
values.setdefault('lang_code', flask.g.lang_code)
@bp.url_value_preprocessor
def pull_lang_code(endpoint, values):
flask.g.lang_code = values.pop('lang_code')
@bp.route('/')
def index():
return flask.url_for('.about')
@bp.route('/about')
def about():
return flask.url_for('.index')
app.register_blueprint(bp)
assert client.get('/de/').data == b'/de/about'
assert client.get('/de/about').data == b'/de/'
def test_templates_and_static(test_apps):
from blueprintapp import app
client = app.test_client()
rv = client.get('/')
assert rv.data == b'Hello from the Frontend'
rv = client.get('/admin/')
assert rv.data == b'Hello from the Admin'
rv = client.get('/admin/index2')
assert rv.data == b'Hello from the Admin'
rv = client.get('/admin/static/test.txt')
assert rv.data.strip() == b'Admin File'
rv.close()
rv = client.get('/admin/static/css/test.css')
assert rv.data.strip() == b'/* nested file */'
rv.close()
# try/finally, in case other tests use this app for Blueprint tests.
max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT']
try:
expected_max_age = 3600
if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == expected_max_age:
expected_max_age = 7200
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = expected_max_age
rv = client.get('/admin/static/css/test.css')
cc = parse_cache_control_header(rv.headers['Cache-Control'])
assert cc.max_age == expected_max_age
rv.close()
finally:
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default
with app.test_request_context():
assert flask.url_for('admin.static', filename='test.txt') == '/admin/static/test.txt'
with app.test_request_context():
with pytest.raises(TemplateNotFound) as e:
flask.render_template('missing.html')
assert e.value.name == 'missing.html'
with flask.Flask(__name__).test_request_context():
assert flask.render_template('nested/nested.txt') == 'I\'m nested'
def test_default_static_cache_timeout(app):
class MyBlueprint(flask.Blueprint):
def get_send_file_max_age(self, filename):
return 100
blueprint = MyBlueprint('blueprint', __name__, static_folder='static')
app.register_blueprint(blueprint)
# try/finally, in case other tests use this app for Blueprint tests.
max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT']
try:
with app.test_request_context():
unexpected_max_age = 3600
if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == unexpected_max_age:
unexpected_max_age = 7200
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = unexpected_max_age
rv = blueprint.send_static_file('index.html')
cc = parse_cache_control_header(rv.headers['Cache-Control'])
assert cc.max_age == 100
rv.close()
finally:
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default
def test_templates_list(test_apps):
from blueprintapp import app
templates = sorted(app.jinja_env.list_templates())
assert templates == ['admin/index.html', 'frontend/index.html']
def test_dotted_names(app, client):
frontend = flask.Blueprint('myapp.frontend', __name__)
backend = flask.Blueprint('myapp.backend', __name__)
@frontend.route('/fe')
def frontend_index():
return flask.url_for('myapp.backend.backend_index')
@frontend.route('/fe2')
def frontend_page2():
return flask.url_for('.frontend_index')
@backend.route('/be')
def backend_index():
return flask.url_for('myapp.frontend.frontend_index')
app.register_blueprint(frontend)
app.register_blueprint(backend)
assert client.get('/fe').data.strip() == b'/be'
assert client.get('/fe2').data.strip() == b'/fe'
assert client.get('/be').data.strip() == b'/fe'
def test_dotted_names_from_app(app, client):
test = flask.Blueprint('test', __name__)
@app.route('/')
def app_index():
return flask.url_for('test.index')
@test.route('/test/')
def index():
return flask.url_for('app_index')
app.register_blueprint(test)
rv = client.get('/')
assert rv.data == b'/test/'
def test_empty_url_defaults(app, client):
bp = flask.Blueprint('bp', __name__)
@bp.route('/', defaults={'page': 1})
@bp.route('/page/<int:page>')
def something(page):
return str(page)
app.register_blueprint(bp)
assert client.get('/').data == b'1'
assert client.get('/page/2').data == b'2'
def test_route_decorator_custom_endpoint(app, client):
bp = flask.Blueprint('bp', __name__)
@bp.route('/foo')
def foo():
return flask.request.endpoint
@bp.route('/bar', endpoint='bar')
def foo_bar():
return flask.request.endpoint
@bp.route('/bar/123', endpoint='123')
def foo_bar_foo():
return flask.request.endpoint
@bp.route('/bar/foo')
def bar_foo():
return flask.request.endpoint
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.request.endpoint
assert client.get('/').data == b'index'
assert client.get('/py/foo').data == b'bp.foo'
assert client.get('/py/bar').data == b'bp.bar'
assert client.get('/py/bar/123').data == b'bp.123'
assert client.get('/py/bar/foo').data == b'bp.bar_foo'
def test_route_decorator_custom_endpoint_with_dots(app, client):
bp = flask.Blueprint('bp', __name__)
@bp.route('/foo')
def foo():
return flask.request.endpoint
try:
@bp.route('/bar', endpoint='bar.bar')
def foo_bar():
return flask.request.endpoint
except AssertionError:
pass
else:
raise AssertionError('expected AssertionError not raised')
try:
@bp.route('/bar/123', endpoint='bar.123')
def foo_bar_foo():
return flask.request.endpoint
except AssertionError:
pass
else:
raise AssertionError('expected AssertionError not raised')
def foo_foo_foo():
pass
pytest.raises(
AssertionError,
lambda: bp.add_url_rule(
'/bar/123', endpoint='bar.123', view_func=foo_foo_foo
)
)
pytest.raises(
AssertionError,
bp.route('/bar/123', endpoint='bar.123'),
lambda: None
)
app.register_blueprint(bp, url_prefix='/py')
assert client.get('/py/foo').data == b'bp.foo'
# The rule's didn't actually made it through
rv = client.get('/py/bar')
assert rv.status_code == 404
rv = client.get('/py/bar/123')
assert rv.status_code == 404
def test_endpoint_decorator(app, client):
from werkzeug.routing import Rule
app.url_map.add(Rule('/foo', endpoint='bar'))
bp = flask.Blueprint('bp', __name__)
@bp.endpoint('bar')
def foobar():
return flask.request.endpoint
app.register_blueprint(bp, url_prefix='/bp_prefix')
assert client.get('/foo').data == b'bar'
assert client.get('/bp_prefix/bar').status_code == 404
def test_template_filter(app):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter()
def my_reverse(s):
return s[::-1]
app.register_blueprint(bp, url_prefix='/py')
assert 'my_reverse' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['my_reverse'] == my_reverse
assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba'
def test_add_template_filter(app):
bp = flask.Blueprint('bp', __name__)
def my_reverse(s):
return s[::-1]
bp.add_app_template_filter(my_reverse)
app.register_blueprint(bp, url_prefix='/py')
assert 'my_reverse' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['my_reverse'] == my_reverse
assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba'
def test_template_filter_with_name(app):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter('strrev')
def my_reverse(s):
return s[::-1]
app.register_blueprint(bp, url_prefix='/py')
assert 'strrev' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['strrev'] == my_reverse
assert app.jinja_env.filters['strrev']('abcd') == 'dcba'
def test_add_template_filter_with_name(app):
bp = flask.Blueprint('bp', __name__)
def my_reverse(s):
return s[::-1]
bp.add_app_template_filter(my_reverse, 'strrev')
app.register_blueprint(bp, url_prefix='/py')
assert 'strrev' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['strrev'] == my_reverse
assert app.jinja_env.filters['strrev']('abcd') == 'dcba'
def test_template_filter_with_template(app, client):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter()
def super_reverse(s):
return s[::-1]
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_filter.html', value='abcd')
rv = client.get('/')
assert rv.data == b'dcba'
def test_template_filter_after_route_with_template(app, client):
@app.route('/')
def index():
return flask.render_template('template_filter.html', value='abcd')
bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter()
def super_reverse(s):
return s[::-1]
app.register_blueprint(bp, url_prefix='/py')
rv = client.get('/')
assert rv.data == b'dcba'
def test_add_template_filter_with_template(app, client):
bp = flask.Blueprint('bp', __name__)
def super_reverse(s):
return s[::-1]
bp.add_app_template_filter(super_reverse)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_filter.html', value='abcd')
rv = client.get('/')
assert rv.data == b'dcba'
def test_template_filter_with_name_and_template(app, client):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter('super_reverse')
def my_reverse(s):
return s[::-1]
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_filter.html', value='abcd')
rv = client.get('/')
assert rv.data == b'dcba'
def test_add_template_filter_with_name_and_template(app, client):
bp = flask.Blueprint('bp', __name__)
def my_reverse(s):
return s[::-1]
bp.add_app_template_filter(my_reverse, 'super_reverse')
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_filter.html', value='abcd')
rv = client.get('/')
assert rv.data == b'dcba'
def test_template_test(app):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_test()
def is_boolean(value):
return isinstance(value, bool)
app.register_blueprint(bp, url_prefix='/py')
assert 'is_boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['is_boolean'] == is_boolean
assert app.jinja_env.tests['is_boolean'](False)
def test_add_template_test(app):
bp = flask.Blueprint('bp', __name__)
def is_boolean(value):
return isinstance(value, bool)
bp.add_app_template_test(is_boolean)
app.register_blueprint(bp, url_prefix='/py')
assert 'is_boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['is_boolean'] == is_boolean
assert app.jinja_env.tests['is_boolean'](False)
def test_template_test_with_name(app):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_test('boolean')
def is_boolean(value):
return isinstance(value, bool)
app.register_blueprint(bp, url_prefix='/py')
assert 'boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['boolean'] == is_boolean
assert app.jinja_env.tests['boolean'](False)
def test_add_template_test_with_name(app):
bp = flask.Blueprint('bp', __name__)
def is_boolean(value):
return isinstance(value, bool)
bp.add_app_template_test(is_boolean, 'boolean')
app.register_blueprint(bp, url_prefix='/py')
assert 'boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['boolean'] == is_boolean
assert app.jinja_env.tests['boolean'](False)
def test_template_test_with_template(app, client):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_test()
def boolean(value):
return isinstance(value, bool)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_test.html', value=False)
rv = client.get('/')
assert b'Success!' in rv.data
def test_template_test_after_route_with_template(app, client):
@app.route('/')
def index():
return flask.render_template('template_test.html', value=False)
bp = flask.Blueprint('bp', __name__)
@bp.app_template_test()
def boolean(value):
return isinstance(value, bool)
app.register_blueprint(bp, url_prefix='/py')
rv = client.get('/')
assert b'Success!' in rv.data
def test_add_template_test_with_template(app, client):
bp = flask.Blueprint('bp', __name__)
def boolean(value):
return isinstance(value, bool)
bp.add_app_template_test(boolean)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_test.html', value=False)
rv = client.get('/')
assert b'Success!' in rv.data
def test_template_test_with_name_and_template(app, client):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_test('boolean')
def is_boolean(value):
return isinstance(value, bool)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_test.html', value=False)
rv = client.get('/')
assert b'Success!' in rv.data
def test_add_template_test_with_name_and_template(app, client):
bp = flask.Blueprint('bp', __name__)
def is_boolean(value):
return isinstance(value, bool)
bp.add_app_template_test(is_boolean, 'boolean')
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_test.html', value=False)
rv = client.get('/')
assert b'Success!' in rv.data
def test_context_processing(app, client):
answer_bp = flask.Blueprint('answer_bp', __name__)
template_string = lambda: flask.render_template_string(
'{% if notanswer %}{{ notanswer }} is not the answer. {% endif %}'
'{% if answer %}{{ answer }} is the answer.{% endif %}'
)
# App global context processor
@answer_bp.app_context_processor
def not_answer_context_processor():
return {'notanswer': 43}
# Blueprint local context processor
@answer_bp.context_processor
def answer_context_processor():
return {'answer': 42}
# Setup endpoints for testing
@answer_bp.route('/bp')
def bp_page():
return template_string()
@app.route('/')
def app_page():
return template_string()
# Register the blueprint
app.register_blueprint(answer_bp)
app_page_bytes = client.get('/').data
answer_page_bytes = client.get('/bp').data
assert b'43' in app_page_bytes
assert b'42' not in app_page_bytes
assert b'42' in answer_page_bytes
assert b'43' in answer_page_bytes
def test_template_global(app):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_global()
def get_answer():
return 42
# Make sure the function is not in the jinja_env already
assert 'get_answer' not in app.jinja_env.globals.keys()
app.register_blueprint(bp)
# Tests
assert 'get_answer' in app.jinja_env.globals.keys()
assert app.jinja_env.globals['get_answer'] is get_answer
assert app.jinja_env.globals['get_answer']() == 42
with app.app_context():
rv = flask.render_template_string('{{ get_answer() }}')
assert rv == '42'
def test_request_processing(app, client):
bp = flask.Blueprint('bp', __name__)
evts = []
@bp.before_request
def before_bp():
evts.append('before')
@bp.after_request
def after_bp(response):
response.data += b'|after'
evts.append('after')
return response
@bp.teardown_request
def teardown_bp(exc):
evts.append('teardown')
# Setup routes for testing
@bp.route('/bp')
def bp_endpoint():
return 'request'
app.register_blueprint(bp)
assert evts == []
rv = client.get('/bp')
assert rv.data == b'request|after'
assert evts == ['before', 'after', 'teardown']
def test_app_request_processing(app, client):
bp = flask.Blueprint('bp', __name__)
evts = []
@bp.before_app_first_request
def before_first_request():
evts.append('first')
@bp.before_app_request
def before_app():
evts.append('before')
@bp.after_app_request
def after_app(response):
response.data += b'|after'
evts.append('after')
return response
@bp.teardown_app_request
def teardown_app(exc):
evts.append('teardown')
app.register_blueprint(bp)
# Setup routes for testing
@app.route('/')
def bp_endpoint():
return 'request'
# before first request
assert evts == []
# first request
resp = client.get('/').data
assert resp == b'request|after'
assert evts == ['first', 'before', 'after', 'teardown']
# second request
resp = client.get('/').data
assert resp == b'request|after'
assert evts == ['first'] + ['before', 'after', 'teardown'] * 2
def test_app_url_processors(app, client):
bp = flask.Blueprint('bp', __name__)
# Register app-wide url defaults and preprocessor on blueprint
@bp.app_url_defaults
def add_language_code(endpoint, values):
values.setdefault('lang_code', flask.g.lang_code)
@bp.app_url_value_preprocessor
def pull_lang_code(endpoint, values):
flask.g.lang_code = values.pop('lang_code')
# Register route rules at the app level
@app.route('/<lang_code>/')
def index():
return flask.url_for('about')
@app.route('/<lang_code>/about')
def about():
return flask.url_for('index')
app.register_blueprint(bp)
assert client.get('/de/').data == b'/de/about'
assert client.get('/de/about').data == b'/de/'