# -*- coding: utf-8 -*- """ tests.blueprints ~~~~~~~~~~~~~~~~ Blueprints (and currently modules) :copyright: © 2010 by the Pallets team. :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='/') @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/') 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 ) foo_foo_foo.__name__ = 'bar.123' pytest.raises( AssertionError, lambda: bp.add_url_rule( '/bar/123', view_func=foo_foo_foo ) ) 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('//') def index(): return flask.url_for('about') @app.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/'