2018-02-09 02:57:40 +08:00
|
|
|
# This file was part of Flask-CLI and was modified under the terms of
|
|
|
|
# its Revised BSD License. Copyright © 2015 CERN.
|
2016-06-06 01:32:00 +08:00
|
|
|
import os
|
2021-10-06 00:11:00 +08:00
|
|
|
import platform
|
2018-01-23 04:16:37 +08:00
|
|
|
import ssl
|
2016-06-06 01:32:00 +08:00
|
|
|
import sys
|
2018-01-23 04:16:37 +08:00
|
|
|
import types
|
2017-04-26 05:15:38 +08:00
|
|
|
from functools import partial
|
2021-05-17 10:34:32 +08:00
|
|
|
from pathlib import Path
|
2016-05-17 01:36:55 +08:00
|
|
|
|
|
|
|
import click
|
|
|
|
import pytest
|
2017-07-15 13:37:53 +08:00
|
|
|
from _pytest.monkeypatch import notset
|
2016-05-17 01:36:55 +08:00
|
|
|
from click.testing import CliRunner
|
|
|
|
|
2019-06-01 23:35:03 +08:00
|
|
|
from flask import Blueprint
|
|
|
|
from flask import current_app
|
|
|
|
from flask import Flask
|
|
|
|
from flask.cli import AppGroup
|
2021-06-23 07:36:03 +08:00
|
|
|
from flask.cli import DispatchingApp
|
2019-06-01 23:35:03 +08:00
|
|
|
from flask.cli import find_best_app
|
|
|
|
from flask.cli import FlaskGroup
|
|
|
|
from flask.cli import get_version
|
|
|
|
from flask.cli import load_dotenv
|
|
|
|
from flask.cli import locate_app
|
|
|
|
from flask.cli import NoAppException
|
|
|
|
from flask.cli import prepare_import
|
|
|
|
from flask.cli import run_command
|
|
|
|
from flask.cli import ScriptInfo
|
|
|
|
from flask.cli import with_appcontext
|
2017-07-15 13:37:53 +08:00
|
|
|
|
2021-05-17 10:34:32 +08:00
|
|
|
cwd = Path.cwd()
|
|
|
|
test_path = (Path(__file__) / ".." / "test_apps").resolve()
|
2017-07-15 13:37:53 +08:00
|
|
|
|
|
|
|
|
2016-06-25 19:24:43 +08:00
|
|
|
@pytest.fixture
|
|
|
|
def runner():
|
|
|
|
return CliRunner()
|
|
|
|
|
|
|
|
|
2016-05-17 01:36:55 +08:00
|
|
|
def test_cli_name(test_apps):
|
2016-05-25 03:06:34 +08:00
|
|
|
"""Make sure the CLI object's name is the app's name and not the app itself"""
|
2016-05-17 01:36:55 +08:00
|
|
|
from cliapp.app import testapp
|
2019-05-07 03:39:41 +08:00
|
|
|
|
2016-05-17 01:36:55 +08:00
|
|
|
assert testapp.cli.name == testapp.name
|
|
|
|
|
|
|
|
|
|
|
|
def test_find_best_app(test_apps):
|
2016-07-06 03:46:01 +08:00
|
|
|
class Module:
|
2019-05-07 03:39:41 +08:00
|
|
|
app = Flask("appname")
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2021-11-12 22:46:54 +08:00
|
|
|
assert find_best_app(Module) == Module.app
|
2016-05-17 01:36:55 +08:00
|
|
|
|
2016-07-06 03:46:01 +08:00
|
|
|
class Module:
|
2019-05-07 03:39:41 +08:00
|
|
|
application = Flask("appname")
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2021-11-12 22:46:54 +08:00
|
|
|
assert find_best_app(Module) == Module.application
|
2016-05-17 01:36:55 +08:00
|
|
|
|
2016-07-06 03:46:01 +08:00
|
|
|
class Module:
|
2019-05-07 03:39:41 +08:00
|
|
|
myapp = Flask("appname")
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2021-11-12 22:46:54 +08:00
|
|
|
assert find_best_app(Module) == Module.myapp
|
2016-05-17 01:36:55 +08:00
|
|
|
|
2017-05-23 03:30:18 +08:00
|
|
|
class Module:
|
|
|
|
@staticmethod
|
|
|
|
def create_app():
|
2019-05-07 03:39:41 +08:00
|
|
|
return Flask("appname")
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2021-11-12 22:46:54 +08:00
|
|
|
app = find_best_app(Module)
|
2020-04-08 06:54:36 +08:00
|
|
|
assert isinstance(app, Flask)
|
|
|
|
assert app.name == "appname"
|
2017-05-24 04:46:45 +08:00
|
|
|
|
2021-06-25 10:39:13 +08:00
|
|
|
class Module:
|
|
|
|
@staticmethod
|
|
|
|
def create_app(**kwargs):
|
|
|
|
return Flask("appname")
|
|
|
|
|
2021-11-12 22:46:54 +08:00
|
|
|
app = find_best_app(Module)
|
2020-04-08 06:54:36 +08:00
|
|
|
assert isinstance(app, Flask)
|
|
|
|
assert app.name == "appname"
|
2017-05-23 03:30:18 +08:00
|
|
|
|
|
|
|
class Module:
|
|
|
|
@staticmethod
|
|
|
|
def make_app():
|
2019-05-07 03:39:41 +08:00
|
|
|
return Flask("appname")
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2021-11-12 22:46:54 +08:00
|
|
|
app = find_best_app(Module)
|
2020-04-08 06:54:36 +08:00
|
|
|
assert isinstance(app, Flask)
|
|
|
|
assert app.name == "appname"
|
2017-05-23 03:30:18 +08:00
|
|
|
|
|
|
|
class Module:
|
2019-05-07 03:39:41 +08:00
|
|
|
myapp = Flask("appname1")
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2017-05-23 03:30:18 +08:00
|
|
|
@staticmethod
|
|
|
|
def create_app():
|
2019-05-07 03:39:41 +08:00
|
|
|
return Flask("appname2")
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2021-11-12 22:46:54 +08:00
|
|
|
assert find_best_app(Module) == Module.myapp
|
2017-05-23 03:30:18 +08:00
|
|
|
|
|
|
|
class Module:
|
2019-05-07 03:39:41 +08:00
|
|
|
myapp = Flask("appname1")
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2017-05-23 03:30:18 +08:00
|
|
|
@staticmethod
|
2017-05-24 04:46:45 +08:00
|
|
|
def create_app():
|
2019-05-07 03:39:41 +08:00
|
|
|
return Flask("appname2")
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2021-11-12 22:46:54 +08:00
|
|
|
assert find_best_app(Module) == Module.myapp
|
2017-05-23 03:30:18 +08:00
|
|
|
|
2016-07-06 03:46:01 +08:00
|
|
|
class Module:
|
|
|
|
pass
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2021-11-12 22:46:54 +08:00
|
|
|
pytest.raises(NoAppException, find_best_app, Module)
|
2016-05-17 01:36:55 +08:00
|
|
|
|
2016-07-06 03:46:01 +08:00
|
|
|
class Module:
|
2019-05-07 03:39:41 +08:00
|
|
|
myapp1 = Flask("appname1")
|
|
|
|
myapp2 = Flask("appname2")
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2021-11-12 22:46:54 +08:00
|
|
|
pytest.raises(NoAppException, find_best_app, Module)
|
2016-05-17 01:36:55 +08:00
|
|
|
|
2017-05-23 03:30:18 +08:00
|
|
|
class Module:
|
|
|
|
@staticmethod
|
2017-05-24 04:46:45 +08:00
|
|
|
def create_app(foo, bar):
|
2019-05-07 03:39:41 +08:00
|
|
|
return Flask("appname2")
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2021-11-12 22:46:54 +08:00
|
|
|
pytest.raises(NoAppException, find_best_app, Module)
|
2017-05-23 03:30:18 +08:00
|
|
|
|
2017-11-25 07:05:57 +08:00
|
|
|
class Module:
|
|
|
|
@staticmethod
|
|
|
|
def create_app():
|
2019-05-07 03:39:41 +08:00
|
|
|
raise TypeError("bad bad factory!")
|
2017-11-25 07:05:57 +08:00
|
|
|
|
2021-11-12 22:46:54 +08:00
|
|
|
pytest.raises(TypeError, find_best_app, Module)
|
2017-11-25 07:05:57 +08:00
|
|
|
|
2016-05-17 01:36:55 +08:00
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value,path,result",
|
2017-07-15 10:27:45 +08:00
|
|
|
(
|
2019-05-07 03:39:41 +08:00
|
|
|
("test", cwd, "test"),
|
|
|
|
("test.py", cwd, "test"),
|
2021-05-17 10:34:32 +08:00
|
|
|
("a/test", cwd / "a", "test"),
|
2019-05-07 03:39:41 +08:00
|
|
|
("test/__init__.py", cwd, "test"),
|
|
|
|
("test/__init__", cwd, "test"),
|
|
|
|
# nested package
|
|
|
|
(
|
2021-05-17 10:34:32 +08:00
|
|
|
test_path / "cliapp" / "inner1" / "__init__",
|
2019-05-07 03:39:41 +08:00
|
|
|
test_path,
|
|
|
|
"cliapp.inner1",
|
|
|
|
),
|
|
|
|
(
|
2021-05-17 10:34:32 +08:00
|
|
|
test_path / "cliapp" / "inner1" / "inner2",
|
2019-05-07 03:39:41 +08:00
|
|
|
test_path,
|
|
|
|
"cliapp.inner1.inner2",
|
|
|
|
),
|
|
|
|
# dotted name
|
|
|
|
("test.a.b", cwd, "test.a.b"),
|
2021-05-17 10:34:32 +08:00
|
|
|
(test_path / "cliapp.app", test_path, "cliapp.app"),
|
2019-05-07 03:39:41 +08:00
|
|
|
# not a Python file, will be caught during import
|
2021-05-17 10:34:32 +08:00
|
|
|
(test_path / "cliapp" / "message.txt", test_path, "cliapp.message.txt"),
|
2017-07-15 10:27:45 +08:00
|
|
|
),
|
2019-05-07 03:39:41 +08:00
|
|
|
)
|
2017-07-15 10:27:45 +08:00
|
|
|
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.
|
2016-06-06 01:32:00 +08:00
|
|
|
"""
|
2017-07-15 10:27:45 +08:00
|
|
|
original_path = sys.path[:]
|
|
|
|
|
|
|
|
def reset_path():
|
|
|
|
sys.path[:] = original_path
|
|
|
|
|
|
|
|
request.addfinalizer(reset_path)
|
|
|
|
|
|
|
|
assert prepare_import(value) == result
|
2021-05-17 10:34:32 +08:00
|
|
|
assert sys.path[0] == str(path)
|
2017-07-15 10:27:45 +08:00
|
|
|
|
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
@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"),
|
|
|
|
("cliapp.factory", 'create_app2("foo", "bar")', "app2_foo_bar"),
|
|
|
|
# trailing comma space
|
|
|
|
("cliapp.factory", 'create_app2("foo", "bar", )', "app2_foo_bar"),
|
|
|
|
# strip whitespace
|
|
|
|
("cliapp.factory", " create_app () ", "app"),
|
|
|
|
),
|
|
|
|
)
|
2017-07-15 10:27:45 +08:00
|
|
|
def test_locate_app(test_apps, iname, aname, result):
|
2021-11-12 22:46:54 +08:00
|
|
|
assert locate_app(iname, aname).name == result
|
2017-07-15 10:27:45 +08:00
|
|
|
|
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
@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),
|
|
|
|
),
|
|
|
|
)
|
2017-07-15 10:27:45 +08:00
|
|
|
def test_locate_app_raises(test_apps, iname, aname):
|
2016-06-04 00:41:10 +08:00
|
|
|
with pytest.raises(NoAppException):
|
2021-11-12 22:46:54 +08:00
|
|
|
locate_app(iname, aname)
|
2016-06-04 00:41:10 +08:00
|
|
|
|
|
|
|
|
2020-07-31 09:36:55 +08:00
|
|
|
def test_locate_app_suppress_raise(test_apps):
|
2021-11-12 22:46:54 +08:00
|
|
|
app = locate_app("notanapp.py", None, raise_if_not_found=False)
|
2017-07-15 10:27:45 +08:00
|
|
|
assert app is None
|
|
|
|
|
|
|
|
# only direct import error is suppressed
|
|
|
|
with pytest.raises(NoAppException):
|
2021-11-12 22:46:54 +08:00
|
|
|
locate_app("cliapp.importerrorapp", None, raise_if_not_found=False)
|
2016-06-07 20:03:55 +08:00
|
|
|
|
|
|
|
|
2016-08-20 23:43:58 +08:00
|
|
|
def test_get_version(test_apps, capsys):
|
2019-01-07 08:17:33 +08:00
|
|
|
from flask import __version__ as flask_version
|
|
|
|
from werkzeug import __version__ as werkzeug_version
|
|
|
|
from platform import python_version
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2020-04-05 00:43:06 +08:00
|
|
|
class MockCtx:
|
2016-08-20 23:43:58 +08:00
|
|
|
resilient_parsing = False
|
|
|
|
color = None
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
def exit(self):
|
|
|
|
return
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2016-08-20 23:43:58 +08:00
|
|
|
ctx = MockCtx()
|
|
|
|
get_version(ctx, None, "test")
|
|
|
|
out, err = capsys.readouterr()
|
2020-04-05 02:39:03 +08:00
|
|
|
assert f"Python {python_version()}" in out
|
|
|
|
assert f"Flask {flask_version}" in out
|
|
|
|
assert f"Werkzeug {werkzeug_version}" in out
|
2016-08-20 23:43:58 +08:00
|
|
|
|
|
|
|
|
2017-06-16 02:27:50 +08:00
|
|
|
def test_scriptinfo(test_apps, monkeypatch):
|
2016-05-17 01:36:55 +08:00
|
|
|
obj = ScriptInfo(app_import_path="cliapp.app:testapp")
|
2018-10-24 21:13:11 +08:00
|
|
|
app = obj.load_app()
|
|
|
|
assert app.name == "testapp"
|
|
|
|
assert obj.load_app() is app
|
|
|
|
|
|
|
|
# import app with module's absolute path
|
2021-05-17 10:34:32 +08:00
|
|
|
cli_app_path = str(test_path / "cliapp" / "app.py")
|
|
|
|
|
2018-10-24 21:13:11 +08:00
|
|
|
obj = ScriptInfo(app_import_path=cli_app_path)
|
|
|
|
app = obj.load_app()
|
2019-05-07 03:39:41 +08:00
|
|
|
assert app.name == "testapp"
|
2018-10-24 21:13:11 +08:00
|
|
|
assert obj.load_app() is app
|
2020-04-05 02:39:03 +08:00
|
|
|
obj = ScriptInfo(app_import_path=f"{cli_app_path}:testapp")
|
2018-10-24 21:13:11 +08:00
|
|
|
app = obj.load_app()
|
2019-05-07 03:39:41 +08:00
|
|
|
assert app.name == "testapp"
|
2018-10-24 21:13:11 +08:00
|
|
|
assert obj.load_app() is app
|
2016-05-17 01:36:55 +08:00
|
|
|
|
2020-04-08 06:54:36 +08:00
|
|
|
def create_app():
|
2016-05-17 01:36:55 +08:00
|
|
|
return Flask("createapp")
|
|
|
|
|
|
|
|
obj = ScriptInfo(create_app=create_app)
|
|
|
|
app = obj.load_app()
|
|
|
|
assert app.name == "createapp"
|
2018-10-24 21:13:11 +08:00
|
|
|
assert obj.load_app() is app
|
2016-05-17 01:36:55 +08:00
|
|
|
|
2017-06-16 02:27:50 +08:00
|
|
|
obj = ScriptInfo()
|
2017-06-16 21:59:37 +08:00
|
|
|
pytest.raises(NoAppException, obj.load_app)
|
2017-06-16 02:27:50 +08:00
|
|
|
|
|
|
|
# import app from wsgi.py in current directory
|
2021-05-17 10:34:32 +08:00
|
|
|
monkeypatch.chdir(test_path / "helloworld")
|
2017-06-16 02:27:50 +08:00
|
|
|
obj = ScriptInfo()
|
|
|
|
app = obj.load_app()
|
2019-05-07 03:39:41 +08:00
|
|
|
assert app.name == "hello"
|
2017-06-16 02:27:50 +08:00
|
|
|
|
|
|
|
# import app from app.py in current directory
|
2021-05-17 10:34:32 +08:00
|
|
|
monkeypatch.chdir(test_path / "cliapp")
|
2017-06-16 02:27:50 +08:00
|
|
|
obj = ScriptInfo()
|
|
|
|
app = obj.load_app()
|
2019-05-07 03:39:41 +08:00
|
|
|
assert app.name == "testapp"
|
2017-06-16 02:27:50 +08:00
|
|
|
|
2016-05-17 01:36:55 +08:00
|
|
|
|
2021-10-06 00:11:00 +08:00
|
|
|
@pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="flaky on pypy")
|
2021-08-06 10:15:31 +08:00
|
|
|
def test_lazy_load_error(monkeypatch):
|
|
|
|
"""When using lazy loading, the correct exception should be
|
|
|
|
re-raised.
|
|
|
|
"""
|
|
|
|
|
|
|
|
class BadExc(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def bad_load():
|
|
|
|
raise BadExc
|
|
|
|
|
|
|
|
lazy = DispatchingApp(bad_load, use_eager_loading=False)
|
|
|
|
|
2022-04-29 00:32:31 +08:00
|
|
|
# reduce flakiness by waiting for the internal loading lock
|
|
|
|
with lazy._lock:
|
|
|
|
with pytest.raises(BadExc):
|
2021-10-05 23:03:30 +08:00
|
|
|
lazy._flush_bg_loading_exception()
|
2021-08-06 10:15:31 +08:00
|
|
|
|
|
|
|
|
2016-06-25 19:24:43 +08:00
|
|
|
def test_with_appcontext(runner):
|
2016-05-17 01:36:55 +08:00
|
|
|
@click.command()
|
|
|
|
@with_appcontext
|
|
|
|
def testcmd():
|
|
|
|
click.echo(current_app.name)
|
|
|
|
|
2020-04-08 06:54:36 +08:00
|
|
|
obj = ScriptInfo(create_app=lambda: Flask("testapp"))
|
2016-05-17 01:36:55 +08:00
|
|
|
|
|
|
|
result = runner.invoke(testcmd, obj=obj)
|
|
|
|
assert result.exit_code == 0
|
2019-05-07 03:39:41 +08:00
|
|
|
assert result.output == "testapp\n"
|
2016-05-17 01:36:55 +08:00
|
|
|
|
|
|
|
|
2016-06-25 19:24:43 +08:00
|
|
|
def test_appgroup(runner):
|
2016-05-17 01:36:55 +08:00
|
|
|
@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)
|
|
|
|
|
2020-04-08 06:54:36 +08:00
|
|
|
obj = ScriptInfo(create_app=lambda: Flask("testappgroup"))
|
2016-05-17 01:36:55 +08:00
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
result = runner.invoke(cli, ["test"], obj=obj)
|
2016-05-17 01:36:55 +08:00
|
|
|
assert result.exit_code == 0
|
2019-05-07 03:39:41 +08:00
|
|
|
assert result.output == "testappgroup\n"
|
2016-05-17 01:36:55 +08:00
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
result = runner.invoke(cli, ["subgroup", "test2"], obj=obj)
|
2016-05-17 01:36:55 +08:00
|
|
|
assert result.exit_code == 0
|
2019-05-07 03:39:41 +08:00
|
|
|
assert result.output == "testappgroup\n"
|
2016-05-17 01:36:55 +08:00
|
|
|
|
|
|
|
|
2016-06-25 19:24:43 +08:00
|
|
|
def test_flaskgroup(runner):
|
2020-04-08 06:54:36 +08:00
|
|
|
def create_app():
|
2016-05-17 01:36:55 +08:00
|
|
|
return Flask("flaskgroup")
|
|
|
|
|
|
|
|
@click.group(cls=FlaskGroup, create_app=create_app)
|
|
|
|
def cli(**params):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@cli.command()
|
|
|
|
def test():
|
|
|
|
click.echo(current_app.name)
|
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
result = runner.invoke(cli, ["test"])
|
2016-05-17 01:36:55 +08:00
|
|
|
assert result.exit_code == 0
|
2019-05-07 03:39:41 +08:00
|
|
|
assert result.output == "flaskgroup\n"
|
2016-06-25 19:17:33 +08:00
|
|
|
|
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
@pytest.mark.parametrize("set_debug_flag", (True, False))
|
2018-05-09 00:05:55 +08:00
|
|
|
def test_flaskgroup_debug(runner, set_debug_flag):
|
2020-04-08 06:54:36 +08:00
|
|
|
def create_app():
|
2018-05-09 00:05:55 +08:00
|
|
|
app = Flask("flaskgroup")
|
|
|
|
app.debug = True
|
|
|
|
return app
|
|
|
|
|
|
|
|
@click.group(cls=FlaskGroup, create_app=create_app, set_debug_flag=set_debug_flag)
|
|
|
|
def cli(**params):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@cli.command()
|
|
|
|
def test():
|
|
|
|
click.echo(str(current_app.debug))
|
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
result = runner.invoke(cli, ["test"])
|
2018-05-09 00:05:55 +08:00
|
|
|
assert result.exit_code == 0
|
2020-04-05 02:39:03 +08:00
|
|
|
assert result.output == f"{not set_debug_flag}\n"
|
2018-05-09 00:05:55 +08:00
|
|
|
|
|
|
|
|
2020-07-31 09:36:55 +08:00
|
|
|
def test_no_command_echo_loading_error():
|
|
|
|
from flask.cli import cli
|
2017-05-25 08:27:36 +08:00
|
|
|
|
2020-07-31 09:36:55 +08:00
|
|
|
runner = CliRunner(mix_stderr=False)
|
|
|
|
result = runner.invoke(cli, ["missing"])
|
|
|
|
assert result.exit_code == 2
|
|
|
|
assert "FLASK_APP" in result.stderr
|
|
|
|
assert "Usage:" in result.stderr
|
|
|
|
|
|
|
|
|
|
|
|
def test_help_echo_loading_error():
|
|
|
|
from flask.cli import cli
|
|
|
|
|
|
|
|
runner = CliRunner(mix_stderr=False)
|
|
|
|
result = runner.invoke(cli, ["--help"])
|
|
|
|
assert result.exit_code == 0
|
|
|
|
assert "FLASK_APP" in result.stderr
|
|
|
|
assert "Usage:" in result.stdout
|
|
|
|
|
|
|
|
|
|
|
|
def test_help_echo_exception():
|
2020-04-08 06:54:36 +08:00
|
|
|
def create_app():
|
2017-03-17 03:56:12 +08:00
|
|
|
raise Exception("oh no")
|
|
|
|
|
2020-07-31 09:36:55 +08:00
|
|
|
cli = FlaskGroup(create_app=create_app)
|
|
|
|
runner = CliRunner(mix_stderr=False)
|
2019-05-07 03:39:41 +08:00
|
|
|
result = runner.invoke(cli, ["--help"])
|
2017-03-17 03:56:12 +08:00
|
|
|
assert result.exit_code == 0
|
2020-07-31 09:36:55 +08:00
|
|
|
assert "Exception: oh no" in result.stderr
|
|
|
|
assert "Usage:" in result.stdout
|
2017-04-26 04:13:10 +08:00
|
|
|
|
|
|
|
|
2016-06-25 19:17:33 +08:00
|
|
|
class TestRoutes:
|
2017-04-26 05:15:38 +08:00
|
|
|
@pytest.fixture
|
|
|
|
def invoke(self, runner):
|
2020-04-08 06:54:36 +08:00
|
|
|
def create_app():
|
2017-04-26 05:15:38 +08:00
|
|
|
app = Flask(__name__)
|
|
|
|
app.testing = True
|
2016-06-25 19:17:33 +08:00
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
@app.route("/get_post/<int:x>/<int:y>", methods=["GET", "POST"])
|
2017-04-26 05:15:38 +08:00
|
|
|
def yyy_get_post(x, y):
|
|
|
|
pass
|
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
@app.route("/zzz_post", methods=["POST"])
|
2017-04-26 05:15:38 +08:00
|
|
|
def aaa_post():
|
|
|
|
pass
|
2016-06-25 19:17:33 +08:00
|
|
|
|
2017-04-26 05:15:38 +08:00
|
|
|
return app
|
|
|
|
|
|
|
|
cli = FlaskGroup(create_app=create_app)
|
|
|
|
return partial(runner.invoke, cli)
|
|
|
|
|
2018-05-31 11:43:51 +08:00
|
|
|
@pytest.fixture
|
|
|
|
def invoke_no_routes(self, runner):
|
2020-04-08 06:54:36 +08:00
|
|
|
def create_app():
|
2018-05-31 11:43:51 +08:00
|
|
|
app = Flask(__name__, static_folder=None)
|
|
|
|
app.testing = True
|
|
|
|
|
|
|
|
return app
|
|
|
|
|
|
|
|
cli = FlaskGroup(create_app=create_app)
|
|
|
|
return partial(runner.invoke, cli)
|
|
|
|
|
2017-04-26 05:15:38 +08:00
|
|
|
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
|
2019-05-07 03:39:41 +08:00
|
|
|
assert line[: len(expect)] == expect
|
2017-04-26 05:15:38 +08:00
|
|
|
|
|
|
|
def test_simple(self, invoke):
|
2019-05-07 03:39:41 +08:00
|
|
|
result = invoke(["routes"])
|
2016-06-25 19:17:33 +08:00
|
|
|
assert result.exit_code == 0
|
2019-05-07 03:39:41 +08:00
|
|
|
self.expect_order(["aaa_post", "static", "yyy_get_post"], result.output)
|
2017-04-26 05:15:38 +08:00
|
|
|
|
|
|
|
def test_sort(self, invoke):
|
2019-05-07 03:39:41 +08:00
|
|
|
default_output = invoke(["routes"]).output
|
|
|
|
endpoint_output = invoke(["routes", "-s", "endpoint"]).output
|
2017-04-26 05:15:38 +08:00
|
|
|
assert default_output == endpoint_output
|
|
|
|
self.expect_order(
|
2019-05-07 03:39:41 +08:00
|
|
|
["static", "yyy_get_post", "aaa_post"],
|
|
|
|
invoke(["routes", "-s", "methods"]).output,
|
2017-04-26 05:15:38 +08:00
|
|
|
)
|
|
|
|
self.expect_order(
|
2019-05-07 03:39:41 +08:00
|
|
|
["yyy_get_post", "static", "aaa_post"],
|
|
|
|
invoke(["routes", "-s", "rule"]).output,
|
2017-04-26 05:15:38 +08:00
|
|
|
)
|
|
|
|
self.expect_order(
|
2019-05-07 03:39:41 +08:00
|
|
|
["aaa_post", "yyy_get_post", "static"],
|
|
|
|
invoke(["routes", "-s", "match"]).output,
|
2017-04-26 05:15:38 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
def test_all_methods(self, invoke):
|
2019-05-07 03:39:41 +08:00
|
|
|
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
|
2017-07-15 13:37:53 +08:00
|
|
|
|
2018-05-31 11:43:51 +08:00
|
|
|
def test_no_routes(self, invoke_no_routes):
|
2019-05-07 03:39:41 +08:00
|
|
|
result = invoke_no_routes(["routes"])
|
2018-05-31 11:43:51 +08:00
|
|
|
assert result.exit_code == 0
|
2019-05-07 03:39:41 +08:00
|
|
|
assert "No routes were registered." in result.output
|
2018-05-31 11:43:51 +08:00
|
|
|
|
2017-07-15 13:37:53 +08:00
|
|
|
|
2022-05-24 00:46:20 +08:00
|
|
|
def dotenv_not_available():
|
|
|
|
try:
|
|
|
|
import dotenv # noqa: F401
|
|
|
|
except ImportError:
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
need_dotenv = pytest.mark.skipif(
|
|
|
|
dotenv_not_available(), reason="dotenv is not installed"
|
|
|
|
)
|
2017-07-15 13:37:53 +08:00
|
|
|
|
|
|
|
|
|
|
|
@need_dotenv
|
|
|
|
def test_load_dotenv(monkeypatch):
|
|
|
|
# can't use monkeypatch.delitem since the keys don't exist yet
|
2021-03-10 21:40:29 +08:00
|
|
|
for item in ("FOO", "BAR", "SPAM", "HAM"):
|
2017-07-15 13:37:53 +08:00
|
|
|
monkeypatch._setitem.append((os.environ, item, notset))
|
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
monkeypatch.setenv("EGGS", "3")
|
2020-04-07 21:46:53 +08:00
|
|
|
monkeypatch.chdir(test_path)
|
2018-10-11 19:22:15 +08:00
|
|
|
assert load_dotenv()
|
2021-05-17 10:34:32 +08:00
|
|
|
assert Path.cwd() == test_path
|
2017-07-15 13:37:53 +08:00
|
|
|
# .flaskenv doesn't overwrite .env
|
2019-05-07 03:39:41 +08:00
|
|
|
assert os.environ["FOO"] == "env"
|
2017-07-15 13:37:53 +08:00
|
|
|
# set only in .flaskenv
|
2019-05-07 03:39:41 +08:00
|
|
|
assert os.environ["BAR"] == "bar"
|
2017-07-15 13:37:53 +08:00
|
|
|
# set only in .env
|
2019-05-07 03:39:41 +08:00
|
|
|
assert os.environ["SPAM"] == "1"
|
2017-07-15 13:37:53 +08:00
|
|
|
# set manually, files don't overwrite
|
2019-05-07 03:39:41 +08:00
|
|
|
assert os.environ["EGGS"] == "3"
|
2021-03-10 21:40:29 +08:00
|
|
|
# test env file encoding
|
|
|
|
assert os.environ["HAM"] == "火腿"
|
2018-10-11 19:22:15 +08:00
|
|
|
# Non existent file should not load
|
2019-05-31 18:57:28 +08:00
|
|
|
assert not load_dotenv("non-existent-file")
|
2018-10-11 19:22:15 +08:00
|
|
|
|
2017-07-15 13:37:53 +08:00
|
|
|
|
|
|
|
@need_dotenv
|
|
|
|
def test_dotenv_path(monkeypatch):
|
2019-05-07 03:39:41 +08:00
|
|
|
for item in ("FOO", "BAR", "EGGS"):
|
2017-07-15 13:37:53 +08:00
|
|
|
monkeypatch._setitem.append((os.environ, item, notset))
|
|
|
|
|
2021-05-17 10:34:32 +08:00
|
|
|
load_dotenv(test_path / ".flaskenv")
|
|
|
|
assert Path.cwd() == cwd
|
2019-05-07 03:39:41 +08:00
|
|
|
assert "FOO" in os.environ
|
2017-07-15 13:37:53 +08:00
|
|
|
|
|
|
|
|
|
|
|
def test_dotenv_optional(monkeypatch):
|
2022-05-24 00:46:20 +08:00
|
|
|
monkeypatch.setitem(sys.modules, "dotenv", None)
|
2017-07-15 13:37:53 +08:00
|
|
|
monkeypatch.chdir(test_path)
|
|
|
|
load_dotenv()
|
2019-05-07 03:39:41 +08:00
|
|
|
assert "FOO" not in os.environ
|
2018-01-23 04:16:37 +08:00
|
|
|
|
|
|
|
|
2018-04-28 23:07:53 +08:00
|
|
|
@need_dotenv
|
|
|
|
def test_disable_dotenv_from_env(monkeypatch, runner):
|
|
|
|
monkeypatch.chdir(test_path)
|
2019-05-07 03:39:41 +08:00
|
|
|
monkeypatch.setitem(os.environ, "FLASK_SKIP_DOTENV", "1")
|
2018-04-28 23:07:53 +08:00
|
|
|
runner.invoke(FlaskGroup())
|
2019-05-07 03:39:41 +08:00
|
|
|
assert "FOO" not in os.environ
|
2018-04-28 23:07:53 +08:00
|
|
|
|
|
|
|
|
2018-01-23 04:16:37 +08:00
|
|
|
def test_run_cert_path():
|
|
|
|
# no key
|
|
|
|
with pytest.raises(click.BadParameter):
|
2019-05-07 03:39:41 +08:00
|
|
|
run_command.make_context("run", ["--cert", __file__])
|
2018-01-23 04:16:37 +08:00
|
|
|
|
|
|
|
# no cert
|
|
|
|
with pytest.raises(click.BadParameter):
|
2019-05-07 03:39:41 +08:00
|
|
|
run_command.make_context("run", ["--key", __file__])
|
2018-01-23 04:16:37 +08:00
|
|
|
|
2022-04-18 20:52:01 +08:00
|
|
|
# cert specified first
|
2019-05-07 03:39:41 +08:00
|
|
|
ctx = run_command.make_context("run", ["--cert", __file__, "--key", __file__])
|
|
|
|
assert ctx.params["cert"] == (__file__, __file__)
|
2018-01-23 04:16:37 +08:00
|
|
|
|
2022-04-18 20:52:01 +08:00
|
|
|
# key specified first
|
|
|
|
ctx = run_command.make_context("run", ["--key", __file__, "--cert", __file__])
|
|
|
|
assert ctx.params["cert"] == (__file__, __file__)
|
|
|
|
|
2018-01-23 04:16:37 +08:00
|
|
|
|
|
|
|
def test_run_cert_adhoc(monkeypatch):
|
2020-02-10 08:01:23 +08:00
|
|
|
monkeypatch.setitem(sys.modules, "cryptography", None)
|
2018-01-23 04:16:37 +08:00
|
|
|
|
2020-02-10 08:01:23 +08:00
|
|
|
# cryptography not installed
|
2018-01-23 04:16:37 +08:00
|
|
|
with pytest.raises(click.BadParameter):
|
2019-05-07 03:39:41 +08:00
|
|
|
run_command.make_context("run", ["--cert", "adhoc"])
|
2018-01-23 04:16:37 +08:00
|
|
|
|
2020-02-10 08:01:23 +08:00
|
|
|
# cryptography installed
|
|
|
|
monkeypatch.setitem(sys.modules, "cryptography", types.ModuleType("cryptography"))
|
2019-05-07 03:39:41 +08:00
|
|
|
ctx = run_command.make_context("run", ["--cert", "adhoc"])
|
|
|
|
assert ctx.params["cert"] == "adhoc"
|
2018-01-23 04:16:37 +08:00
|
|
|
|
|
|
|
# no key with adhoc
|
|
|
|
with pytest.raises(click.BadParameter):
|
2019-05-07 03:39:41 +08:00
|
|
|
run_command.make_context("run", ["--cert", "adhoc", "--key", __file__])
|
2018-01-23 04:16:37 +08:00
|
|
|
|
|
|
|
|
|
|
|
def test_run_cert_import(monkeypatch):
|
2019-05-07 03:39:41 +08:00
|
|
|
monkeypatch.setitem(sys.modules, "not_here", None)
|
2018-01-23 04:16:37 +08:00
|
|
|
|
|
|
|
# ImportError
|
|
|
|
with pytest.raises(click.BadParameter):
|
2019-05-07 03:39:41 +08:00
|
|
|
run_command.make_context("run", ["--cert", "not_here"])
|
2018-01-23 04:16:37 +08:00
|
|
|
|
2020-04-05 00:25:54 +08:00
|
|
|
with pytest.raises(click.BadParameter):
|
|
|
|
run_command.make_context("run", ["--cert", "flask"])
|
2018-01-23 04:16:37 +08:00
|
|
|
|
|
|
|
# SSLContext
|
2020-04-05 00:25:54 +08:00
|
|
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
2018-01-23 04:16:37 +08:00
|
|
|
|
2019-05-07 03:39:41 +08:00
|
|
|
monkeypatch.setitem(sys.modules, "ssl_context", ssl_context)
|
|
|
|
ctx = run_command.make_context("run", ["--cert", "ssl_context"])
|
|
|
|
assert ctx.params["cert"] is ssl_context
|
2018-01-23 04:16:37 +08:00
|
|
|
|
|
|
|
# no --key with SSLContext
|
|
|
|
with pytest.raises(click.BadParameter):
|
2019-05-07 03:39:41 +08:00
|
|
|
run_command.make_context("run", ["--cert", "ssl_context", "--key", __file__])
|
2018-05-15 10:05:54 +08:00
|
|
|
|
|
|
|
|
2019-05-31 23:45:38 +08:00
|
|
|
def test_run_cert_no_ssl(monkeypatch):
|
2022-05-24 00:46:20 +08:00
|
|
|
monkeypatch.setitem(sys.modules, "ssl", None)
|
|
|
|
|
2019-05-31 23:45:38 +08:00
|
|
|
with pytest.raises(click.BadParameter):
|
|
|
|
run_command.make_context("run", ["--cert", "not_here"])
|
|
|
|
|
|
|
|
|
2018-05-15 10:05:54 +08:00
|
|
|
def test_cli_blueprints(app):
|
|
|
|
"""Test blueprint commands register correctly to the application"""
|
|
|
|
custom = Blueprint("custom", __name__, cli_group="customized")
|
|
|
|
nested = Blueprint("nested", __name__)
|
|
|
|
merged = Blueprint("merged", __name__, cli_group=None)
|
|
|
|
late = Blueprint("late", __name__)
|
|
|
|
|
|
|
|
@custom.cli.command("custom")
|
|
|
|
def custom_command():
|
|
|
|
click.echo("custom_result")
|
|
|
|
|
|
|
|
@nested.cli.command("nested")
|
|
|
|
def nested_command():
|
|
|
|
click.echo("nested_result")
|
|
|
|
|
|
|
|
@merged.cli.command("merged")
|
|
|
|
def merged_command():
|
|
|
|
click.echo("merged_result")
|
|
|
|
|
|
|
|
@late.cli.command("late")
|
|
|
|
def late_command():
|
|
|
|
click.echo("late_result")
|
|
|
|
|
|
|
|
app.register_blueprint(custom)
|
|
|
|
app.register_blueprint(nested)
|
|
|
|
app.register_blueprint(merged)
|
|
|
|
app.register_blueprint(late, cli_group="late_registration")
|
|
|
|
|
|
|
|
app_runner = app.test_cli_runner()
|
|
|
|
|
|
|
|
result = app_runner.invoke(args=["customized", "custom"])
|
|
|
|
assert "custom_result" in result.output
|
|
|
|
|
|
|
|
result = app_runner.invoke(args=["nested", "nested"])
|
|
|
|
assert "nested_result" in result.output
|
|
|
|
|
|
|
|
result = app_runner.invoke(args=["merged"])
|
|
|
|
assert "merged_result" in result.output
|
|
|
|
|
|
|
|
result = app_runner.invoke(args=["late_registration", "late"])
|
|
|
|
assert "late_result" in result.output
|
2019-05-31 23:48:32 +08:00
|
|
|
|
|
|
|
|
|
|
|
def test_cli_empty(app):
|
|
|
|
"""If a Blueprint's CLI group is empty, do not register it."""
|
|
|
|
bp = Blueprint("blue", __name__, cli_group="blue")
|
|
|
|
app.register_blueprint(bp)
|
|
|
|
|
|
|
|
result = app.test_cli_runner().invoke(args=["blue", "--help"])
|
2020-04-05 02:39:03 +08:00
|
|
|
assert result.exit_code == 2, f"Unexpected success:\n\n{result.output}"
|