mirror of https://github.com/pallets/flask.git
				
				
				
			
		
			
				
	
	
		
			542 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			542 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
| # -*- coding: utf-8 -*-
 | |
| """
 | |
|     tests.test_cli
 | |
|     ~~~~~~~~~~~~~~
 | |
| 
 | |
|     :copyright: © 2010 by the Pallets team.
 | |
|     :license: BSD, see LICENSE for more details.
 | |
| """
 | |
| 
 | |
| # This file was part of Flask-CLI and was modified under the terms of
 | |
| # its Revised BSD License. Copyright © 2015 CERN.
 | |
| 
 | |
| from __future__ import absolute_import
 | |
| 
 | |
| import os
 | |
| import ssl
 | |
| import sys
 | |
| import types
 | |
| from functools import partial
 | |
| 
 | |
| import click
 | |
| import pytest
 | |
| from _pytest.monkeypatch import notset
 | |
| from click.testing import CliRunner
 | |
| 
 | |
| from flask import Flask, current_app
 | |
| from flask.cli import (
 | |
|     AppGroup, FlaskGroup, NoAppException, ScriptInfo, dotenv, find_best_app,
 | |
|     get_version, load_dotenv, locate_app, prepare_import, run_command,
 | |
|     with_appcontext
 | |
| )
 | |
| 
 | |
| cwd = os.getcwd()
 | |
| test_path = os.path.abspath(os.path.join(
 | |
|     os.path.dirname(__file__), 'test_apps'
 | |
| ))
 | |
| 
 | |
| 
 | |
| @pytest.fixture
 | |
| def runner():
 | |
|     return CliRunner()
 | |
| 
 | |
| 
 | |
| def test_cli_name(test_apps):
 | |
|     """Make sure the CLI object's name is the app's name and not the app itself"""
 | |
|     from cliapp.app import testapp
 | |
|     assert testapp.cli.name == testapp.name
 | |
| 
 | |
| 
 | |
| def test_find_best_app(test_apps):
 | |
|     """Test if `find_best_app` behaves as expected with different combinations of input."""
 | |
|     script_info = ScriptInfo()
 | |
| 
 | |
|     class Module:
 | |
|         app = Flask('appname')
 | |
| 
 | |
|     assert find_best_app(script_info, Module) == Module.app
 | |
| 
 | |
|     class Module:
 | |
|         application = Flask('appname')
 | |
| 
 | |
|     assert find_best_app(script_info, Module) == Module.application
 | |
| 
 | |
|     class Module:
 | |
|         myapp = Flask('appname')
 | |
| 
 | |
|     assert find_best_app(script_info, Module) == Module.myapp
 | |
| 
 | |
|     class Module:
 | |
|         @staticmethod
 | |
|         def create_app():
 | |
|             return Flask('appname')
 | |
| 
 | |
|     assert isinstance(find_best_app(script_info, Module), Flask)
 | |
|     assert find_best_app(script_info, Module).name == 'appname'
 | |
| 
 | |
|     class Module:
 | |
|         @staticmethod
 | |
|         def create_app(foo):
 | |
|             return Flask('appname')
 | |
| 
 | |
|     assert isinstance(find_best_app(script_info, Module), Flask)
 | |
|     assert find_best_app(script_info, Module).name == 'appname'
 | |
| 
 | |
|     class Module:
 | |
|         @staticmethod
 | |
|         def create_app(foo=None, script_info=None):
 | |
|             return Flask('appname')
 | |
| 
 | |
|     assert isinstance(find_best_app(script_info, Module), Flask)
 | |
|     assert find_best_app(script_info, Module).name == 'appname'
 | |
| 
 | |
|     class Module:
 | |
|         @staticmethod
 | |
|         def make_app():
 | |
|             return Flask('appname')
 | |
| 
 | |
|     assert isinstance(find_best_app(script_info, Module), Flask)
 | |
|     assert find_best_app(script_info, Module).name == 'appname'
 | |
| 
 | |
|     class Module:
 | |
|         myapp = Flask('appname1')
 | |
| 
 | |
|         @staticmethod
 | |
|         def create_app():
 | |
|             return Flask('appname2')
 | |
| 
 | |
|     assert find_best_app(script_info, Module) == Module.myapp
 | |
| 
 | |
|     class Module:
 | |
|         myapp = Flask('appname1')
 | |
| 
 | |
|         @staticmethod
 | |
|         def create_app():
 | |
|             return Flask('appname2')
 | |
| 
 | |
|     assert find_best_app(script_info, Module) == Module.myapp
 | |
| 
 | |
|     class Module:
 | |
|         pass
 | |
| 
 | |
|     pytest.raises(NoAppException, find_best_app, script_info, Module)
 | |
| 
 | |
|     class Module:
 | |
|         myapp1 = Flask('appname1')
 | |
|         myapp2 = Flask('appname2')
 | |
| 
 | |
|     pytest.raises(NoAppException, find_best_app, script_info, Module)
 | |
| 
 | |
|     class Module:
 | |
|         @staticmethod
 | |
|         def create_app(foo, bar):
 | |
|             return Flask('appname2')
 | |
| 
 | |
|     pytest.raises(NoAppException, find_best_app, script_info, Module)
 | |
| 
 | |
|     class Module:
 | |
|         @staticmethod
 | |
|         def create_app():
 | |
|             raise TypeError('bad bad factory!')
 | |
| 
 | |
|     pytest.raises(TypeError, find_best_app, script_info, Module)
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize('value,path,result', (
 | |
|     ('test', cwd, 'test'),
 | |
|     ('test.py', cwd, 'test'),
 | |
|     ('a/test', os.path.join(cwd, 'a'), 'test'),
 | |
|     ('test/__init__.py', cwd, 'test'),
 | |
|     ('test/__init__', cwd, 'test'),
 | |
|     # nested package
 | |
|     (
 | |
|         os.path.join(test_path, 'cliapp', 'inner1', '__init__'),
 | |
|         test_path, 'cliapp.inner1'
 | |
|     ),
 | |
|     (
 | |
|         os.path.join(test_path, 'cliapp', 'inner1', 'inner2'),
 | |
|         test_path, 'cliapp.inner1.inner2'
 | |
|     ),
 | |
|     # dotted name
 | |
|     ('test.a.b', cwd, 'test.a.b'),
 | |
|     (os.path.join(test_path, 'cliapp.app'), test_path, 'cliapp.app'),
 | |
|     # not a Python file, will be caught during import
 | |
|     (
 | |
|         os.path.join(test_path, 'cliapp', 'message.txt'),
 | |
|         test_path, 'cliapp.message.txt'
 | |
|     ),
 | |
| ))
 | |
| def test_prepare_import(request, value, path, result):
 | |
|     """Expect the correct path to be set and the correct import and app names
 | |
|     to be returned.
 | |
| 
 | |
|     :func:`prepare_exec_for_file` has a side effect where the parent directory
 | |
|     of the given import is added to :data:`sys.path`. This is reset after the
 | |
|     test runs.
 | |
|     """
 | |
|     original_path = sys.path[:]
 | |
| 
 | |
|     def reset_path():
 | |
|         sys.path[:] = original_path
 | |
| 
 | |
|     request.addfinalizer(reset_path)
 | |
| 
 | |
|     assert prepare_import(value) == result
 | |
|     assert sys.path[0] == path
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize('iname,aname,result', (
 | |
|     ('cliapp.app', None, 'testapp'),
 | |
|     ('cliapp.app', 'testapp', 'testapp'),
 | |
|     ('cliapp.factory', None, 'app'),
 | |
|     ('cliapp.factory', 'create_app', 'app'),
 | |
|     ('cliapp.factory', 'create_app()', 'app'),
 | |
|     # no script_info
 | |
|     ('cliapp.factory', 'create_app2("foo", "bar")', 'app2_foo_bar'),
 | |
|     # trailing comma space
 | |
|     ('cliapp.factory', 'create_app2("foo", "bar", )', 'app2_foo_bar'),
 | |
|     # takes script_info
 | |
|     ('cliapp.factory', 'create_app3("foo")', 'app3_foo_spam'),
 | |
|     # strip whitespace
 | |
|     ('cliapp.factory', ' create_app () ', 'app'),
 | |
| ))
 | |
| def test_locate_app(test_apps, iname, aname, result):
 | |
|     info = ScriptInfo()
 | |
|     info.data['test'] = 'spam'
 | |
|     assert locate_app(info, iname, aname).name == result
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize('iname,aname', (
 | |
|     ('notanapp.py', None),
 | |
|     ('cliapp/app', None),
 | |
|     ('cliapp.app', 'notanapp'),
 | |
|     # not enough arguments
 | |
|     ('cliapp.factory', 'create_app2("foo")'),
 | |
|     # invalid identifier
 | |
|     ('cliapp.factory', 'create_app('),
 | |
|     # no app returned
 | |
|     ('cliapp.factory', 'no_app'),
 | |
|     # nested import error
 | |
|     ('cliapp.importerrorapp', None),
 | |
|     # not a Python file
 | |
|     ('cliapp.message.txt', None),
 | |
| ))
 | |
| def test_locate_app_raises(test_apps, iname, aname):
 | |
|     info = ScriptInfo()
 | |
| 
 | |
|     with pytest.raises(NoAppException):
 | |
|         locate_app(info, iname, aname)
 | |
| 
 | |
| 
 | |
| def test_locate_app_suppress_raise():
 | |
|     info = ScriptInfo()
 | |
|     app = locate_app(info, 'notanapp.py', None, raise_if_not_found=False)
 | |
|     assert app is None
 | |
| 
 | |
|     # only direct import error is suppressed
 | |
|     with pytest.raises(NoAppException):
 | |
|         locate_app(
 | |
|             info, 'cliapp.importerrorapp', None, raise_if_not_found=False
 | |
|         )
 | |
| 
 | |
| 
 | |
| def test_get_version(test_apps, capsys):
 | |
|     """Test of get_version."""
 | |
|     from flask import __version__ as flask_ver
 | |
|     from sys import version as py_ver
 | |
| 
 | |
|     class MockCtx(object):
 | |
|         resilient_parsing = False
 | |
|         color = None
 | |
| 
 | |
|         def exit(self): return
 | |
| 
 | |
|     ctx = MockCtx()
 | |
|     get_version(ctx, None, "test")
 | |
|     out, err = capsys.readouterr()
 | |
|     assert flask_ver in out
 | |
|     assert py_ver in out
 | |
| 
 | |
| 
 | |
| def test_scriptinfo(test_apps, monkeypatch):
 | |
|     """Test of ScriptInfo."""
 | |
|     obj = ScriptInfo(app_import_path="cliapp.app:testapp")
 | |
|     assert obj.load_app().name == "testapp"
 | |
|     assert obj.load_app().name == "testapp"
 | |
| 
 | |
|     def create_app(info):
 | |
|         return Flask("createapp")
 | |
| 
 | |
|     obj = ScriptInfo(create_app=create_app)
 | |
|     app = obj.load_app()
 | |
|     assert app.name == "createapp"
 | |
|     assert obj.load_app() == app
 | |
| 
 | |
|     obj = ScriptInfo()
 | |
|     pytest.raises(NoAppException, obj.load_app)
 | |
| 
 | |
|     # import app from wsgi.py in current directory
 | |
|     monkeypatch.chdir(os.path.abspath(os.path.join(
 | |
|         os.path.dirname(__file__), 'test_apps', 'helloworld'
 | |
|     )))
 | |
|     obj = ScriptInfo()
 | |
|     app = obj.load_app()
 | |
|     assert app.name == 'hello'
 | |
| 
 | |
|     # import app from app.py in current directory
 | |
|     monkeypatch.chdir(os.path.abspath(os.path.join(
 | |
|         os.path.dirname(__file__), 'test_apps', 'cliapp'
 | |
|     )))
 | |
|     obj = ScriptInfo()
 | |
|     app = obj.load_app()
 | |
|     assert app.name == 'testapp'
 | |
| 
 | |
| 
 | |
| def test_with_appcontext(runner):
 | |
|     """Test of with_appcontext."""
 | |
| 
 | |
|     @click.command()
 | |
|     @with_appcontext
 | |
|     def testcmd():
 | |
|         click.echo(current_app.name)
 | |
| 
 | |
|     obj = ScriptInfo(create_app=lambda info: Flask("testapp"))
 | |
| 
 | |
|     result = runner.invoke(testcmd, obj=obj)
 | |
|     assert result.exit_code == 0
 | |
|     assert result.output == 'testapp\n'
 | |
| 
 | |
| 
 | |
| def test_appgroup(runner):
 | |
|     """Test of with_appcontext."""
 | |
| 
 | |
|     @click.group(cls=AppGroup)
 | |
|     def cli():
 | |
|         pass
 | |
| 
 | |
|     @cli.command(with_appcontext=True)
 | |
|     def test():
 | |
|         click.echo(current_app.name)
 | |
| 
 | |
|     @cli.group()
 | |
|     def subgroup():
 | |
|         pass
 | |
| 
 | |
|     @subgroup.command(with_appcontext=True)
 | |
|     def test2():
 | |
|         click.echo(current_app.name)
 | |
| 
 | |
|     obj = ScriptInfo(create_app=lambda info: Flask("testappgroup"))
 | |
| 
 | |
|     result = runner.invoke(cli, ['test'], obj=obj)
 | |
|     assert result.exit_code == 0
 | |
|     assert result.output == 'testappgroup\n'
 | |
| 
 | |
|     result = runner.invoke(cli, ['subgroup', 'test2'], obj=obj)
 | |
|     assert result.exit_code == 0
 | |
|     assert result.output == 'testappgroup\n'
 | |
| 
 | |
| 
 | |
| def test_flaskgroup(runner):
 | |
|     """Test FlaskGroup."""
 | |
| 
 | |
|     def create_app(info):
 | |
|         return Flask("flaskgroup")
 | |
| 
 | |
|     @click.group(cls=FlaskGroup, create_app=create_app)
 | |
|     def cli(**params):
 | |
|         pass
 | |
| 
 | |
|     @cli.command()
 | |
|     def test():
 | |
|         click.echo(current_app.name)
 | |
| 
 | |
|     result = runner.invoke(cli, ['test'])
 | |
|     assert result.exit_code == 0
 | |
|     assert result.output == 'flaskgroup\n'
 | |
| 
 | |
| 
 | |
| def test_print_exceptions(runner):
 | |
|     """Print the stacktrace if the CLI."""
 | |
| 
 | |
|     def create_app(info):
 | |
|         raise Exception("oh no")
 | |
|         return Flask("flaskgroup")
 | |
| 
 | |
|     @click.group(cls=FlaskGroup, create_app=create_app)
 | |
|     def cli(**params):
 | |
|         pass
 | |
| 
 | |
|     result = runner.invoke(cli, ['--help'])
 | |
|     assert result.exit_code == 0
 | |
|     assert 'Exception: oh no' in result.output
 | |
|     assert 'Traceback' in result.output
 | |
| 
 | |
| 
 | |
| class TestRoutes:
 | |
|     @pytest.fixture
 | |
|     def invoke(self, runner):
 | |
|         def create_app(info):
 | |
|             app = Flask(__name__)
 | |
|             app.testing = True
 | |
| 
 | |
|             @app.route('/get_post/<int:x>/<int:y>', methods=['GET', 'POST'])
 | |
|             def yyy_get_post(x, y):
 | |
|                 pass
 | |
| 
 | |
|             @app.route('/zzz_post', methods=['POST'])
 | |
|             def aaa_post():
 | |
|                 pass
 | |
| 
 | |
|             return app
 | |
| 
 | |
|         cli = FlaskGroup(create_app=create_app)
 | |
|         return partial(runner.invoke, cli)
 | |
| 
 | |
|     def expect_order(self, order, output):
 | |
|         # skip the header and match the start of each row
 | |
|         for expect, line in zip(order, output.splitlines()[2:]):
 | |
|             # do this instead of startswith for nicer pytest output
 | |
|             assert line[:len(expect)] == expect
 | |
| 
 | |
|     def test_simple(self, invoke):
 | |
|         result = invoke(['routes'])
 | |
|         assert result.exit_code == 0
 | |
|         self.expect_order(
 | |
|             ['aaa_post', 'static', 'yyy_get_post'],
 | |
|             result.output
 | |
|         )
 | |
| 
 | |
|     def test_sort(self, invoke):
 | |
|         default_output = invoke(['routes']).output
 | |
|         endpoint_output = invoke(['routes', '-s', 'endpoint']).output
 | |
|         assert default_output == endpoint_output
 | |
|         self.expect_order(
 | |
|             ['static', 'yyy_get_post', 'aaa_post'],
 | |
|             invoke(['routes', '-s', 'methods']).output
 | |
|         )
 | |
|         self.expect_order(
 | |
|             ['yyy_get_post', 'static', 'aaa_post'],
 | |
|             invoke(['routes', '-s', 'rule']).output
 | |
|         )
 | |
|         self.expect_order(
 | |
|             ['aaa_post', 'yyy_get_post', 'static'],
 | |
|             invoke(['routes', '-s', 'match']).output
 | |
|         )
 | |
| 
 | |
|     def test_all_methods(self, invoke):
 | |
|         output = invoke(['routes']).output
 | |
|         assert 'GET, HEAD, OPTIONS, POST' not in output
 | |
|         output = invoke(['routes', '--all-methods']).output
 | |
|         assert 'GET, HEAD, OPTIONS, POST' in output
 | |
| 
 | |
| 
 | |
| need_dotenv = pytest.mark.skipif(
 | |
|     dotenv is None, reason='dotenv is not installed'
 | |
| )
 | |
| 
 | |
| 
 | |
| @need_dotenv
 | |
| def test_load_dotenv(monkeypatch):
 | |
|     # can't use monkeypatch.delitem since the keys don't exist yet
 | |
|     for item in ('FOO', 'BAR', 'SPAM'):
 | |
|         monkeypatch._setitem.append((os.environ, item, notset))
 | |
| 
 | |
|     monkeypatch.setenv('EGGS', '3')
 | |
|     monkeypatch.chdir(os.path.join(test_path, 'cliapp', 'inner1'))
 | |
|     load_dotenv()
 | |
|     assert os.getcwd() == test_path
 | |
|     # .flaskenv doesn't overwrite .env
 | |
|     assert os.environ['FOO'] == 'env'
 | |
|     # set only in .flaskenv
 | |
|     assert os.environ['BAR'] == 'bar'
 | |
|     # set only in .env
 | |
|     assert os.environ['SPAM'] == '1'
 | |
|     # set manually, files don't overwrite
 | |
|     assert os.environ['EGGS'] == '3'
 | |
| 
 | |
| 
 | |
| @need_dotenv
 | |
| def test_dotenv_path(monkeypatch):
 | |
|     for item in ('FOO', 'BAR', 'EGGS'):
 | |
|         monkeypatch._setitem.append((os.environ, item, notset))
 | |
| 
 | |
|     cwd = os.getcwd()
 | |
|     load_dotenv(os.path.join(test_path, '.flaskenv'))
 | |
|     assert os.getcwd() == cwd
 | |
|     assert 'FOO' in os.environ
 | |
| 
 | |
| 
 | |
| def test_dotenv_optional(monkeypatch):
 | |
|     monkeypatch.setattr('flask.cli.dotenv', None)
 | |
|     monkeypatch.chdir(test_path)
 | |
|     load_dotenv()
 | |
|     assert 'FOO' not in os.environ
 | |
| 
 | |
| 
 | |
| @need_dotenv
 | |
| def test_disable_dotenv_from_env(monkeypatch, runner):
 | |
|     monkeypatch.chdir(test_path)
 | |
|     monkeypatch.setitem(os.environ, 'FLASK_SKIP_DOTENV', '1')
 | |
|     runner.invoke(FlaskGroup())
 | |
|     assert 'FOO' not in os.environ
 | |
| 
 | |
| 
 | |
| def test_run_cert_path():
 | |
|     # no key
 | |
|     with pytest.raises(click.BadParameter):
 | |
|         run_command.make_context('run', ['--cert', __file__])
 | |
| 
 | |
|     # no cert
 | |
|     with pytest.raises(click.BadParameter):
 | |
|         run_command.make_context('run', ['--key', __file__])
 | |
| 
 | |
|     ctx = run_command.make_context(
 | |
|         'run', ['--cert', __file__, '--key', __file__])
 | |
|     assert ctx.params['cert'] == (__file__, __file__)
 | |
| 
 | |
| 
 | |
| def test_run_cert_adhoc(monkeypatch):
 | |
|     monkeypatch.setitem(sys.modules, 'OpenSSL', None)
 | |
| 
 | |
|     # pyOpenSSL not installed
 | |
|     with pytest.raises(click.BadParameter):
 | |
|         run_command.make_context('run', ['--cert', 'adhoc'])
 | |
| 
 | |
|     # pyOpenSSL installed
 | |
|     monkeypatch.setitem(sys.modules, 'OpenSSL', types.ModuleType('OpenSSL'))
 | |
|     ctx = run_command.make_context('run', ['--cert', 'adhoc'])
 | |
|     assert ctx.params['cert'] == 'adhoc'
 | |
| 
 | |
|     # no key with adhoc
 | |
|     with pytest.raises(click.BadParameter):
 | |
|         run_command.make_context('run', ['--cert', 'adhoc', '--key', __file__])
 | |
| 
 | |
| 
 | |
| def test_run_cert_import(monkeypatch):
 | |
|     monkeypatch.setitem(sys.modules, 'not_here', None)
 | |
| 
 | |
|     # ImportError
 | |
|     with pytest.raises(click.BadParameter):
 | |
|         run_command.make_context('run', ['--cert', 'not_here'])
 | |
| 
 | |
|     # not an SSLContext
 | |
|     if sys.version_info >= (2, 7):
 | |
|         with pytest.raises(click.BadParameter):
 | |
|             run_command.make_context('run', ['--cert', 'flask'])
 | |
| 
 | |
|     # SSLContext
 | |
|     if sys.version_info < (2, 7):
 | |
|         ssl_context = object()
 | |
|     else:
 | |
|         ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
 | |
| 
 | |
|     monkeypatch.setitem(sys.modules, 'ssl_context', ssl_context)
 | |
|     ctx = run_command.make_context('run', ['--cert', 'ssl_context'])
 | |
|     assert ctx.params['cert'] is ssl_context
 | |
| 
 | |
|     # no --key with SSLContext
 | |
|     with pytest.raises(click.BadParameter):
 | |
|         run_command.make_context(
 | |
|             'run', ['--cert', 'ssl_context', '--key', __file__])
 |