I just want the tutorial
This commit is contained in:
parent
2fec0b206c
commit
127440c727
235 changed files with 46 additions and 33059 deletions
|
|
@ -1,160 +1,62 @@
|
|||
import os
|
||||
import pkgutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
from _pytest import monkeypatch
|
||||
|
||||
from flask import Flask
|
||||
from flask.globals import request_ctx
|
||||
from flaskr import create_app
|
||||
from flaskr.db import get_db
|
||||
from flaskr.db import init_db
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def _standard_os_environ():
|
||||
"""Set up ``os.environ`` at the start of the test session to have
|
||||
standard values. Returns a list of operations that is used by
|
||||
:func:`._reset_os_environ` after each test.
|
||||
"""
|
||||
mp = monkeypatch.MonkeyPatch()
|
||||
out = (
|
||||
(os.environ, "FLASK_ENV_FILE", monkeypatch.notset),
|
||||
(os.environ, "FLASK_APP", monkeypatch.notset),
|
||||
(os.environ, "FLASK_DEBUG", monkeypatch.notset),
|
||||
(os.environ, "FLASK_RUN_FROM_CLI", monkeypatch.notset),
|
||||
(os.environ, "WERKZEUG_RUN_MAIN", monkeypatch.notset),
|
||||
)
|
||||
|
||||
for _, key, value in out:
|
||||
if value is monkeypatch.notset:
|
||||
mp.delenv(key, False)
|
||||
else:
|
||||
mp.setenv(key, value)
|
||||
|
||||
yield out
|
||||
mp.undo()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_os_environ(monkeypatch, _standard_os_environ):
|
||||
"""Reset ``os.environ`` to the standard environ after each test,
|
||||
in case a test changed something without cleaning up.
|
||||
"""
|
||||
monkeypatch._setitem.extend(_standard_os_environ)
|
||||
# read in SQL for populating test data
|
||||
with open(os.path.join(os.path.dirname(__file__), "data.sql"), "rb") as f:
|
||||
_data_sql = f.read().decode("utf8")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = Flask("flask_test", root_path=os.path.dirname(__file__))
|
||||
app.config.update(
|
||||
TESTING=True,
|
||||
SECRET_KEY="test key",
|
||||
)
|
||||
return app
|
||||
"""Create and configure a new app instance for each test."""
|
||||
# create a temporary file to isolate the database for each test
|
||||
db_fd, db_path = tempfile.mkstemp()
|
||||
# create the app with common test config
|
||||
app = create_app({"TESTING": True, "DATABASE": db_path})
|
||||
|
||||
# create the database and load test data
|
||||
with app.app_context():
|
||||
init_db()
|
||||
get_db().executescript(_data_sql)
|
||||
|
||||
@pytest.fixture
|
||||
def app_ctx(app):
|
||||
with app.app_context() as ctx:
|
||||
yield ctx
|
||||
yield app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def req_ctx(app):
|
||||
with app.test_request_context() as ctx:
|
||||
yield ctx
|
||||
# close and remove the temporary database
|
||||
os.close(db_fd)
|
||||
os.unlink(db_path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(app):
|
||||
"""A test client for the app."""
|
||||
return app.test_client()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_apps(monkeypatch):
|
||||
monkeypatch.syspath_prepend(os.path.join(os.path.dirname(__file__), "test_apps"))
|
||||
original_modules = set(sys.modules.keys())
|
||||
|
||||
yield
|
||||
|
||||
# Remove any imports cached during the test. Otherwise "import app"
|
||||
# will work in the next test even though it's no longer on the path.
|
||||
for key in sys.modules.keys() - original_modules:
|
||||
sys.modules.pop(key)
|
||||
def runner(app):
|
||||
"""A test runner for the app's Click commands."""
|
||||
return app.test_cli_runner()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def leak_detector():
|
||||
yield
|
||||
class AuthActions:
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
# make sure we're not leaking a request context since we are
|
||||
# testing flask internally in debug mode in a few cases
|
||||
leaks = []
|
||||
while request_ctx:
|
||||
leaks.append(request_ctx._get_current_object())
|
||||
request_ctx.pop()
|
||||
def login(self, username="test", password="test"):
|
||||
return self._client.post(
|
||||
"/auth/login", data={"username": username, "password": password}
|
||||
)
|
||||
|
||||
assert leaks == []
|
||||
|
||||
|
||||
@pytest.fixture(params=(True, False))
|
||||
def limit_loader(request, monkeypatch):
|
||||
"""Patch pkgutil.get_loader to give loader without get_filename or archive.
|
||||
|
||||
This provides for tests where a system has custom loaders, e.g. Google App
|
||||
Engine's HardenedModulesHook, which have neither the `get_filename` method
|
||||
nor the `archive` attribute.
|
||||
|
||||
This fixture will run the testcase twice, once with and once without the
|
||||
limitation/mock.
|
||||
"""
|
||||
if not request.param:
|
||||
return
|
||||
|
||||
class LimitedLoader:
|
||||
def __init__(self, loader):
|
||||
self.loader = loader
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name in {"archive", "get_filename"}:
|
||||
raise AttributeError(f"Mocking a loader which does not have {name!r}.")
|
||||
return getattr(self.loader, name)
|
||||
|
||||
old_get_loader = pkgutil.get_loader
|
||||
|
||||
def get_loader(*args, **kwargs):
|
||||
return LimitedLoader(old_get_loader(*args, **kwargs))
|
||||
|
||||
monkeypatch.setattr(pkgutil, "get_loader", get_loader)
|
||||
def logout(self):
|
||||
return self._client.get("/auth/logout")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def modules_tmp_path(tmp_path, monkeypatch):
|
||||
"""A temporary directory added to sys.path."""
|
||||
rv = tmp_path / "modules_tmp"
|
||||
rv.mkdir()
|
||||
monkeypatch.syspath_prepend(os.fspath(rv))
|
||||
return rv
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def modules_tmp_path_prefix(modules_tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(sys, "prefix", os.fspath(modules_tmp_path))
|
||||
return modules_tmp_path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def site_packages(modules_tmp_path, monkeypatch):
|
||||
"""Create a fake site-packages."""
|
||||
py_dir = f"python{sys.version_info.major}.{sys.version_info.minor}"
|
||||
rv = modules_tmp_path / "lib" / py_dir / "site-packages"
|
||||
rv.mkdir(parents=True)
|
||||
monkeypatch.syspath_prepend(os.fspath(rv))
|
||||
return rv
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def purge_module(request):
|
||||
def inner(name):
|
||||
request.addfinalizer(lambda: sys.modules.pop(name, None))
|
||||
|
||||
return inner
|
||||
def auth(client):
|
||||
return AuthActions(client)
|
||||
|
|
|
|||
8
tests/data.sql
Normal file
8
tests/data.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
INSERT INTO user (username, password)
|
||||
VALUES
|
||||
('test', 'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'),
|
||||
('other', 'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79');
|
||||
|
||||
INSERT INTO post (title, body, author_id, created)
|
||||
VALUES
|
||||
('test title', 'test' || x'0a' || 'body', 1, '2018-01-01 00:00:00');
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"TEST_KEY": "foo",
|
||||
"SECRET_KEY": "config"
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
TEST_KEY="foo"
|
||||
SECRET_KEY="config"
|
||||
|
|
@ -1 +0,0 @@
|
|||
<h1>Hello World!</h1>
|
||||
|
|
@ -1 +0,0 @@
|
|||
{% macro hello(name) %}Hello {{ name }}!{% endmacro %}
|
||||
|
|
@ -1 +0,0 @@
|
|||
<p>{{ value }}|{{ injected_value }}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{{ text }}
|
||||
{{ html }}
|
||||
{% autoescape false %}{{ text }}
|
||||
{{ html }}{% endautoescape %}
|
||||
{% autoescape true %}{{ text }}
|
||||
{{ html }}{% endautoescape %}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{{ foo}} Mail
|
||||
|
|
@ -1 +0,0 @@
|
|||
I'm nested
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{{ text }}
|
||||
{{ html }}
|
||||
{% autoescape false %}{{ text }}
|
||||
{{ html }}{% endautoescape %}
|
||||
{% autoescape true %}{{ text }}
|
||||
{{ html }}{% endautoescape %}
|
||||
{{ text }}
|
||||
{{ html }}
|
||||
|
|
@ -1 +0,0 @@
|
|||
<h1>{{ whiskey }}</h1>
|
||||
|
|
@ -1 +0,0 @@
|
|||
{{ value|super_reverse }}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{% if value is boolean %}
|
||||
Success!
|
||||
{% endif %}
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
import pytest
|
||||
|
||||
import flask
|
||||
from flask.globals import app_ctx
|
||||
from flask.globals import request_ctx
|
||||
|
||||
|
||||
def test_basic_url_generation(app):
|
||||
app.config["SERVER_NAME"] = "localhost"
|
||||
app.config["PREFERRED_URL_SCHEME"] = "https"
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
pass
|
||||
|
||||
with app.app_context():
|
||||
rv = flask.url_for("index")
|
||||
assert rv == "https://localhost/"
|
||||
|
||||
|
||||
def test_url_generation_requires_server_name(app):
|
||||
with app.app_context():
|
||||
with pytest.raises(RuntimeError):
|
||||
flask.url_for("index")
|
||||
|
||||
|
||||
def test_url_generation_without_context_fails():
|
||||
with pytest.raises(RuntimeError):
|
||||
flask.url_for("index")
|
||||
|
||||
|
||||
def test_request_context_means_app_context(app):
|
||||
with app.test_request_context():
|
||||
assert flask.current_app._get_current_object() is app
|
||||
assert not flask.current_app
|
||||
|
||||
|
||||
def test_app_context_provides_current_app(app):
|
||||
with app.app_context():
|
||||
assert flask.current_app._get_current_object() is app
|
||||
assert not flask.current_app
|
||||
|
||||
|
||||
def test_app_tearing_down(app):
|
||||
cleanup_stuff = []
|
||||
|
||||
@app.teardown_appcontext
|
||||
def cleanup(exception):
|
||||
cleanup_stuff.append(exception)
|
||||
|
||||
with app.app_context():
|
||||
pass
|
||||
|
||||
assert cleanup_stuff == [None]
|
||||
|
||||
|
||||
def test_app_tearing_down_with_previous_exception(app):
|
||||
cleanup_stuff = []
|
||||
|
||||
@app.teardown_appcontext
|
||||
def cleanup(exception):
|
||||
cleanup_stuff.append(exception)
|
||||
|
||||
try:
|
||||
raise Exception("dummy")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
with app.app_context():
|
||||
pass
|
||||
|
||||
assert cleanup_stuff == [None]
|
||||
|
||||
|
||||
def test_app_tearing_down_with_handled_exception_by_except_block(app):
|
||||
cleanup_stuff = []
|
||||
|
||||
@app.teardown_appcontext
|
||||
def cleanup(exception):
|
||||
cleanup_stuff.append(exception)
|
||||
|
||||
with app.app_context():
|
||||
try:
|
||||
raise Exception("dummy")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
assert cleanup_stuff == [None]
|
||||
|
||||
|
||||
def test_app_tearing_down_with_handled_exception_by_app_handler(app, client):
|
||||
app.config["PROPAGATE_EXCEPTIONS"] = True
|
||||
cleanup_stuff = []
|
||||
|
||||
@app.teardown_appcontext
|
||||
def cleanup(exception):
|
||||
cleanup_stuff.append(exception)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
raise Exception("dummy")
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def handler(f):
|
||||
return flask.jsonify(str(f))
|
||||
|
||||
with app.app_context():
|
||||
client.get("/")
|
||||
|
||||
assert cleanup_stuff == [None]
|
||||
|
||||
|
||||
def test_app_tearing_down_with_unhandled_exception(app, client):
|
||||
app.config["PROPAGATE_EXCEPTIONS"] = True
|
||||
cleanup_stuff = []
|
||||
|
||||
@app.teardown_appcontext
|
||||
def cleanup(exception):
|
||||
cleanup_stuff.append(exception)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
raise ValueError("dummy")
|
||||
|
||||
with pytest.raises(ValueError, match="dummy"):
|
||||
with app.app_context():
|
||||
client.get("/")
|
||||
|
||||
assert len(cleanup_stuff) == 1
|
||||
assert isinstance(cleanup_stuff[0], ValueError)
|
||||
assert str(cleanup_stuff[0]) == "dummy"
|
||||
|
||||
|
||||
def test_app_ctx_globals_methods(app, app_ctx):
|
||||
# get
|
||||
assert flask.g.get("foo") is None
|
||||
assert flask.g.get("foo", "bar") == "bar"
|
||||
# __contains__
|
||||
assert "foo" not in flask.g
|
||||
flask.g.foo = "bar"
|
||||
assert "foo" in flask.g
|
||||
# setdefault
|
||||
flask.g.setdefault("bar", "the cake is a lie")
|
||||
flask.g.setdefault("bar", "hello world")
|
||||
assert flask.g.bar == "the cake is a lie"
|
||||
# pop
|
||||
assert flask.g.pop("bar") == "the cake is a lie"
|
||||
with pytest.raises(KeyError):
|
||||
flask.g.pop("bar")
|
||||
assert flask.g.pop("bar", "more cake") == "more cake"
|
||||
# __iter__
|
||||
assert list(flask.g) == ["foo"]
|
||||
# __repr__
|
||||
assert repr(flask.g) == "<flask.g of 'flask_test'>"
|
||||
|
||||
|
||||
def test_custom_app_ctx_globals_class(app):
|
||||
class CustomRequestGlobals:
|
||||
def __init__(self):
|
||||
self.spam = "eggs"
|
||||
|
||||
app.app_ctx_globals_class = CustomRequestGlobals
|
||||
with app.app_context():
|
||||
assert flask.render_template_string("{{ g.spam }}") == "eggs"
|
||||
|
||||
|
||||
def test_context_refcounts(app, client):
|
||||
called = []
|
||||
|
||||
@app.teardown_request
|
||||
def teardown_req(error=None):
|
||||
called.append("request")
|
||||
|
||||
@app.teardown_appcontext
|
||||
def teardown_app(error=None):
|
||||
called.append("app")
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
with app_ctx:
|
||||
with request_ctx:
|
||||
pass
|
||||
|
||||
assert flask.request.environ["werkzeug.request"] is not None
|
||||
return ""
|
||||
|
||||
res = client.get("/")
|
||||
assert res.status_code == 200
|
||||
assert res.data == b""
|
||||
assert called == ["request", "app"]
|
||||
|
||||
|
||||
def test_clean_pop(app):
|
||||
app.testing = False
|
||||
called = []
|
||||
|
||||
@app.teardown_request
|
||||
def teardown_req(error=None):
|
||||
raise ZeroDivisionError
|
||||
|
||||
@app.teardown_appcontext
|
||||
def teardown_app(error=None):
|
||||
called.append("TEARDOWN")
|
||||
|
||||
with app.app_context():
|
||||
called.append(flask.current_app.name)
|
||||
|
||||
assert called == ["flask_test", "TEARDOWN"]
|
||||
assert not flask.current_app
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
FOO=env
|
||||
SPAM=1
|
||||
EGGS=2
|
||||
HAM=火腿
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
FOO=flaskenv
|
||||
BAR=bar
|
||||
EGGS=0
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["DEBUG"] = True
|
||||
from blueprintapp.apps.admin import admin # noqa: E402
|
||||
from blueprintapp.apps.frontend import frontend # noqa: E402
|
||||
|
||||
app.register_blueprint(admin)
|
||||
app.register_blueprint(frontend)
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
from flask import Blueprint
|
||||
from flask import render_template
|
||||
|
||||
admin = Blueprint(
|
||||
"admin",
|
||||
__name__,
|
||||
url_prefix="/admin",
|
||||
template_folder="templates",
|
||||
static_folder="static",
|
||||
)
|
||||
|
||||
|
||||
@admin.route("/")
|
||||
def index():
|
||||
return render_template("admin/index.html")
|
||||
|
||||
|
||||
@admin.route("/index2")
|
||||
def index2():
|
||||
return render_template("./admin/index.html")
|
||||
|
|
@ -1 +0,0 @@
|
|||
/* nested file */
|
||||
|
|
@ -1 +0,0 @@
|
|||
Admin File
|
||||
|
|
@ -1 +0,0 @@
|
|||
Hello from the Admin
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
from flask import Blueprint
|
||||
from flask import render_template
|
||||
|
||||
frontend = Blueprint("frontend", __name__, template_folder="templates")
|
||||
|
||||
|
||||
@frontend.route("/")
|
||||
def index():
|
||||
return render_template("frontend/index.html")
|
||||
|
||||
|
||||
@frontend.route("/missing")
|
||||
def missing_template():
|
||||
return render_template("missing_template.html")
|
||||
|
|
@ -1 +0,0 @@
|
|||
Hello from the Frontend
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
from flask import Flask
|
||||
|
||||
testapp = Flask("testapp")
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
from flask import Flask
|
||||
|
||||
|
||||
def create_app():
|
||||
return Flask("app")
|
||||
|
||||
|
||||
def create_app2(foo, bar):
|
||||
return Flask("_".join(["app2", foo, bar]))
|
||||
|
||||
|
||||
def no_app():
|
||||
pass
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
from flask import Flask
|
||||
|
||||
raise ImportError()
|
||||
|
||||
testapp = Flask("testapp")
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
from flask import Flask
|
||||
|
||||
application = Flask(__name__)
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
|
@ -1 +0,0 @@
|
|||
So long, and thanks for all the fish.
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
from flask import Flask
|
||||
|
||||
app1 = Flask("app1")
|
||||
app2 = Flask("app2")
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def hello():
|
||||
return "Hello World!"
|
||||
|
|
@ -1 +0,0 @@
|
|||
from hello import app # noqa: F401
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
from flask import Module
|
||||
|
||||
mod = Module(__name__, "foo", subdomain="foo")
|
||||
|
|
@ -1 +0,0 @@
|
|||
Hello Subdomain
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from flask import Blueprint
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
from flask.views import MethodView
|
||||
from flask.views import View
|
||||
|
||||
pytest.importorskip("asgiref")
|
||||
|
||||
|
||||
class AppError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BlueprintError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AsyncView(View):
|
||||
methods = ["GET", "POST"]
|
||||
|
||||
async def dispatch_request(self):
|
||||
await asyncio.sleep(0)
|
||||
return request.method
|
||||
|
||||
|
||||
class AsyncMethodView(MethodView):
|
||||
async def get(self):
|
||||
await asyncio.sleep(0)
|
||||
return "GET"
|
||||
|
||||
async def post(self):
|
||||
await asyncio.sleep(0)
|
||||
return "POST"
|
||||
|
||||
|
||||
@pytest.fixture(name="async_app")
|
||||
def _async_app():
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/", methods=["GET", "POST"])
|
||||
@app.route("/home", methods=["GET", "POST"])
|
||||
async def index():
|
||||
await asyncio.sleep(0)
|
||||
return request.method
|
||||
|
||||
@app.errorhandler(AppError)
|
||||
async def handle(_):
|
||||
return "", 412
|
||||
|
||||
@app.route("/error")
|
||||
async def error():
|
||||
raise AppError()
|
||||
|
||||
blueprint = Blueprint("bp", __name__)
|
||||
|
||||
@blueprint.route("/", methods=["GET", "POST"])
|
||||
async def bp_index():
|
||||
await asyncio.sleep(0)
|
||||
return request.method
|
||||
|
||||
@blueprint.errorhandler(BlueprintError)
|
||||
async def bp_handle(_):
|
||||
return "", 412
|
||||
|
||||
@blueprint.route("/error")
|
||||
async def bp_error():
|
||||
raise BlueprintError()
|
||||
|
||||
app.register_blueprint(blueprint, url_prefix="/bp")
|
||||
|
||||
app.add_url_rule("/view", view_func=AsyncView.as_view("view"))
|
||||
app.add_url_rule("/methodview", view_func=AsyncMethodView.as_view("methodview"))
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", ["/", "/home", "/bp/", "/view", "/methodview"])
|
||||
def test_async_route(path, async_app):
|
||||
test_client = async_app.test_client()
|
||||
response = test_client.get(path)
|
||||
assert b"GET" in response.get_data()
|
||||
response = test_client.post(path)
|
||||
assert b"POST" in response.get_data()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", ["/error", "/bp/error"])
|
||||
def test_async_error_handler(path, async_app):
|
||||
test_client = async_app.test_client()
|
||||
response = test_client.get(path)
|
||||
assert response.status_code == 412
|
||||
|
||||
|
||||
def test_async_before_after_request():
|
||||
app_before_called = False
|
||||
app_after_called = False
|
||||
bp_before_called = False
|
||||
bp_after_called = False
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return ""
|
||||
|
||||
@app.before_request
|
||||
async def before():
|
||||
nonlocal app_before_called
|
||||
app_before_called = True
|
||||
|
||||
@app.after_request
|
||||
async def after(response):
|
||||
nonlocal app_after_called
|
||||
app_after_called = True
|
||||
return response
|
||||
|
||||
blueprint = Blueprint("bp", __name__)
|
||||
|
||||
@blueprint.route("/")
|
||||
def bp_index():
|
||||
return ""
|
||||
|
||||
@blueprint.before_request
|
||||
async def bp_before():
|
||||
nonlocal bp_before_called
|
||||
bp_before_called = True
|
||||
|
||||
@blueprint.after_request
|
||||
async def bp_after(response):
|
||||
nonlocal bp_after_called
|
||||
bp_after_called = True
|
||||
return response
|
||||
|
||||
app.register_blueprint(blueprint, url_prefix="/bp")
|
||||
|
||||
test_client = app.test_client()
|
||||
test_client.get("/")
|
||||
assert app_before_called
|
||||
assert app_after_called
|
||||
test_client.get("/bp/")
|
||||
assert bp_before_called
|
||||
assert bp_after_called
|
||||
69
tests/test_auth.py
Normal file
69
tests/test_auth.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import pytest
|
||||
from flask import g
|
||||
from flask import session
|
||||
|
||||
from flaskr.db import get_db
|
||||
|
||||
|
||||
def test_register(client, app):
|
||||
# test that viewing the page renders without template errors
|
||||
assert client.get("/auth/register").status_code == 200
|
||||
|
||||
# test that successful registration redirects to the login page
|
||||
response = client.post("/auth/register", data={"username": "a", "password": "a"})
|
||||
assert response.headers["Location"] == "/auth/login"
|
||||
|
||||
# test that the user was inserted into the database
|
||||
with app.app_context():
|
||||
assert (
|
||||
get_db().execute("SELECT * FROM user WHERE username = 'a'").fetchone()
|
||||
is not None
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("username", "password", "message"),
|
||||
(
|
||||
("", "", b"Username is required."),
|
||||
("a", "", b"Password is required."),
|
||||
("test", "test", b"already registered"),
|
||||
),
|
||||
)
|
||||
def test_register_validate_input(client, username, password, message):
|
||||
response = client.post(
|
||||
"/auth/register", data={"username": username, "password": password}
|
||||
)
|
||||
assert message in response.data
|
||||
|
||||
|
||||
def test_login(client, auth):
|
||||
# test that viewing the page renders without template errors
|
||||
assert client.get("/auth/login").status_code == 200
|
||||
|
||||
# test that successful login redirects to the index page
|
||||
response = auth.login()
|
||||
assert response.headers["Location"] == "/"
|
||||
|
||||
# login request set the user_id in the session
|
||||
# check that the user is loaded from the session
|
||||
with client:
|
||||
client.get("/")
|
||||
assert session["user_id"] == 1
|
||||
assert g.user["username"] == "test"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("username", "password", "message"),
|
||||
(("a", "test", b"Incorrect username."), ("test", "a", b"Incorrect password.")),
|
||||
)
|
||||
def test_login_validate_input(auth, username, password, message):
|
||||
response = auth.login(username, password)
|
||||
assert message in response.data
|
||||
|
||||
|
||||
def test_logout(client, auth):
|
||||
auth.login()
|
||||
|
||||
with client:
|
||||
auth.logout()
|
||||
assert "user_id" not in session
|
||||
1890
tests/test_basic.py
1890
tests/test_basic.py
File diff suppressed because it is too large
Load diff
83
tests/test_blog.py
Normal file
83
tests/test_blog.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import pytest
|
||||
|
||||
from flaskr.db import get_db
|
||||
|
||||
|
||||
def test_index(client, auth):
|
||||
response = client.get("/")
|
||||
assert b"Log In" in response.data
|
||||
assert b"Register" in response.data
|
||||
|
||||
auth.login()
|
||||
response = client.get("/")
|
||||
assert b"test title" in response.data
|
||||
assert b"by test on 2018-01-01" in response.data
|
||||
assert b"test\nbody" in response.data
|
||||
assert b'href="/1/update"' in response.data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", ("/create", "/1/update", "/1/delete"))
|
||||
def test_login_required(client, path):
|
||||
response = client.post(path)
|
||||
assert response.headers["Location"] == "/auth/login"
|
||||
|
||||
|
||||
def test_author_required(app, client, auth):
|
||||
# change the post author to another user
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
db.execute("UPDATE post SET author_id = 2 WHERE id = 1")
|
||||
db.commit()
|
||||
|
||||
auth.login()
|
||||
# current user can't modify other user's post
|
||||
assert client.post("/1/update").status_code == 403
|
||||
assert client.post("/1/delete").status_code == 403
|
||||
# current user doesn't see edit link
|
||||
assert b'href="/1/update"' not in client.get("/").data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", ("/2/update", "/2/delete"))
|
||||
def test_exists_required(client, auth, path):
|
||||
auth.login()
|
||||
assert client.post(path).status_code == 404
|
||||
|
||||
|
||||
def test_create(client, auth, app):
|
||||
auth.login()
|
||||
assert client.get("/create").status_code == 200
|
||||
client.post("/create", data={"title": "created", "body": ""})
|
||||
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
count = db.execute("SELECT COUNT(id) FROM post").fetchone()[0]
|
||||
assert count == 2
|
||||
|
||||
|
||||
def test_update(client, auth, app):
|
||||
auth.login()
|
||||
assert client.get("/1/update").status_code == 200
|
||||
client.post("/1/update", data={"title": "updated", "body": ""})
|
||||
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
post = db.execute("SELECT * FROM post WHERE id = 1").fetchone()
|
||||
assert post["title"] == "updated"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", ("/create", "/1/update"))
|
||||
def test_create_update_validate(client, auth, path):
|
||||
auth.login()
|
||||
response = client.post(path, data={"title": "", "body": ""})
|
||||
assert b"Title is required." in response.data
|
||||
|
||||
|
||||
def test_delete(client, auth, app):
|
||||
auth.login()
|
||||
response = client.post("/1/delete")
|
||||
assert response.headers["Location"] == "/"
|
||||
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
post = db.execute("SELECT * FROM post WHERE id = 1").fetchone()
|
||||
assert post is None
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,686 +0,0 @@
|
|||
# This file was part of Flask-CLI and was modified under the terms of
|
||||
# its Revised BSD License. Copyright © 2015 CERN.
|
||||
import importlib.metadata
|
||||
import os
|
||||
import platform
|
||||
import ssl
|
||||
import sys
|
||||
import types
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
import pytest
|
||||
from _pytest.monkeypatch import notset
|
||||
from click.testing import CliRunner
|
||||
|
||||
from flask import Blueprint
|
||||
from flask import current_app
|
||||
from flask import Flask
|
||||
from flask.cli import AppGroup
|
||||
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
|
||||
|
||||
cwd = Path.cwd()
|
||||
test_path = (Path(__file__) / ".." / "test_apps").resolve()
|
||||
|
||||
|
||||
@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):
|
||||
class Module:
|
||||
app = Flask("appname")
|
||||
|
||||
assert find_best_app(Module) == Module.app
|
||||
|
||||
class Module:
|
||||
application = Flask("appname")
|
||||
|
||||
assert find_best_app(Module) == Module.application
|
||||
|
||||
class Module:
|
||||
myapp = Flask("appname")
|
||||
|
||||
assert find_best_app(Module) == Module.myapp
|
||||
|
||||
class Module:
|
||||
@staticmethod
|
||||
def create_app():
|
||||
return Flask("appname")
|
||||
|
||||
app = find_best_app(Module)
|
||||
assert isinstance(app, Flask)
|
||||
assert app.name == "appname"
|
||||
|
||||
class Module:
|
||||
@staticmethod
|
||||
def create_app(**kwargs):
|
||||
return Flask("appname")
|
||||
|
||||
app = find_best_app(Module)
|
||||
assert isinstance(app, Flask)
|
||||
assert app.name == "appname"
|
||||
|
||||
class Module:
|
||||
@staticmethod
|
||||
def make_app():
|
||||
return Flask("appname")
|
||||
|
||||
app = find_best_app(Module)
|
||||
assert isinstance(app, Flask)
|
||||
assert app.name == "appname"
|
||||
|
||||
class Module:
|
||||
myapp = Flask("appname1")
|
||||
|
||||
@staticmethod
|
||||
def create_app():
|
||||
return Flask("appname2")
|
||||
|
||||
assert find_best_app(Module) == Module.myapp
|
||||
|
||||
class Module:
|
||||
myapp = Flask("appname1")
|
||||
|
||||
@staticmethod
|
||||
def create_app():
|
||||
return Flask("appname2")
|
||||
|
||||
assert find_best_app(Module) == Module.myapp
|
||||
|
||||
class Module:
|
||||
pass
|
||||
|
||||
pytest.raises(NoAppException, find_best_app, Module)
|
||||
|
||||
class Module:
|
||||
myapp1 = Flask("appname1")
|
||||
myapp2 = Flask("appname2")
|
||||
|
||||
pytest.raises(NoAppException, find_best_app, Module)
|
||||
|
||||
class Module:
|
||||
@staticmethod
|
||||
def create_app(foo, bar):
|
||||
return Flask("appname2")
|
||||
|
||||
pytest.raises(NoAppException, find_best_app, Module)
|
||||
|
||||
class Module:
|
||||
@staticmethod
|
||||
def create_app():
|
||||
raise TypeError("bad bad factory!")
|
||||
|
||||
pytest.raises(TypeError, find_best_app, Module)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value,path,result",
|
||||
(
|
||||
("test", cwd, "test"),
|
||||
("test.py", cwd, "test"),
|
||||
("a/test", cwd / "a", "test"),
|
||||
("test/__init__.py", cwd, "test"),
|
||||
("test/__init__", cwd, "test"),
|
||||
# nested package
|
||||
(
|
||||
test_path / "cliapp" / "inner1" / "__init__",
|
||||
test_path,
|
||||
"cliapp.inner1",
|
||||
),
|
||||
(
|
||||
test_path / "cliapp" / "inner1" / "inner2",
|
||||
test_path,
|
||||
"cliapp.inner1.inner2",
|
||||
),
|
||||
# dotted name
|
||||
("test.a.b", cwd, "test.a.b"),
|
||||
(test_path / "cliapp.app", test_path, "cliapp.app"),
|
||||
# not a Python file, will be caught during import
|
||||
(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] == str(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"),
|
||||
("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"),
|
||||
),
|
||||
)
|
||||
def test_locate_app(test_apps, iname, aname, result):
|
||||
assert locate_app(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):
|
||||
with pytest.raises(NoAppException):
|
||||
locate_app(iname, aname)
|
||||
|
||||
|
||||
def test_locate_app_suppress_raise(test_apps):
|
||||
app = locate_app("notanapp.py", None, raise_if_not_found=False)
|
||||
assert app is None
|
||||
|
||||
# only direct import error is suppressed
|
||||
with pytest.raises(NoAppException):
|
||||
locate_app("cliapp.importerrorapp", None, raise_if_not_found=False)
|
||||
|
||||
|
||||
def test_get_version(test_apps, capsys):
|
||||
class MockCtx:
|
||||
resilient_parsing = False
|
||||
color = None
|
||||
|
||||
def exit(self):
|
||||
return
|
||||
|
||||
ctx = MockCtx()
|
||||
get_version(ctx, None, "test")
|
||||
out, err = capsys.readouterr()
|
||||
assert f"Python {platform.python_version()}" in out
|
||||
assert f"Flask {importlib.metadata.version('flask')}" in out
|
||||
assert f"Werkzeug {importlib.metadata.version('werkzeug')}" in out
|
||||
|
||||
|
||||
def test_scriptinfo(test_apps, monkeypatch):
|
||||
obj = ScriptInfo(app_import_path="cliapp.app:testapp")
|
||||
app = obj.load_app()
|
||||
assert app.name == "testapp"
|
||||
assert obj.load_app() is app
|
||||
|
||||
# import app with module's absolute path
|
||||
cli_app_path = str(test_path / "cliapp" / "app.py")
|
||||
|
||||
obj = ScriptInfo(app_import_path=cli_app_path)
|
||||
app = obj.load_app()
|
||||
assert app.name == "testapp"
|
||||
assert obj.load_app() is app
|
||||
obj = ScriptInfo(app_import_path=f"{cli_app_path}:testapp")
|
||||
app = obj.load_app()
|
||||
assert app.name == "testapp"
|
||||
assert obj.load_app() is app
|
||||
|
||||
def create_app():
|
||||
return Flask("createapp")
|
||||
|
||||
obj = ScriptInfo(create_app=create_app)
|
||||
app = obj.load_app()
|
||||
assert app.name == "createapp"
|
||||
assert obj.load_app() is app
|
||||
|
||||
obj = ScriptInfo()
|
||||
pytest.raises(NoAppException, obj.load_app)
|
||||
|
||||
# import app from wsgi.py in current directory
|
||||
monkeypatch.chdir(test_path / "helloworld")
|
||||
obj = ScriptInfo()
|
||||
app = obj.load_app()
|
||||
assert app.name == "hello"
|
||||
|
||||
# import app from app.py in current directory
|
||||
monkeypatch.chdir(test_path / "cliapp")
|
||||
obj = ScriptInfo()
|
||||
app = obj.load_app()
|
||||
assert app.name == "testapp"
|
||||
|
||||
|
||||
def test_app_cli_has_app_context(app, runner):
|
||||
def _param_cb(ctx, param, value):
|
||||
# current_app should be available in parameter callbacks
|
||||
return bool(current_app)
|
||||
|
||||
@app.cli.command()
|
||||
@click.argument("value", callback=_param_cb)
|
||||
def check(value):
|
||||
app = click.get_current_context().obj.load_app()
|
||||
# the loaded app should be the same as current_app
|
||||
same_app = current_app._get_current_object() is app
|
||||
return same_app, value
|
||||
|
||||
cli = FlaskGroup(create_app=lambda: app)
|
||||
result = runner.invoke(cli, ["check", "x"], standalone_mode=False)
|
||||
assert result.return_value == (True, True)
|
||||
|
||||
|
||||
def test_with_appcontext(runner):
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
def testcmd():
|
||||
click.echo(current_app.name)
|
||||
|
||||
obj = ScriptInfo(create_app=lambda: Flask("testapp"))
|
||||
|
||||
result = runner.invoke(testcmd, obj=obj)
|
||||
assert result.exit_code == 0
|
||||
assert result.output == "testapp\n"
|
||||
|
||||
|
||||
def test_appgroup_app_context(runner):
|
||||
@click.group(cls=AppGroup)
|
||||
def cli():
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
def test():
|
||||
click.echo(current_app.name)
|
||||
|
||||
@cli.group()
|
||||
def subgroup():
|
||||
pass
|
||||
|
||||
@subgroup.command()
|
||||
def test2():
|
||||
click.echo(current_app.name)
|
||||
|
||||
obj = ScriptInfo(create_app=lambda: 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_app_context(runner):
|
||||
def create_app():
|
||||
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"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("set_debug_flag", (True, False))
|
||||
def test_flaskgroup_debug(runner, set_debug_flag):
|
||||
def create_app():
|
||||
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))
|
||||
|
||||
result = runner.invoke(cli, ["test"])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == f"{not set_debug_flag}\n"
|
||||
|
||||
|
||||
def test_flaskgroup_nested(app, runner):
|
||||
cli = click.Group("cli")
|
||||
flask_group = FlaskGroup(name="flask", create_app=lambda: app)
|
||||
cli.add_command(flask_group)
|
||||
|
||||
@flask_group.command()
|
||||
def show():
|
||||
click.echo(current_app.name)
|
||||
|
||||
result = runner.invoke(cli, ["flask", "show"])
|
||||
assert result.output == "flask_test\n"
|
||||
|
||||
|
||||
def test_no_command_echo_loading_error():
|
||||
from flask.cli import cli
|
||||
|
||||
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():
|
||||
def create_app():
|
||||
raise Exception("oh no")
|
||||
|
||||
cli = FlaskGroup(create_app=create_app)
|
||||
runner = CliRunner(mix_stderr=False)
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "Exception: oh no" in result.stderr
|
||||
assert "Usage:" in result.stdout
|
||||
|
||||
|
||||
class TestRoutes:
|
||||
@pytest.fixture
|
||||
def app(self):
|
||||
app = Flask(__name__)
|
||||
app.add_url_rule(
|
||||
"/get_post/<int:x>/<int:y>",
|
||||
methods=["GET", "POST"],
|
||||
endpoint="yyy_get_post",
|
||||
)
|
||||
app.add_url_rule("/zzz_post", methods=["POST"], endpoint="aaa_post")
|
||||
return app
|
||||
|
||||
@pytest.fixture
|
||||
def invoke(self, app, runner):
|
||||
cli = FlaskGroup(create_app=lambda: 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, app, 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,
|
||||
)
|
||||
match_order = [r.endpoint for r in app.url_map.iter_rules()]
|
||||
self.expect_order(match_order, 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
|
||||
|
||||
def test_no_routes(self, runner):
|
||||
app = Flask(__name__, static_folder=None)
|
||||
cli = FlaskGroup(create_app=lambda: app)
|
||||
result = runner.invoke(cli, ["routes"])
|
||||
assert result.exit_code == 0
|
||||
assert "No routes were registered." in result.output
|
||||
|
||||
def test_subdomain(self, runner):
|
||||
app = Flask(__name__, static_folder=None)
|
||||
app.add_url_rule("/a", subdomain="a", endpoint="a")
|
||||
app.add_url_rule("/b", subdomain="b", endpoint="b")
|
||||
cli = FlaskGroup(create_app=lambda: app)
|
||||
result = runner.invoke(cli, ["routes"])
|
||||
assert result.exit_code == 0
|
||||
assert "Subdomain" in result.output
|
||||
|
||||
def test_host(self, runner):
|
||||
app = Flask(__name__, static_folder=None, host_matching=True)
|
||||
app.add_url_rule("/a", host="a", endpoint="a")
|
||||
app.add_url_rule("/b", host="b", endpoint="b")
|
||||
cli = FlaskGroup(create_app=lambda: app)
|
||||
result = runner.invoke(cli, ["routes"])
|
||||
assert result.exit_code == 0
|
||||
assert "Host" in result.output
|
||||
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
|
||||
@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", "HAM"):
|
||||
monkeypatch._setitem.append((os.environ, item, notset))
|
||||
|
||||
monkeypatch.setenv("EGGS", "3")
|
||||
monkeypatch.chdir(test_path)
|
||||
assert load_dotenv()
|
||||
assert Path.cwd() == 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"
|
||||
# test env file encoding
|
||||
assert os.environ["HAM"] == "火腿"
|
||||
# Non existent file should not load
|
||||
assert not load_dotenv("non-existent-file")
|
||||
|
||||
|
||||
@need_dotenv
|
||||
def test_dotenv_path(monkeypatch):
|
||||
for item in ("FOO", "BAR", "EGGS"):
|
||||
monkeypatch._setitem.append((os.environ, item, notset))
|
||||
|
||||
load_dotenv(test_path / ".flaskenv")
|
||||
assert Path.cwd() == cwd
|
||||
assert "FOO" in os.environ
|
||||
|
||||
|
||||
def test_dotenv_optional(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "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__])
|
||||
|
||||
# cert specified first
|
||||
ctx = run_command.make_context("run", ["--cert", __file__, "--key", __file__])
|
||||
assert ctx.params["cert"] == (__file__, __file__)
|
||||
|
||||
# key specified first
|
||||
ctx = run_command.make_context("run", ["--key", __file__, "--cert", __file__])
|
||||
assert ctx.params["cert"] == (__file__, __file__)
|
||||
|
||||
|
||||
def test_run_cert_adhoc(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "cryptography", None)
|
||||
|
||||
# cryptography not installed
|
||||
with pytest.raises(click.BadParameter):
|
||||
run_command.make_context("run", ["--cert", "adhoc"])
|
||||
|
||||
# cryptography installed
|
||||
monkeypatch.setitem(sys.modules, "cryptography", types.ModuleType("cryptography"))
|
||||
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"])
|
||||
|
||||
with pytest.raises(click.BadParameter):
|
||||
run_command.make_context("run", ["--cert", "flask"])
|
||||
|
||||
# SSLContext
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
|
||||
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__])
|
||||
|
||||
|
||||
def test_run_cert_no_ssl(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "ssl", None)
|
||||
|
||||
with pytest.raises(click.BadParameter):
|
||||
run_command.make_context("run", ["--cert", "not_here"])
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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"])
|
||||
assert result.exit_code == 2, f"Unexpected success:\n\n{result.output}"
|
||||
|
||||
|
||||
def test_run_exclude_patterns():
|
||||
ctx = run_command.make_context("run", ["--exclude-patterns", __file__])
|
||||
assert ctx.params["exclude_patterns"] == [__file__]
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
import flask
|
||||
|
||||
# config keys used for the TestConfig
|
||||
TEST_KEY = "foo"
|
||||
SECRET_KEY = "config"
|
||||
|
||||
|
||||
def common_object_test(app):
|
||||
assert app.secret_key == "config"
|
||||
assert app.config["TEST_KEY"] == "foo"
|
||||
assert "TestConfig" not in app.config
|
||||
|
||||
|
||||
def test_config_from_pyfile():
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_pyfile(f"{__file__.rsplit('.', 1)[0]}.py")
|
||||
common_object_test(app)
|
||||
|
||||
|
||||
def test_config_from_object():
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_object(__name__)
|
||||
common_object_test(app)
|
||||
|
||||
|
||||
def test_config_from_file_json():
|
||||
app = flask.Flask(__name__)
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
app.config.from_file(os.path.join(current_dir, "static", "config.json"), json.load)
|
||||
common_object_test(app)
|
||||
|
||||
|
||||
def test_config_from_file_toml():
|
||||
tomllib = pytest.importorskip("tomllib", reason="tomllib added in 3.11")
|
||||
app = flask.Flask(__name__)
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
app.config.from_file(
|
||||
os.path.join(current_dir, "static", "config.toml"), tomllib.load, text=False
|
||||
)
|
||||
common_object_test(app)
|
||||
|
||||
|
||||
def test_from_prefixed_env(monkeypatch):
|
||||
monkeypatch.setenv("FLASK_STRING", "value")
|
||||
monkeypatch.setenv("FLASK_BOOL", "true")
|
||||
monkeypatch.setenv("FLASK_INT", "1")
|
||||
monkeypatch.setenv("FLASK_FLOAT", "1.2")
|
||||
monkeypatch.setenv("FLASK_LIST", "[1, 2]")
|
||||
monkeypatch.setenv("FLASK_DICT", '{"k": "v"}')
|
||||
monkeypatch.setenv("NOT_FLASK_OTHER", "other")
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_prefixed_env()
|
||||
|
||||
assert app.config["STRING"] == "value"
|
||||
assert app.config["BOOL"] is True
|
||||
assert app.config["INT"] == 1
|
||||
assert app.config["FLOAT"] == 1.2
|
||||
assert app.config["LIST"] == [1, 2]
|
||||
assert app.config["DICT"] == {"k": "v"}
|
||||
assert "OTHER" not in app.config
|
||||
|
||||
|
||||
def test_from_prefixed_env_custom_prefix(monkeypatch):
|
||||
monkeypatch.setenv("FLASK_A", "a")
|
||||
monkeypatch.setenv("NOT_FLASK_A", "b")
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_prefixed_env("NOT_FLASK")
|
||||
|
||||
assert app.config["A"] == "b"
|
||||
|
||||
|
||||
def test_from_prefixed_env_nested(monkeypatch):
|
||||
monkeypatch.setenv("FLASK_EXIST__ok", "other")
|
||||
monkeypatch.setenv("FLASK_EXIST__inner__ik", "2")
|
||||
monkeypatch.setenv("FLASK_EXIST__new__more", '{"k": false}')
|
||||
monkeypatch.setenv("FLASK_NEW__K", "v")
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.config["EXIST"] = {"ok": "value", "flag": True, "inner": {"ik": 1}}
|
||||
app.config.from_prefixed_env()
|
||||
|
||||
if os.name != "nt":
|
||||
assert app.config["EXIST"] == {
|
||||
"ok": "other",
|
||||
"flag": True,
|
||||
"inner": {"ik": 2},
|
||||
"new": {"more": {"k": False}},
|
||||
}
|
||||
else:
|
||||
# Windows env var keys are always uppercase.
|
||||
assert app.config["EXIST"] == {
|
||||
"ok": "value",
|
||||
"OK": "other",
|
||||
"flag": True,
|
||||
"inner": {"ik": 1},
|
||||
"INNER": {"IK": 2},
|
||||
"NEW": {"MORE": {"k": False}},
|
||||
}
|
||||
|
||||
assert app.config["NEW"] == {"K": "v"}
|
||||
|
||||
|
||||
def test_config_from_mapping():
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_mapping({"SECRET_KEY": "config", "TEST_KEY": "foo"})
|
||||
common_object_test(app)
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_mapping([("SECRET_KEY", "config"), ("TEST_KEY", "foo")])
|
||||
common_object_test(app)
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_mapping(SECRET_KEY="config", TEST_KEY="foo")
|
||||
common_object_test(app)
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_mapping(SECRET_KEY="config", TEST_KEY="foo", skip_key="skip")
|
||||
common_object_test(app)
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
with pytest.raises(TypeError):
|
||||
app.config.from_mapping({}, {})
|
||||
|
||||
|
||||
def test_config_from_class():
|
||||
class Base:
|
||||
TEST_KEY = "foo"
|
||||
|
||||
class Test(Base):
|
||||
SECRET_KEY = "config"
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_object(Test)
|
||||
common_object_test(app)
|
||||
|
||||
|
||||
def test_config_from_envvar(monkeypatch):
|
||||
monkeypatch.setattr("os.environ", {})
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
with pytest.raises(RuntimeError) as e:
|
||||
app.config.from_envvar("FOO_SETTINGS")
|
||||
|
||||
assert "'FOO_SETTINGS' is not set" in str(e.value)
|
||||
assert not app.config.from_envvar("FOO_SETTINGS", silent=True)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"os.environ", {"FOO_SETTINGS": f"{__file__.rsplit('.', 1)[0]}.py"}
|
||||
)
|
||||
assert app.config.from_envvar("FOO_SETTINGS")
|
||||
common_object_test(app)
|
||||
|
||||
|
||||
def test_config_from_envvar_missing(monkeypatch):
|
||||
monkeypatch.setattr("os.environ", {"FOO_SETTINGS": "missing.cfg"})
|
||||
app = flask.Flask(__name__)
|
||||
with pytest.raises(IOError) as e:
|
||||
app.config.from_envvar("FOO_SETTINGS")
|
||||
msg = str(e.value)
|
||||
assert msg.startswith(
|
||||
"[Errno 2] Unable to load configuration file (No such file or directory):"
|
||||
)
|
||||
assert msg.endswith("missing.cfg'")
|
||||
assert not app.config.from_envvar("FOO_SETTINGS", silent=True)
|
||||
|
||||
|
||||
def test_config_missing():
|
||||
app = flask.Flask(__name__)
|
||||
with pytest.raises(IOError) as e:
|
||||
app.config.from_pyfile("missing.cfg")
|
||||
msg = str(e.value)
|
||||
assert msg.startswith(
|
||||
"[Errno 2] Unable to load configuration file (No such file or directory):"
|
||||
)
|
||||
assert msg.endswith("missing.cfg'")
|
||||
assert not app.config.from_pyfile("missing.cfg", silent=True)
|
||||
|
||||
|
||||
def test_config_missing_file():
|
||||
app = flask.Flask(__name__)
|
||||
with pytest.raises(IOError) as e:
|
||||
app.config.from_file("missing.json", load=json.load)
|
||||
msg = str(e.value)
|
||||
assert msg.startswith(
|
||||
"[Errno 2] Unable to load configuration file (No such file or directory):"
|
||||
)
|
||||
assert msg.endswith("missing.json'")
|
||||
assert not app.config.from_file("missing.json", load=json.load, silent=True)
|
||||
|
||||
|
||||
def test_custom_config_class():
|
||||
class Config(flask.Config):
|
||||
pass
|
||||
|
||||
class Flask(flask.Flask):
|
||||
config_class = Config
|
||||
|
||||
app = Flask(__name__)
|
||||
assert isinstance(app.config, Config)
|
||||
app.config.from_object(__name__)
|
||||
common_object_test(app)
|
||||
|
||||
|
||||
def test_session_lifetime():
|
||||
app = flask.Flask(__name__)
|
||||
app.config["PERMANENT_SESSION_LIFETIME"] = 42
|
||||
assert app.permanent_session_lifetime.seconds == 42
|
||||
|
||||
|
||||
def test_get_namespace():
|
||||
app = flask.Flask(__name__)
|
||||
app.config["FOO_OPTION_1"] = "foo option 1"
|
||||
app.config["FOO_OPTION_2"] = "foo option 2"
|
||||
app.config["BAR_STUFF_1"] = "bar stuff 1"
|
||||
app.config["BAR_STUFF_2"] = "bar stuff 2"
|
||||
foo_options = app.config.get_namespace("FOO_")
|
||||
assert 2 == len(foo_options)
|
||||
assert "foo option 1" == foo_options["option_1"]
|
||||
assert "foo option 2" == foo_options["option_2"]
|
||||
bar_options = app.config.get_namespace("BAR_", lowercase=False)
|
||||
assert 2 == len(bar_options)
|
||||
assert "bar stuff 1" == bar_options["STUFF_1"]
|
||||
assert "bar stuff 2" == bar_options["STUFF_2"]
|
||||
foo_options = app.config.get_namespace("FOO_", trim_namespace=False)
|
||||
assert 2 == len(foo_options)
|
||||
assert "foo option 1" == foo_options["foo_option_1"]
|
||||
assert "foo option 2" == foo_options["foo_option_2"]
|
||||
bar_options = app.config.get_namespace(
|
||||
"BAR_", lowercase=False, trim_namespace=False
|
||||
)
|
||||
assert 2 == len(bar_options)
|
||||
assert "bar stuff 1" == bar_options["BAR_STUFF_1"]
|
||||
assert "bar stuff 2" == bar_options["BAR_STUFF_2"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("encoding", ["utf-8", "iso-8859-15", "latin-1"])
|
||||
def test_from_pyfile_weird_encoding(tmp_path, encoding):
|
||||
f = tmp_path / "my_config.py"
|
||||
f.write_text(f'# -*- coding: {encoding} -*-\nTEST_VALUE = "föö"\n', encoding)
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_pyfile(os.fspath(f))
|
||||
value = app.config["TEST_VALUE"]
|
||||
assert value == "föö"
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
from werkzeug.routing import BaseConverter
|
||||
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask import url_for
|
||||
|
||||
|
||||
def test_custom_converters(app, client):
|
||||
class ListConverter(BaseConverter):
|
||||
def to_python(self, value):
|
||||
return value.split(",")
|
||||
|
||||
def to_url(self, value):
|
||||
base_to_url = super().to_url
|
||||
return ",".join(base_to_url(x) for x in value)
|
||||
|
||||
app.url_map.converters["list"] = ListConverter
|
||||
|
||||
@app.route("/<list:args>")
|
||||
def index(args):
|
||||
return "|".join(args)
|
||||
|
||||
assert client.get("/1,2,3").data == b"1|2|3"
|
||||
|
||||
with app.test_request_context():
|
||||
assert url_for("index", args=[4, 5, 6]) == "/4,5,6"
|
||||
|
||||
|
||||
def test_context_available(app, client):
|
||||
class ContextConverter(BaseConverter):
|
||||
def to_python(self, value):
|
||||
assert request is not None
|
||||
assert session is not None
|
||||
return value
|
||||
|
||||
app.url_map.converters["ctx"] = ContextConverter
|
||||
|
||||
@app.get("/<ctx:name>")
|
||||
def index(name):
|
||||
return name
|
||||
|
||||
assert client.get("/admin").data == b"admin"
|
||||
29
tests/test_db.py
Normal file
29
tests/test_db.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import sqlite3
|
||||
|
||||
import pytest
|
||||
|
||||
from flaskr.db import get_db
|
||||
|
||||
|
||||
def test_get_close_db(app):
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
assert db is get_db()
|
||||
|
||||
with pytest.raises(sqlite3.ProgrammingError) as e:
|
||||
db.execute("SELECT 1")
|
||||
|
||||
assert "closed" in str(e.value)
|
||||
|
||||
|
||||
def test_init_db_command(runner, monkeypatch):
|
||||
class Recorder:
|
||||
called = False
|
||||
|
||||
def fake_init_db():
|
||||
Recorder.called = True
|
||||
|
||||
monkeypatch.setattr("flaskr.db.init_db", fake_init_db)
|
||||
result = runner.invoke(args=["init-db"])
|
||||
assert "Initialized" in result.output
|
||||
assert Recorder.called
|
||||
12
tests/test_factory.py
Normal file
12
tests/test_factory.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from flaskr import create_app
|
||||
|
||||
|
||||
def test_config():
|
||||
"""Test create_app without passing test config."""
|
||||
assert not create_app().testing
|
||||
assert create_app({"TESTING": True}).testing
|
||||
|
||||
|
||||
def test_hello(client):
|
||||
response = client.get("/hello")
|
||||
assert response.data == b"Hello, World!"
|
||||
|
|
@ -1,360 +0,0 @@
|
|||
import io
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import werkzeug.exceptions
|
||||
|
||||
import flask
|
||||
from flask.helpers import get_debug_flag
|
||||
|
||||
|
||||
class FakePath:
|
||||
"""Fake object to represent a ``PathLike object``.
|
||||
|
||||
This represents a ``pathlib.Path`` object in python 3.
|
||||
See: https://www.python.org/dev/peps/pep-0519/
|
||||
"""
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def __fspath__(self):
|
||||
return self.path
|
||||
|
||||
|
||||
class PyBytesIO:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._io = io.BytesIO(*args, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._io, name)
|
||||
|
||||
|
||||
class TestSendfile:
|
||||
def test_send_file(self, app, req_ctx):
|
||||
rv = flask.send_file("static/index.html")
|
||||
assert rv.direct_passthrough
|
||||
assert rv.mimetype == "text/html"
|
||||
|
||||
with app.open_resource("static/index.html") as f:
|
||||
rv.direct_passthrough = False
|
||||
assert rv.data == f.read()
|
||||
|
||||
rv.close()
|
||||
|
||||
def test_static_file(self, app, req_ctx):
|
||||
# Default max_age is None.
|
||||
|
||||
# Test with static file handler.
|
||||
rv = app.send_static_file("index.html")
|
||||
assert rv.cache_control.max_age is None
|
||||
rv.close()
|
||||
|
||||
# Test with direct use of send_file.
|
||||
rv = flask.send_file("static/index.html")
|
||||
assert rv.cache_control.max_age is None
|
||||
rv.close()
|
||||
|
||||
app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 3600
|
||||
|
||||
# Test with static file handler.
|
||||
rv = app.send_static_file("index.html")
|
||||
assert rv.cache_control.max_age == 3600
|
||||
rv.close()
|
||||
|
||||
# Test with direct use of send_file.
|
||||
rv = flask.send_file("static/index.html")
|
||||
assert rv.cache_control.max_age == 3600
|
||||
rv.close()
|
||||
|
||||
# Test with pathlib.Path.
|
||||
rv = app.send_static_file(FakePath("index.html"))
|
||||
assert rv.cache_control.max_age == 3600
|
||||
rv.close()
|
||||
|
||||
class StaticFileApp(flask.Flask):
|
||||
def get_send_file_max_age(self, filename):
|
||||
return 10
|
||||
|
||||
app = StaticFileApp(__name__)
|
||||
|
||||
with app.test_request_context():
|
||||
# Test with static file handler.
|
||||
rv = app.send_static_file("index.html")
|
||||
assert rv.cache_control.max_age == 10
|
||||
rv.close()
|
||||
|
||||
# Test with direct use of send_file.
|
||||
rv = flask.send_file("static/index.html")
|
||||
assert rv.cache_control.max_age == 10
|
||||
rv.close()
|
||||
|
||||
def test_send_from_directory(self, app, req_ctx):
|
||||
app.root_path = os.path.join(
|
||||
os.path.dirname(__file__), "test_apps", "subdomaintestmodule"
|
||||
)
|
||||
rv = flask.send_from_directory("static", "hello.txt")
|
||||
rv.direct_passthrough = False
|
||||
assert rv.data.strip() == b"Hello Subdomain"
|
||||
rv.close()
|
||||
|
||||
|
||||
class TestUrlFor:
|
||||
def test_url_for_with_anchor(self, app, req_ctx):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "42"
|
||||
|
||||
assert flask.url_for("index", _anchor="x y") == "/#x%20y"
|
||||
|
||||
def test_url_for_with_scheme(self, app, req_ctx):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "42"
|
||||
|
||||
assert (
|
||||
flask.url_for("index", _external=True, _scheme="https")
|
||||
== "https://localhost/"
|
||||
)
|
||||
|
||||
def test_url_for_with_scheme_not_external(self, app, req_ctx):
|
||||
app.add_url_rule("/", endpoint="index")
|
||||
|
||||
# Implicit external with scheme.
|
||||
url = flask.url_for("index", _scheme="https")
|
||||
assert url == "https://localhost/"
|
||||
|
||||
# Error when external=False with scheme
|
||||
with pytest.raises(ValueError):
|
||||
flask.url_for("index", _scheme="https", _external=False)
|
||||
|
||||
def test_url_for_with_alternating_schemes(self, app, req_ctx):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "42"
|
||||
|
||||
assert flask.url_for("index", _external=True) == "http://localhost/"
|
||||
assert (
|
||||
flask.url_for("index", _external=True, _scheme="https")
|
||||
== "https://localhost/"
|
||||
)
|
||||
assert flask.url_for("index", _external=True) == "http://localhost/"
|
||||
|
||||
def test_url_with_method(self, app, req_ctx):
|
||||
from flask.views import MethodView
|
||||
|
||||
class MyView(MethodView):
|
||||
def get(self, id=None):
|
||||
if id is None:
|
||||
return "List"
|
||||
return f"Get {id:d}"
|
||||
|
||||
def post(self):
|
||||
return "Create"
|
||||
|
||||
myview = MyView.as_view("myview")
|
||||
app.add_url_rule("/myview/", methods=["GET"], view_func=myview)
|
||||
app.add_url_rule("/myview/<int:id>", methods=["GET"], view_func=myview)
|
||||
app.add_url_rule("/myview/create", methods=["POST"], view_func=myview)
|
||||
|
||||
assert flask.url_for("myview", _method="GET") == "/myview/"
|
||||
assert flask.url_for("myview", id=42, _method="GET") == "/myview/42"
|
||||
assert flask.url_for("myview", _method="POST") == "/myview/create"
|
||||
|
||||
def test_url_for_with_self(self, app, req_ctx):
|
||||
@app.route("/<self>")
|
||||
def index(self):
|
||||
return "42"
|
||||
|
||||
assert flask.url_for("index", self="2") == "/2"
|
||||
|
||||
|
||||
def test_redirect_no_app():
|
||||
response = flask.redirect("https://localhost", 307)
|
||||
assert response.location == "https://localhost"
|
||||
assert response.status_code == 307
|
||||
|
||||
|
||||
def test_redirect_with_app(app):
|
||||
def redirect(location, code=302):
|
||||
raise ValueError
|
||||
|
||||
app.redirect = redirect
|
||||
|
||||
with app.app_context(), pytest.raises(ValueError):
|
||||
flask.redirect("other")
|
||||
|
||||
|
||||
def test_abort_no_app():
|
||||
with pytest.raises(werkzeug.exceptions.Unauthorized):
|
||||
flask.abort(401)
|
||||
|
||||
with pytest.raises(LookupError):
|
||||
flask.abort(900)
|
||||
|
||||
|
||||
def test_app_aborter_class():
|
||||
class MyAborter(werkzeug.exceptions.Aborter):
|
||||
pass
|
||||
|
||||
class MyFlask(flask.Flask):
|
||||
aborter_class = MyAborter
|
||||
|
||||
app = MyFlask(__name__)
|
||||
assert isinstance(app.aborter, MyAborter)
|
||||
|
||||
|
||||
def test_abort_with_app(app):
|
||||
class My900Error(werkzeug.exceptions.HTTPException):
|
||||
code = 900
|
||||
|
||||
app.aborter.mapping[900] = My900Error
|
||||
|
||||
with app.app_context(), pytest.raises(My900Error):
|
||||
flask.abort(900)
|
||||
|
||||
|
||||
class TestNoImports:
|
||||
"""Test Flasks are created without import.
|
||||
|
||||
Avoiding ``__import__`` helps create Flask instances where there are errors
|
||||
at import time. Those runtime errors will be apparent to the user soon
|
||||
enough, but tools which build Flask instances meta-programmatically benefit
|
||||
from a Flask which does not ``__import__``. Instead of importing to
|
||||
retrieve file paths or metadata on a module or package, use the pkgutil and
|
||||
imp modules in the Python standard library.
|
||||
"""
|
||||
|
||||
def test_name_with_import_error(self, modules_tmp_path):
|
||||
(modules_tmp_path / "importerror.py").write_text("raise NotImplementedError()")
|
||||
try:
|
||||
flask.Flask("importerror")
|
||||
except NotImplementedError:
|
||||
AssertionError("Flask(import_name) is importing import_name.")
|
||||
|
||||
|
||||
class TestStreaming:
|
||||
def test_streaming_with_context(self, app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
def generate():
|
||||
yield "Hello "
|
||||
yield flask.request.args["name"]
|
||||
yield "!"
|
||||
|
||||
return flask.Response(flask.stream_with_context(generate()))
|
||||
|
||||
rv = client.get("/?name=World")
|
||||
assert rv.data == b"Hello World!"
|
||||
|
||||
def test_streaming_with_context_as_decorator(self, app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
@flask.stream_with_context
|
||||
def generate(hello):
|
||||
yield hello
|
||||
yield flask.request.args["name"]
|
||||
yield "!"
|
||||
|
||||
return flask.Response(generate("Hello "))
|
||||
|
||||
rv = client.get("/?name=World")
|
||||
assert rv.data == b"Hello World!"
|
||||
|
||||
def test_streaming_with_context_and_custom_close(self, app, client):
|
||||
called = []
|
||||
|
||||
class Wrapper:
|
||||
def __init__(self, gen):
|
||||
self._gen = gen
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
called.append(42)
|
||||
|
||||
def __next__(self):
|
||||
return next(self._gen)
|
||||
|
||||
next = __next__
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
def generate():
|
||||
yield "Hello "
|
||||
yield flask.request.args["name"]
|
||||
yield "!"
|
||||
|
||||
return flask.Response(flask.stream_with_context(Wrapper(generate())))
|
||||
|
||||
rv = client.get("/?name=World")
|
||||
assert rv.data == b"Hello World!"
|
||||
assert called == [42]
|
||||
|
||||
def test_stream_keeps_session(self, app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
flask.session["test"] = "flask"
|
||||
|
||||
@flask.stream_with_context
|
||||
def gen():
|
||||
yield flask.session["test"]
|
||||
|
||||
return flask.Response(gen())
|
||||
|
||||
rv = client.get("/")
|
||||
assert rv.data == b"flask"
|
||||
|
||||
|
||||
class TestHelpers:
|
||||
@pytest.mark.parametrize(
|
||||
("debug", "expect"),
|
||||
[
|
||||
("", False),
|
||||
("0", False),
|
||||
("False", False),
|
||||
("No", False),
|
||||
("True", True),
|
||||
],
|
||||
)
|
||||
def test_get_debug_flag(self, monkeypatch, debug, expect):
|
||||
monkeypatch.setenv("FLASK_DEBUG", debug)
|
||||
assert get_debug_flag() == expect
|
||||
|
||||
def test_make_response(self):
|
||||
app = flask.Flask(__name__)
|
||||
with app.test_request_context():
|
||||
rv = flask.helpers.make_response()
|
||||
assert rv.status_code == 200
|
||||
assert rv.mimetype == "text/html"
|
||||
|
||||
rv = flask.helpers.make_response("Hello")
|
||||
assert rv.status_code == 200
|
||||
assert rv.data == b"Hello"
|
||||
assert rv.mimetype == "text/html"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("r", "rb", "rt"))
|
||||
def test_open_resource(mode):
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
with app.open_resource("static/index.html", mode) as f:
|
||||
assert "<h1>Hello World!</h1>" in str(f.read())
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("w", "x", "a", "r+"))
|
||||
def test_open_resource_exceptions(mode):
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
app.open_resource("static/index.html", mode)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("encoding", ("utf-8", "utf-16-le"))
|
||||
def test_open_resource_with_encoding(tmp_path, encoding):
|
||||
app = flask.Flask(__name__, root_path=os.fspath(tmp_path))
|
||||
(tmp_path / "test").write_text("test", encoding=encoding)
|
||||
|
||||
with app.open_resource("test", mode="rt", encoding=encoding) as f:
|
||||
assert f.read() == "test"
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
def test_explicit_instance_paths(modules_tmp_path):
|
||||
with pytest.raises(ValueError, match=".*must be absolute"):
|
||||
flask.Flask(__name__, instance_path="instance")
|
||||
|
||||
app = flask.Flask(__name__, instance_path=os.fspath(modules_tmp_path))
|
||||
assert app.instance_path == os.fspath(modules_tmp_path)
|
||||
|
||||
|
||||
def test_uninstalled_module_paths(modules_tmp_path, purge_module):
|
||||
(modules_tmp_path / "config_module_app.py").write_text(
|
||||
"import os\n"
|
||||
"import flask\n"
|
||||
"here = os.path.abspath(os.path.dirname(__file__))\n"
|
||||
"app = flask.Flask(__name__)\n"
|
||||
)
|
||||
purge_module("config_module_app")
|
||||
|
||||
from config_module_app import app
|
||||
|
||||
assert app.instance_path == os.fspath(modules_tmp_path / "instance")
|
||||
|
||||
|
||||
def test_uninstalled_package_paths(modules_tmp_path, purge_module):
|
||||
app = modules_tmp_path / "config_package_app"
|
||||
app.mkdir()
|
||||
(app / "__init__.py").write_text(
|
||||
"import os\n"
|
||||
"import flask\n"
|
||||
"here = os.path.abspath(os.path.dirname(__file__))\n"
|
||||
"app = flask.Flask(__name__)\n"
|
||||
)
|
||||
purge_module("config_package_app")
|
||||
|
||||
from config_package_app import app
|
||||
|
||||
assert app.instance_path == os.fspath(modules_tmp_path / "instance")
|
||||
|
||||
|
||||
def test_uninstalled_namespace_paths(tmp_path, monkeypatch, purge_module):
|
||||
def create_namespace(package):
|
||||
project = tmp_path / f"project-{package}"
|
||||
monkeypatch.syspath_prepend(os.fspath(project))
|
||||
ns = project / "namespace" / package
|
||||
ns.mkdir(parents=True)
|
||||
(ns / "__init__.py").write_text("import flask\napp = flask.Flask(__name__)\n")
|
||||
return project
|
||||
|
||||
_ = create_namespace("package1")
|
||||
project2 = create_namespace("package2")
|
||||
purge_module("namespace.package2")
|
||||
purge_module("namespace")
|
||||
|
||||
from namespace.package2 import app
|
||||
|
||||
assert app.instance_path == os.fspath(project2 / "instance")
|
||||
|
||||
|
||||
def test_installed_module_paths(
|
||||
modules_tmp_path, modules_tmp_path_prefix, purge_module, site_packages, limit_loader
|
||||
):
|
||||
(site_packages / "site_app.py").write_text(
|
||||
"import flask\napp = flask.Flask(__name__)\n"
|
||||
)
|
||||
purge_module("site_app")
|
||||
|
||||
from site_app import app
|
||||
|
||||
assert app.instance_path == os.fspath(
|
||||
modules_tmp_path / "var" / "site_app-instance"
|
||||
)
|
||||
|
||||
|
||||
def test_installed_package_paths(
|
||||
limit_loader, modules_tmp_path, modules_tmp_path_prefix, purge_module, monkeypatch
|
||||
):
|
||||
installed_path = modules_tmp_path / "path"
|
||||
installed_path.mkdir()
|
||||
monkeypatch.syspath_prepend(installed_path)
|
||||
|
||||
app = installed_path / "installed_package"
|
||||
app.mkdir()
|
||||
(app / "__init__.py").write_text("import flask\napp = flask.Flask(__name__)\n")
|
||||
purge_module("installed_package")
|
||||
|
||||
from installed_package import app
|
||||
|
||||
assert app.instance_path == os.fspath(
|
||||
modules_tmp_path / "var" / "installed_package-instance"
|
||||
)
|
||||
|
||||
|
||||
def test_prefix_package_paths(
|
||||
limit_loader, modules_tmp_path, modules_tmp_path_prefix, purge_module, site_packages
|
||||
):
|
||||
app = site_packages / "site_package"
|
||||
app.mkdir()
|
||||
(app / "__init__.py").write_text("import flask\napp = flask.Flask(__name__)\n")
|
||||
purge_module("site_package")
|
||||
|
||||
import site_package
|
||||
|
||||
assert site_package.app.instance_path == os.fspath(
|
||||
modules_tmp_path / "var" / "site_package-instance"
|
||||
)
|
||||
|
|
@ -1,346 +0,0 @@
|
|||
import datetime
|
||||
import decimal
|
||||
import io
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
from werkzeug.http import http_date
|
||||
|
||||
import flask
|
||||
from flask import json
|
||||
from flask.json.provider import DefaultJSONProvider
|
||||
|
||||
|
||||
@pytest.mark.parametrize("debug", (True, False))
|
||||
def test_bad_request_debug_message(app, client, debug):
|
||||
app.config["DEBUG"] = debug
|
||||
app.config["TRAP_BAD_REQUEST_ERRORS"] = False
|
||||
|
||||
@app.route("/json", methods=["POST"])
|
||||
def post_json():
|
||||
flask.request.get_json()
|
||||
return None
|
||||
|
||||
rv = client.post("/json", data=None, content_type="application/json")
|
||||
assert rv.status_code == 400
|
||||
contains = b"Failed to decode JSON object" in rv.data
|
||||
assert contains == debug
|
||||
|
||||
|
||||
def test_json_bad_requests(app, client):
|
||||
@app.route("/json", methods=["POST"])
|
||||
def return_json():
|
||||
return flask.jsonify(foo=str(flask.request.get_json()))
|
||||
|
||||
rv = client.post("/json", data="malformed", content_type="application/json")
|
||||
assert rv.status_code == 400
|
||||
|
||||
|
||||
def test_json_custom_mimetypes(app, client):
|
||||
@app.route("/json", methods=["POST"])
|
||||
def return_json():
|
||||
return flask.request.get_json()
|
||||
|
||||
rv = client.post("/json", data='"foo"', content_type="application/x+json")
|
||||
assert rv.data == b"foo"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_value,expected", [(True, '"\\u2603"'), (False, '"\u2603"')]
|
||||
)
|
||||
def test_json_as_unicode(test_value, expected, app, app_ctx):
|
||||
app.json.ensure_ascii = test_value
|
||||
rv = app.json.dumps("\N{SNOWMAN}")
|
||||
assert rv == expected
|
||||
|
||||
|
||||
def test_json_dump_to_file(app, app_ctx):
|
||||
test_data = {"name": "Flask"}
|
||||
out = io.StringIO()
|
||||
|
||||
flask.json.dump(test_data, out)
|
||||
out.seek(0)
|
||||
rv = flask.json.load(out)
|
||||
assert rv == test_data
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_value", [0, -1, 1, 23, 3.14, "s", "longer string", True, False, None]
|
||||
)
|
||||
def test_jsonify_basic_types(test_value, app, client):
|
||||
url = "/jsonify_basic_types"
|
||||
app.add_url_rule(url, url, lambda x=test_value: flask.jsonify(x))
|
||||
rv = client.get(url)
|
||||
assert rv.mimetype == "application/json"
|
||||
assert flask.json.loads(rv.data) == test_value
|
||||
|
||||
|
||||
def test_jsonify_dicts(app, client):
|
||||
d = {
|
||||
"a": 0,
|
||||
"b": 23,
|
||||
"c": 3.14,
|
||||
"d": "t",
|
||||
"e": "Hi",
|
||||
"f": True,
|
||||
"g": False,
|
||||
"h": ["test list", 10, False],
|
||||
"i": {"test": "dict"},
|
||||
}
|
||||
|
||||
@app.route("/kw")
|
||||
def return_kwargs():
|
||||
return flask.jsonify(**d)
|
||||
|
||||
@app.route("/dict")
|
||||
def return_dict():
|
||||
return flask.jsonify(d)
|
||||
|
||||
for url in "/kw", "/dict":
|
||||
rv = client.get(url)
|
||||
assert rv.mimetype == "application/json"
|
||||
assert flask.json.loads(rv.data) == d
|
||||
|
||||
|
||||
def test_jsonify_arrays(app, client):
|
||||
"""Test jsonify of lists and args unpacking."""
|
||||
a_list = [
|
||||
0,
|
||||
42,
|
||||
3.14,
|
||||
"t",
|
||||
"hello",
|
||||
True,
|
||||
False,
|
||||
["test list", 2, False],
|
||||
{"test": "dict"},
|
||||
]
|
||||
|
||||
@app.route("/args_unpack")
|
||||
def return_args_unpack():
|
||||
return flask.jsonify(*a_list)
|
||||
|
||||
@app.route("/array")
|
||||
def return_array():
|
||||
return flask.jsonify(a_list)
|
||||
|
||||
for url in "/args_unpack", "/array":
|
||||
rv = client.get(url)
|
||||
assert rv.mimetype == "application/json"
|
||||
assert flask.json.loads(rv.data) == a_list
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value", [datetime.datetime(1973, 3, 11, 6, 30, 45), datetime.date(1975, 1, 5)]
|
||||
)
|
||||
def test_jsonify_datetime(app, client, value):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.jsonify(value=value)
|
||||
|
||||
r = client.get()
|
||||
assert r.json["value"] == http_date(value)
|
||||
|
||||
|
||||
class FixedOffset(datetime.tzinfo):
|
||||
"""Fixed offset in hours east from UTC.
|
||||
|
||||
This is a slight adaptation of the ``FixedOffset`` example found in
|
||||
https://docs.python.org/2.7/library/datetime.html.
|
||||
"""
|
||||
|
||||
def __init__(self, hours, name):
|
||||
self.__offset = datetime.timedelta(hours=hours)
|
||||
self.__name = name
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self.__offset
|
||||
|
||||
def tzname(self, dt):
|
||||
return self.__name
|
||||
|
||||
def dst(self, dt):
|
||||
return datetime.timedelta()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tz", (("UTC", 0), ("PST", -8), ("KST", 9)))
|
||||
def test_jsonify_aware_datetimes(tz):
|
||||
"""Test if aware datetime.datetime objects are converted into GMT."""
|
||||
tzinfo = FixedOffset(hours=tz[1], name=tz[0])
|
||||
dt = datetime.datetime(2017, 1, 1, 12, 34, 56, tzinfo=tzinfo)
|
||||
gmt = FixedOffset(hours=0, name="GMT")
|
||||
expected = dt.astimezone(gmt).strftime('"%a, %d %b %Y %H:%M:%S %Z"')
|
||||
assert flask.json.dumps(dt) == expected
|
||||
|
||||
|
||||
def test_jsonify_uuid_types(app, client):
|
||||
"""Test jsonify with uuid.UUID types"""
|
||||
|
||||
test_uuid = uuid.UUID(bytes=b"\xde\xad\xbe\xef" * 4)
|
||||
url = "/uuid_test"
|
||||
app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid))
|
||||
|
||||
rv = client.get(url)
|
||||
|
||||
rv_x = flask.json.loads(rv.data)["x"]
|
||||
assert rv_x == str(test_uuid)
|
||||
rv_uuid = uuid.UUID(rv_x)
|
||||
assert rv_uuid == test_uuid
|
||||
|
||||
|
||||
def test_json_decimal():
|
||||
rv = flask.json.dumps(decimal.Decimal("0.003"))
|
||||
assert rv == '"0.003"'
|
||||
|
||||
|
||||
def test_json_attr(app, client):
|
||||
@app.route("/add", methods=["POST"])
|
||||
def add():
|
||||
json = flask.request.get_json()
|
||||
return str(json["a"] + json["b"])
|
||||
|
||||
rv = client.post(
|
||||
"/add",
|
||||
data=flask.json.dumps({"a": 1, "b": 2}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert rv.data == b"3"
|
||||
|
||||
|
||||
def test_tojson_filter(app, req_ctx):
|
||||
# The tojson filter is tested in Jinja, this confirms that it's
|
||||
# using Flask's dumps.
|
||||
rv = flask.render_template_string(
|
||||
"const data = {{ data|tojson }};",
|
||||
data={"name": "</script>", "time": datetime.datetime(2021, 2, 1, 7, 15)},
|
||||
)
|
||||
assert rv == (
|
||||
'const data = {"name": "\\u003c/script\\u003e",'
|
||||
' "time": "Mon, 01 Feb 2021 07:15:00 GMT"};'
|
||||
)
|
||||
|
||||
|
||||
def test_json_customization(app, client):
|
||||
class X: # noqa: B903, for Python2 compatibility
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
def default(o):
|
||||
if isinstance(o, X):
|
||||
return f"<{o.val}>"
|
||||
|
||||
return DefaultJSONProvider.default(o)
|
||||
|
||||
class CustomProvider(DefaultJSONProvider):
|
||||
def object_hook(self, obj):
|
||||
if len(obj) == 1 and "_foo" in obj:
|
||||
return X(obj["_foo"])
|
||||
|
||||
return obj
|
||||
|
||||
def loads(self, s, **kwargs):
|
||||
kwargs.setdefault("object_hook", self.object_hook)
|
||||
return super().loads(s, **kwargs)
|
||||
|
||||
app.json = CustomProvider(app)
|
||||
app.json.default = default
|
||||
|
||||
@app.route("/", methods=["POST"])
|
||||
def index():
|
||||
return flask.json.dumps(flask.request.get_json()["x"])
|
||||
|
||||
rv = client.post(
|
||||
"/",
|
||||
data=flask.json.dumps({"x": {"_foo": 42}}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert rv.data == b'"<42>"'
|
||||
|
||||
|
||||
def _has_encoding(name):
|
||||
try:
|
||||
import codecs
|
||||
|
||||
codecs.lookup(name)
|
||||
return True
|
||||
except LookupError:
|
||||
return False
|
||||
|
||||
|
||||
def test_json_key_sorting(app, client):
|
||||
app.debug = True
|
||||
assert app.json.sort_keys
|
||||
d = dict.fromkeys(range(20), "foo")
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.jsonify(values=d)
|
||||
|
||||
rv = client.get("/")
|
||||
lines = [x.strip() for x in rv.data.strip().decode("utf-8").splitlines()]
|
||||
sorted_by_str = [
|
||||
"{",
|
||||
'"values": {',
|
||||
'"0": "foo",',
|
||||
'"1": "foo",',
|
||||
'"10": "foo",',
|
||||
'"11": "foo",',
|
||||
'"12": "foo",',
|
||||
'"13": "foo",',
|
||||
'"14": "foo",',
|
||||
'"15": "foo",',
|
||||
'"16": "foo",',
|
||||
'"17": "foo",',
|
||||
'"18": "foo",',
|
||||
'"19": "foo",',
|
||||
'"2": "foo",',
|
||||
'"3": "foo",',
|
||||
'"4": "foo",',
|
||||
'"5": "foo",',
|
||||
'"6": "foo",',
|
||||
'"7": "foo",',
|
||||
'"8": "foo",',
|
||||
'"9": "foo"',
|
||||
"}",
|
||||
"}",
|
||||
]
|
||||
sorted_by_int = [
|
||||
"{",
|
||||
'"values": {',
|
||||
'"0": "foo",',
|
||||
'"1": "foo",',
|
||||
'"2": "foo",',
|
||||
'"3": "foo",',
|
||||
'"4": "foo",',
|
||||
'"5": "foo",',
|
||||
'"6": "foo",',
|
||||
'"7": "foo",',
|
||||
'"8": "foo",',
|
||||
'"9": "foo",',
|
||||
'"10": "foo",',
|
||||
'"11": "foo",',
|
||||
'"12": "foo",',
|
||||
'"13": "foo",',
|
||||
'"14": "foo",',
|
||||
'"15": "foo",',
|
||||
'"16": "foo",',
|
||||
'"17": "foo",',
|
||||
'"18": "foo",',
|
||||
'"19": "foo"',
|
||||
"}",
|
||||
"}",
|
||||
]
|
||||
|
||||
try:
|
||||
assert lines == sorted_by_int
|
||||
except AssertionError:
|
||||
assert lines == sorted_by_str
|
||||
|
||||
|
||||
def test_html_method():
|
||||
class ObjectWithHTML:
|
||||
def __html__(self):
|
||||
return "<p>test</p>"
|
||||
|
||||
result = json.dumps(ObjectWithHTML())
|
||||
assert result == '"<p>test</p>"'
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
from datetime import datetime
|
||||
from datetime import timezone
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from markupsafe import Markup
|
||||
|
||||
from flask.json.tag import JSONTag
|
||||
from flask.json.tag import TaggedJSONSerializer
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data",
|
||||
(
|
||||
{" t": (1, 2, 3)},
|
||||
{" t__": b"a"},
|
||||
{" di": " di"},
|
||||
{"x": (1, 2, 3), "y": 4},
|
||||
(1, 2, 3),
|
||||
[(1, 2, 3)],
|
||||
b"\xff",
|
||||
Markup("<html>"),
|
||||
uuid4(),
|
||||
datetime.now(tz=timezone.utc).replace(microsecond=0),
|
||||
),
|
||||
)
|
||||
def test_dump_load_unchanged(data):
|
||||
s = TaggedJSONSerializer()
|
||||
assert s.loads(s.dumps(data)) == data
|
||||
|
||||
|
||||
def test_duplicate_tag():
|
||||
class TagDict(JSONTag):
|
||||
key = " d"
|
||||
|
||||
s = TaggedJSONSerializer()
|
||||
pytest.raises(KeyError, s.register, TagDict)
|
||||
s.register(TagDict, force=True, index=0)
|
||||
assert isinstance(s.tags[" d"], TagDict)
|
||||
assert isinstance(s.order[0], TagDict)
|
||||
|
||||
|
||||
def test_custom_tag():
|
||||
class Foo: # noqa: B903, for Python2 compatibility
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
class TagFoo(JSONTag):
|
||||
__slots__ = ()
|
||||
key = " f"
|
||||
|
||||
def check(self, value):
|
||||
return isinstance(value, Foo)
|
||||
|
||||
def to_json(self, value):
|
||||
return self.serializer.tag(value.data)
|
||||
|
||||
def to_python(self, value):
|
||||
return Foo(value)
|
||||
|
||||
s = TaggedJSONSerializer()
|
||||
s.register(TagFoo)
|
||||
assert s.loads(s.dumps(Foo("bar"))).data == "bar"
|
||||
|
||||
|
||||
def test_tag_interface():
|
||||
t = JSONTag(None)
|
||||
pytest.raises(NotImplementedError, t.check, None)
|
||||
pytest.raises(NotImplementedError, t.to_json, None)
|
||||
pytest.raises(NotImplementedError, t.to_python, None)
|
||||
|
||||
|
||||
def test_tag_order():
|
||||
class Tag1(JSONTag):
|
||||
key = " 1"
|
||||
|
||||
class Tag2(JSONTag):
|
||||
key = " 2"
|
||||
|
||||
s = TaggedJSONSerializer()
|
||||
|
||||
s.register(Tag1, index=-1)
|
||||
assert isinstance(s.order[-2], Tag1)
|
||||
|
||||
s.register(Tag2, index=None)
|
||||
assert isinstance(s.order[-1], Tag2)
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
import logging
|
||||
import sys
|
||||
from io import StringIO
|
||||
|
||||
import pytest
|
||||
|
||||
from flask.logging import default_handler
|
||||
from flask.logging import has_level_handler
|
||||
from flask.logging import wsgi_errors_stream
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_logging(pytestconfig):
|
||||
root_handlers = logging.root.handlers[:]
|
||||
logging.root.handlers = []
|
||||
root_level = logging.root.level
|
||||
|
||||
logger = logging.getLogger("flask_test")
|
||||
logger.handlers = []
|
||||
logger.setLevel(logging.NOTSET)
|
||||
|
||||
logging_plugin = pytestconfig.pluginmanager.unregister(name="logging-plugin")
|
||||
|
||||
yield
|
||||
|
||||
logging.root.handlers[:] = root_handlers
|
||||
logging.root.setLevel(root_level)
|
||||
|
||||
logger.handlers = []
|
||||
logger.setLevel(logging.NOTSET)
|
||||
|
||||
if logging_plugin:
|
||||
pytestconfig.pluginmanager.register(logging_plugin, "logging-plugin")
|
||||
|
||||
|
||||
def test_logger(app):
|
||||
assert app.logger.name == "flask_test"
|
||||
assert app.logger.level == logging.NOTSET
|
||||
assert app.logger.handlers == [default_handler]
|
||||
|
||||
|
||||
def test_logger_debug(app):
|
||||
app.debug = True
|
||||
assert app.logger.level == logging.DEBUG
|
||||
assert app.logger.handlers == [default_handler]
|
||||
|
||||
|
||||
def test_existing_handler(app):
|
||||
logging.root.addHandler(logging.StreamHandler())
|
||||
assert app.logger.level == logging.NOTSET
|
||||
assert not app.logger.handlers
|
||||
|
||||
|
||||
def test_wsgi_errors_stream(app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
app.logger.error("test")
|
||||
return ""
|
||||
|
||||
stream = StringIO()
|
||||
client.get("/", errors_stream=stream)
|
||||
assert "ERROR in test_logging: test" in stream.getvalue()
|
||||
|
||||
assert wsgi_errors_stream._get_current_object() is sys.stderr
|
||||
|
||||
with app.test_request_context(errors_stream=stream):
|
||||
assert wsgi_errors_stream._get_current_object() is stream
|
||||
|
||||
|
||||
def test_has_level_handler():
|
||||
logger = logging.getLogger("flask.app")
|
||||
assert not has_level_handler(logger)
|
||||
|
||||
handler = logging.StreamHandler()
|
||||
logging.root.addHandler(handler)
|
||||
assert has_level_handler(logger)
|
||||
|
||||
logger.propagate = False
|
||||
assert not has_level_handler(logger)
|
||||
logger.propagate = True
|
||||
|
||||
handler.setLevel(logging.ERROR)
|
||||
assert not has_level_handler(logger)
|
||||
|
||||
|
||||
def test_log_view_exception(app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
raise Exception("test")
|
||||
|
||||
app.testing = False
|
||||
stream = StringIO()
|
||||
rv = client.get("/", errors_stream=stream)
|
||||
assert rv.status_code == 500
|
||||
assert rv.data
|
||||
err = stream.getvalue()
|
||||
assert "Exception on / [GET]" in err
|
||||
assert "Exception: test" in err
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import flask
|
||||
|
||||
|
||||
def test_aborting(app):
|
||||
class Foo(Exception):
|
||||
whatever = 42
|
||||
|
||||
@app.errorhandler(Foo)
|
||||
def handle_foo(e):
|
||||
return str(e.whatever)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
raise flask.abort(flask.redirect(flask.url_for("test")))
|
||||
|
||||
@app.route("/test")
|
||||
def test():
|
||||
raise Foo()
|
||||
|
||||
with app.test_client() as c:
|
||||
rv = c.get("/")
|
||||
location_parts = rv.headers["Location"].rpartition("/")
|
||||
|
||||
if location_parts[0]:
|
||||
# For older Werkzeug that used absolute redirects.
|
||||
assert location_parts[0] == "http://localhost"
|
||||
|
||||
assert location_parts[2] == "test"
|
||||
rv = c.get("/test")
|
||||
assert rv.data == b"42"
|
||||
|
|
@ -1,325 +0,0 @@
|
|||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
import flask
|
||||
from flask.globals import request_ctx
|
||||
from flask.sessions import SecureCookieSessionInterface
|
||||
from flask.sessions import SessionInterface
|
||||
|
||||
try:
|
||||
from greenlet import greenlet
|
||||
except ImportError:
|
||||
greenlet = None
|
||||
|
||||
|
||||
def test_teardown_on_pop(app):
|
||||
buffer = []
|
||||
|
||||
@app.teardown_request
|
||||
def end_of_request(exception):
|
||||
buffer.append(exception)
|
||||
|
||||
ctx = app.test_request_context()
|
||||
ctx.push()
|
||||
assert buffer == []
|
||||
ctx.pop()
|
||||
assert buffer == [None]
|
||||
|
||||
|
||||
def test_teardown_with_previous_exception(app):
|
||||
buffer = []
|
||||
|
||||
@app.teardown_request
|
||||
def end_of_request(exception):
|
||||
buffer.append(exception)
|
||||
|
||||
try:
|
||||
raise Exception("dummy")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
with app.test_request_context():
|
||||
assert buffer == []
|
||||
assert buffer == [None]
|
||||
|
||||
|
||||
def test_teardown_with_handled_exception(app):
|
||||
buffer = []
|
||||
|
||||
@app.teardown_request
|
||||
def end_of_request(exception):
|
||||
buffer.append(exception)
|
||||
|
||||
with app.test_request_context():
|
||||
assert buffer == []
|
||||
try:
|
||||
raise Exception("dummy")
|
||||
except Exception:
|
||||
pass
|
||||
assert buffer == [None]
|
||||
|
||||
|
||||
def test_proper_test_request_context(app):
|
||||
app.config.update(SERVER_NAME="localhost.localdomain:5000")
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return None
|
||||
|
||||
@app.route("/", subdomain="foo")
|
||||
def sub():
|
||||
return None
|
||||
|
||||
with app.test_request_context("/"):
|
||||
assert (
|
||||
flask.url_for("index", _external=True)
|
||||
== "http://localhost.localdomain:5000/"
|
||||
)
|
||||
|
||||
with app.test_request_context("/"):
|
||||
assert (
|
||||
flask.url_for("sub", _external=True)
|
||||
== "http://foo.localhost.localdomain:5000/"
|
||||
)
|
||||
|
||||
# suppress Werkzeug 0.15 warning about name mismatch
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings(
|
||||
"ignore", "Current server name", UserWarning, "flask.app"
|
||||
)
|
||||
with app.test_request_context(
|
||||
"/", environ_overrides={"HTTP_HOST": "localhost"}
|
||||
):
|
||||
pass
|
||||
|
||||
app.config.update(SERVER_NAME="localhost")
|
||||
with app.test_request_context("/", environ_overrides={"SERVER_NAME": "localhost"}):
|
||||
pass
|
||||
|
||||
app.config.update(SERVER_NAME="localhost:80")
|
||||
with app.test_request_context(
|
||||
"/", environ_overrides={"SERVER_NAME": "localhost:80"}
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def test_context_binding(app):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return f"Hello {flask.request.args['name']}!"
|
||||
|
||||
@app.route("/meh")
|
||||
def meh():
|
||||
return flask.request.url
|
||||
|
||||
with app.test_request_context("/?name=World"):
|
||||
assert index() == "Hello World!"
|
||||
with app.test_request_context("/meh"):
|
||||
assert meh() == "http://localhost/meh"
|
||||
assert not flask.request
|
||||
|
||||
|
||||
def test_context_test(app):
|
||||
assert not flask.request
|
||||
assert not flask.has_request_context()
|
||||
ctx = app.test_request_context()
|
||||
ctx.push()
|
||||
try:
|
||||
assert flask.request
|
||||
assert flask.has_request_context()
|
||||
finally:
|
||||
ctx.pop()
|
||||
|
||||
|
||||
def test_manual_context_binding(app):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return f"Hello {flask.request.args['name']}!"
|
||||
|
||||
ctx = app.test_request_context("/?name=World")
|
||||
ctx.push()
|
||||
assert index() == "Hello World!"
|
||||
ctx.pop()
|
||||
with pytest.raises(RuntimeError):
|
||||
index()
|
||||
|
||||
|
||||
@pytest.mark.skipif(greenlet is None, reason="greenlet not installed")
|
||||
class TestGreenletContextCopying:
|
||||
def test_greenlet_context_copying(self, app, client):
|
||||
greenlets = []
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
flask.session["fizz"] = "buzz"
|
||||
reqctx = request_ctx.copy()
|
||||
|
||||
def g():
|
||||
assert not flask.request
|
||||
assert not flask.current_app
|
||||
with reqctx:
|
||||
assert flask.request
|
||||
assert flask.current_app == app
|
||||
assert flask.request.path == "/"
|
||||
assert flask.request.args["foo"] == "bar"
|
||||
assert flask.session.get("fizz") == "buzz"
|
||||
assert not flask.request
|
||||
return 42
|
||||
|
||||
greenlets.append(greenlet(g))
|
||||
return "Hello World!"
|
||||
|
||||
rv = client.get("/?foo=bar")
|
||||
assert rv.data == b"Hello World!"
|
||||
|
||||
result = greenlets[0].run()
|
||||
assert result == 42
|
||||
|
||||
def test_greenlet_context_copying_api(self, app, client):
|
||||
greenlets = []
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
flask.session["fizz"] = "buzz"
|
||||
|
||||
@flask.copy_current_request_context
|
||||
def g():
|
||||
assert flask.request
|
||||
assert flask.current_app == app
|
||||
assert flask.request.path == "/"
|
||||
assert flask.request.args["foo"] == "bar"
|
||||
assert flask.session.get("fizz") == "buzz"
|
||||
return 42
|
||||
|
||||
greenlets.append(greenlet(g))
|
||||
return "Hello World!"
|
||||
|
||||
rv = client.get("/?foo=bar")
|
||||
assert rv.data == b"Hello World!"
|
||||
|
||||
result = greenlets[0].run()
|
||||
assert result == 42
|
||||
|
||||
|
||||
def test_session_error_pops_context():
|
||||
class SessionError(Exception):
|
||||
pass
|
||||
|
||||
class FailingSessionInterface(SessionInterface):
|
||||
def open_session(self, app, request):
|
||||
raise SessionError()
|
||||
|
||||
class CustomFlask(flask.Flask):
|
||||
session_interface = FailingSessionInterface()
|
||||
|
||||
app = CustomFlask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
# shouldn't get here
|
||||
AssertionError()
|
||||
|
||||
response = app.test_client().get("/")
|
||||
assert response.status_code == 500
|
||||
assert not flask.request
|
||||
assert not flask.current_app
|
||||
|
||||
|
||||
def test_session_dynamic_cookie_name():
|
||||
# This session interface will use a cookie with a different name if the
|
||||
# requested url ends with the string "dynamic_cookie"
|
||||
class PathAwareSessionInterface(SecureCookieSessionInterface):
|
||||
def get_cookie_name(self, app):
|
||||
if flask.request.url.endswith("dynamic_cookie"):
|
||||
return "dynamic_cookie_name"
|
||||
else:
|
||||
return super().get_cookie_name(app)
|
||||
|
||||
class CustomFlask(flask.Flask):
|
||||
session_interface = PathAwareSessionInterface()
|
||||
|
||||
app = CustomFlask(__name__)
|
||||
app.secret_key = "secret_key"
|
||||
|
||||
@app.route("/set", methods=["POST"])
|
||||
def set():
|
||||
flask.session["value"] = flask.request.form["value"]
|
||||
return "value set"
|
||||
|
||||
@app.route("/get")
|
||||
def get():
|
||||
v = flask.session.get("value", "None")
|
||||
return v
|
||||
|
||||
@app.route("/set_dynamic_cookie", methods=["POST"])
|
||||
def set_dynamic_cookie():
|
||||
flask.session["value"] = flask.request.form["value"]
|
||||
return "value set"
|
||||
|
||||
@app.route("/get_dynamic_cookie")
|
||||
def get_dynamic_cookie():
|
||||
v = flask.session.get("value", "None")
|
||||
return v
|
||||
|
||||
test_client = app.test_client()
|
||||
|
||||
# first set the cookie in both /set urls but each with a different value
|
||||
assert test_client.post("/set", data={"value": "42"}).data == b"value set"
|
||||
assert (
|
||||
test_client.post("/set_dynamic_cookie", data={"value": "616"}).data
|
||||
== b"value set"
|
||||
)
|
||||
|
||||
# now check that the relevant values come back - meaning that different
|
||||
# cookies are being used for the urls that end with "dynamic cookie"
|
||||
assert test_client.get("/get").data == b"42"
|
||||
assert test_client.get("/get_dynamic_cookie").data == b"616"
|
||||
|
||||
|
||||
def test_bad_environ_raises_bad_request():
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
from flask.testing import EnvironBuilder
|
||||
|
||||
builder = EnvironBuilder(app)
|
||||
environ = builder.get_environ()
|
||||
|
||||
# use a non-printable character in the Host - this is key to this test
|
||||
environ["HTTP_HOST"] = "\x8a"
|
||||
|
||||
with app.request_context(environ):
|
||||
response = app.full_dispatch_request()
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_environ_for_valid_idna_completes():
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "Hello World!"
|
||||
|
||||
from flask.testing import EnvironBuilder
|
||||
|
||||
builder = EnvironBuilder(app)
|
||||
environ = builder.get_environ()
|
||||
|
||||
# these characters are all IDNA-compatible
|
||||
environ["HTTP_HOST"] = "ąśźäüжŠßя.com"
|
||||
|
||||
with app.request_context(environ):
|
||||
response = app.full_dispatch_request()
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_normal_environ_completes():
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "Hello World!"
|
||||
|
||||
response = app.test_client().get("/", headers={"host": "xn--on-0ia.com"})
|
||||
assert response.status_code == 200
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import flask
|
||||
from flask.globals import request_ctx
|
||||
from flask.sessions import SessionInterface
|
||||
|
||||
|
||||
def test_open_session_with_endpoint():
|
||||
"""If request.endpoint (or other URL matching behavior) is needed
|
||||
while loading the session, RequestContext.match_request() can be
|
||||
called manually.
|
||||
"""
|
||||
|
||||
class MySessionInterface(SessionInterface):
|
||||
def save_session(self, app, session, response):
|
||||
pass
|
||||
|
||||
def open_session(self, app, request):
|
||||
request_ctx.match_request()
|
||||
assert request.endpoint is not None
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.session_interface = MySessionInterface()
|
||||
|
||||
@app.get("/")
|
||||
def index():
|
||||
return "Hello, World!"
|
||||
|
||||
response = app.test_client().get("/")
|
||||
assert response.status_code == 200
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
import flask
|
||||
|
||||
|
||||
def test_template_rendered(app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.render_template("simple_template.html", whiskey=42)
|
||||
|
||||
recorded = []
|
||||
|
||||
def record(sender, template, context):
|
||||
recorded.append((template, context))
|
||||
|
||||
flask.template_rendered.connect(record, app)
|
||||
try:
|
||||
client.get("/")
|
||||
assert len(recorded) == 1
|
||||
template, context = recorded[0]
|
||||
assert template.name == "simple_template.html"
|
||||
assert context["whiskey"] == 42
|
||||
finally:
|
||||
flask.template_rendered.disconnect(record, app)
|
||||
|
||||
|
||||
def test_before_render_template():
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.render_template("simple_template.html", whiskey=42)
|
||||
|
||||
recorded = []
|
||||
|
||||
def record(sender, template, context):
|
||||
context["whiskey"] = 43
|
||||
recorded.append((template, context))
|
||||
|
||||
flask.before_render_template.connect(record, app)
|
||||
try:
|
||||
rv = app.test_client().get("/")
|
||||
assert len(recorded) == 1
|
||||
template, context = recorded[0]
|
||||
assert template.name == "simple_template.html"
|
||||
assert context["whiskey"] == 43
|
||||
assert rv.data == b"<h1>43</h1>"
|
||||
finally:
|
||||
flask.before_render_template.disconnect(record, app)
|
||||
|
||||
|
||||
def test_request_signals():
|
||||
app = flask.Flask(__name__)
|
||||
calls = []
|
||||
|
||||
def before_request_signal(sender):
|
||||
calls.append("before-signal")
|
||||
|
||||
def after_request_signal(sender, response):
|
||||
assert response.data == b"stuff"
|
||||
calls.append("after-signal")
|
||||
|
||||
@app.before_request
|
||||
def before_request_handler():
|
||||
calls.append("before-handler")
|
||||
|
||||
@app.after_request
|
||||
def after_request_handler(response):
|
||||
calls.append("after-handler")
|
||||
response.data = "stuff"
|
||||
return response
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
calls.append("handler")
|
||||
return "ignored anyway"
|
||||
|
||||
flask.request_started.connect(before_request_signal, app)
|
||||
flask.request_finished.connect(after_request_signal, app)
|
||||
|
||||
try:
|
||||
rv = app.test_client().get("/")
|
||||
assert rv.data == b"stuff"
|
||||
|
||||
assert calls == [
|
||||
"before-signal",
|
||||
"before-handler",
|
||||
"handler",
|
||||
"after-handler",
|
||||
"after-signal",
|
||||
]
|
||||
finally:
|
||||
flask.request_started.disconnect(before_request_signal, app)
|
||||
flask.request_finished.disconnect(after_request_signal, app)
|
||||
|
||||
|
||||
def test_request_exception_signal():
|
||||
app = flask.Flask(__name__)
|
||||
recorded = []
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
raise ZeroDivisionError
|
||||
|
||||
def record(sender, exception):
|
||||
recorded.append(exception)
|
||||
|
||||
flask.got_request_exception.connect(record, app)
|
||||
try:
|
||||
assert app.test_client().get("/").status_code == 500
|
||||
assert len(recorded) == 1
|
||||
assert isinstance(recorded[0], ZeroDivisionError)
|
||||
finally:
|
||||
flask.got_request_exception.disconnect(record, app)
|
||||
|
||||
|
||||
def test_appcontext_signals(app, client):
|
||||
recorded = []
|
||||
|
||||
def record_push(sender, **kwargs):
|
||||
recorded.append("push")
|
||||
|
||||
def record_pop(sender, **kwargs):
|
||||
recorded.append("pop")
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "Hello"
|
||||
|
||||
flask.appcontext_pushed.connect(record_push, app)
|
||||
flask.appcontext_popped.connect(record_pop, app)
|
||||
try:
|
||||
rv = client.get("/")
|
||||
assert rv.data == b"Hello"
|
||||
assert recorded == ["push", "pop"]
|
||||
finally:
|
||||
flask.appcontext_pushed.disconnect(record_push, app)
|
||||
flask.appcontext_popped.disconnect(record_pop, app)
|
||||
|
||||
|
||||
def test_flash_signal(app):
|
||||
@app.route("/")
|
||||
def index():
|
||||
flask.flash("This is a flash message", category="notice")
|
||||
return flask.redirect("/other")
|
||||
|
||||
recorded = []
|
||||
|
||||
def record(sender, message, category):
|
||||
recorded.append((message, category))
|
||||
|
||||
flask.message_flashed.connect(record, app)
|
||||
try:
|
||||
client = app.test_client()
|
||||
with client.session_transaction():
|
||||
client.get("/")
|
||||
assert len(recorded) == 1
|
||||
message, category = recorded[0]
|
||||
assert message == "This is a flash message"
|
||||
assert category == "notice"
|
||||
finally:
|
||||
flask.message_flashed.disconnect(record, app)
|
||||
|
||||
|
||||
def test_appcontext_tearing_down_signal(app, client):
|
||||
app.testing = False
|
||||
recorded = []
|
||||
|
||||
def record_teardown(sender, exc):
|
||||
recorded.append(exc)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
raise ZeroDivisionError
|
||||
|
||||
flask.appcontext_tearing_down.connect(record_teardown, app)
|
||||
try:
|
||||
rv = client.get("/")
|
||||
assert rv.status_code == 500
|
||||
assert len(recorded) == 1
|
||||
assert isinstance(recorded[0], ZeroDivisionError)
|
||||
finally:
|
||||
flask.appcontext_tearing_down.disconnect(record_teardown, app)
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
from io import StringIO
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
def test_suppressed_exception_logging():
|
||||
class SuppressedFlask(flask.Flask):
|
||||
def log_exception(self, exc_info):
|
||||
pass
|
||||
|
||||
out = StringIO()
|
||||
app = SuppressedFlask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
raise Exception("test")
|
||||
|
||||
rv = app.test_client().get("/", errors_stream=out)
|
||||
assert rv.status_code == 500
|
||||
assert b"Internal Server Error" in rv.data
|
||||
assert not out.getvalue()
|
||||
|
|
@ -1,451 +0,0 @@
|
|||
import logging
|
||||
|
||||
import pytest
|
||||
import werkzeug.serving
|
||||
from jinja2 import TemplateNotFound
|
||||
from markupsafe import Markup
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
def test_context_processing(app, client):
|
||||
@app.context_processor
|
||||
def context_processor():
|
||||
return {"injected_value": 42}
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.render_template("context_template.html", value=23)
|
||||
|
||||
rv = client.get("/")
|
||||
assert rv.data == b"<p>23|42"
|
||||
|
||||
|
||||
def test_original_win(app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.render_template_string("{{ config }}", config=42)
|
||||
|
||||
rv = client.get("/")
|
||||
assert rv.data == b"42"
|
||||
|
||||
|
||||
def test_simple_stream(app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.stream_template_string("{{ config }}", config=42)
|
||||
|
||||
rv = client.get("/")
|
||||
assert rv.data == b"42"
|
||||
|
||||
|
||||
def test_request_less_rendering(app, app_ctx):
|
||||
app.config["WORLD_NAME"] = "Special World"
|
||||
|
||||
@app.context_processor
|
||||
def context_processor():
|
||||
return dict(foo=42)
|
||||
|
||||
rv = flask.render_template_string("Hello {{ config.WORLD_NAME }} {{ foo }}")
|
||||
assert rv == "Hello Special World 42"
|
||||
|
||||
|
||||
def test_standard_context(app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
flask.g.foo = 23
|
||||
flask.session["test"] = "aha"
|
||||
return flask.render_template_string(
|
||||
"""
|
||||
{{ request.args.foo }}
|
||||
{{ g.foo }}
|
||||
{{ config.DEBUG }}
|
||||
{{ session.test }}
|
||||
"""
|
||||
)
|
||||
|
||||
rv = client.get("/?foo=42")
|
||||
assert rv.data.split() == [b"42", b"23", b"False", b"aha"]
|
||||
|
||||
|
||||
def test_escaping(app, client):
|
||||
text = "<p>Hello World!"
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.render_template(
|
||||
"escaping_template.html", text=text, html=Markup(text)
|
||||
)
|
||||
|
||||
lines = client.get("/").data.splitlines()
|
||||
assert lines == [
|
||||
b"<p>Hello World!",
|
||||
b"<p>Hello World!",
|
||||
b"<p>Hello World!",
|
||||
b"<p>Hello World!",
|
||||
b"<p>Hello World!",
|
||||
b"<p>Hello World!",
|
||||
]
|
||||
|
||||
|
||||
def test_no_escaping(app, client):
|
||||
text = "<p>Hello World!"
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.render_template(
|
||||
"non_escaping_template.txt", text=text, html=Markup(text)
|
||||
)
|
||||
|
||||
lines = client.get("/").data.splitlines()
|
||||
assert lines == [
|
||||
b"<p>Hello World!",
|
||||
b"<p>Hello World!",
|
||||
b"<p>Hello World!",
|
||||
b"<p>Hello World!",
|
||||
b"<p>Hello World!",
|
||||
b"<p>Hello World!",
|
||||
b"<p>Hello World!",
|
||||
b"<p>Hello World!",
|
||||
]
|
||||
|
||||
|
||||
def test_escaping_without_template_filename(app, client, req_ctx):
|
||||
assert flask.render_template_string("{{ foo }}", foo="<test>") == "<test>"
|
||||
assert flask.render_template("mail.txt", foo="<test>") == "<test> Mail"
|
||||
|
||||
|
||||
def test_macros(app, req_ctx):
|
||||
macro = flask.get_template_attribute("_macro.html", "hello")
|
||||
assert macro("World") == "Hello World!"
|
||||
|
||||
|
||||
def test_template_filter(app):
|
||||
@app.template_filter()
|
||||
def my_reverse(s):
|
||||
return s[::-1]
|
||||
|
||||
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):
|
||||
def my_reverse(s):
|
||||
return s[::-1]
|
||||
|
||||
app.add_template_filter(my_reverse)
|
||||
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):
|
||||
@app.template_filter("strrev")
|
||||
def my_reverse(s):
|
||||
return s[::-1]
|
||||
|
||||
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):
|
||||
def my_reverse(s):
|
||||
return s[::-1]
|
||||
|
||||
app.add_template_filter(my_reverse, "strrev")
|
||||
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):
|
||||
@app.template_filter()
|
||||
def super_reverse(s):
|
||||
return s[::-1]
|
||||
|
||||
@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_template(app, client):
|
||||
def super_reverse(s):
|
||||
return s[::-1]
|
||||
|
||||
app.add_template_filter(super_reverse)
|
||||
|
||||
@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):
|
||||
@app.template_filter("super_reverse")
|
||||
def my_reverse(s):
|
||||
return s[::-1]
|
||||
|
||||
@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):
|
||||
def my_reverse(s):
|
||||
return s[::-1]
|
||||
|
||||
app.add_template_filter(my_reverse, "super_reverse")
|
||||
|
||||
@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):
|
||||
@app.template_test()
|
||||
def boolean(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
assert "boolean" in app.jinja_env.tests.keys()
|
||||
assert app.jinja_env.tests["boolean"] == boolean
|
||||
assert app.jinja_env.tests["boolean"](False)
|
||||
|
||||
|
||||
def test_add_template_test(app):
|
||||
def boolean(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
app.add_template_test(boolean)
|
||||
assert "boolean" in app.jinja_env.tests.keys()
|
||||
assert app.jinja_env.tests["boolean"] == boolean
|
||||
assert app.jinja_env.tests["boolean"](False)
|
||||
|
||||
|
||||
def test_template_test_with_name(app):
|
||||
@app.template_test("boolean")
|
||||
def is_boolean(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
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):
|
||||
def is_boolean(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
app.add_template_test(is_boolean, "boolean")
|
||||
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):
|
||||
@app.template_test()
|
||||
def boolean(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
@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_template(app, client):
|
||||
def boolean(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
app.add_template_test(boolean)
|
||||
|
||||
@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):
|
||||
@app.template_test("boolean")
|
||||
def is_boolean(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
@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):
|
||||
def is_boolean(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
app.add_template_test(is_boolean, "boolean")
|
||||
|
||||
@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_global(app, app_ctx):
|
||||
@app.template_global()
|
||||
def get_stuff():
|
||||
return 42
|
||||
|
||||
assert "get_stuff" in app.jinja_env.globals.keys()
|
||||
assert app.jinja_env.globals["get_stuff"] == get_stuff
|
||||
assert app.jinja_env.globals["get_stuff"](), 42
|
||||
|
||||
rv = flask.render_template_string("{{ get_stuff() }}")
|
||||
assert rv == "42"
|
||||
|
||||
|
||||
def test_custom_template_loader(client):
|
||||
class MyFlask(flask.Flask):
|
||||
def create_global_jinja_loader(self):
|
||||
from jinja2 import DictLoader
|
||||
|
||||
return DictLoader({"index.html": "Hello Custom World!"})
|
||||
|
||||
app = MyFlask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.render_template("index.html")
|
||||
|
||||
c = app.test_client()
|
||||
rv = c.get("/")
|
||||
assert rv.data == b"Hello Custom World!"
|
||||
|
||||
|
||||
def test_iterable_loader(app, client):
|
||||
@app.context_processor
|
||||
def context_processor():
|
||||
return {"whiskey": "Jameson"}
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.render_template(
|
||||
[
|
||||
"no_template.xml", # should skip this one
|
||||
"simple_template.html", # should render this
|
||||
"context_template.html",
|
||||
],
|
||||
value=23,
|
||||
)
|
||||
|
||||
rv = client.get("/")
|
||||
assert rv.data == b"<h1>Jameson</h1>"
|
||||
|
||||
|
||||
def test_templates_auto_reload(app):
|
||||
# debug is False, config option is None
|
||||
assert app.debug is False
|
||||
assert app.config["TEMPLATES_AUTO_RELOAD"] is None
|
||||
assert app.jinja_env.auto_reload is False
|
||||
# debug is False, config option is False
|
||||
app = flask.Flask(__name__)
|
||||
app.config["TEMPLATES_AUTO_RELOAD"] = False
|
||||
assert app.debug is False
|
||||
assert app.jinja_env.auto_reload is False
|
||||
# debug is False, config option is True
|
||||
app = flask.Flask(__name__)
|
||||
app.config["TEMPLATES_AUTO_RELOAD"] = True
|
||||
assert app.debug is False
|
||||
assert app.jinja_env.auto_reload is True
|
||||
# debug is True, config option is None
|
||||
app = flask.Flask(__name__)
|
||||
app.config["DEBUG"] = True
|
||||
assert app.config["TEMPLATES_AUTO_RELOAD"] is None
|
||||
assert app.jinja_env.auto_reload is True
|
||||
# debug is True, config option is False
|
||||
app = flask.Flask(__name__)
|
||||
app.config["DEBUG"] = True
|
||||
app.config["TEMPLATES_AUTO_RELOAD"] = False
|
||||
assert app.jinja_env.auto_reload is False
|
||||
# debug is True, config option is True
|
||||
app = flask.Flask(__name__)
|
||||
app.config["DEBUG"] = True
|
||||
app.config["TEMPLATES_AUTO_RELOAD"] = True
|
||||
assert app.jinja_env.auto_reload is True
|
||||
|
||||
|
||||
def test_templates_auto_reload_debug_run(app, monkeypatch):
|
||||
def run_simple_mock(*args, **kwargs):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock)
|
||||
|
||||
app.run()
|
||||
assert not app.jinja_env.auto_reload
|
||||
|
||||
app.run(debug=True)
|
||||
assert app.jinja_env.auto_reload
|
||||
|
||||
|
||||
def test_template_loader_debugging(test_apps, monkeypatch):
|
||||
from blueprintapp import app
|
||||
|
||||
called = []
|
||||
|
||||
class _TestHandler(logging.Handler):
|
||||
def handle(self, record):
|
||||
called.append(True)
|
||||
text = str(record.msg)
|
||||
assert "1: trying loader of application 'blueprintapp'" in text
|
||||
assert (
|
||||
"2: trying loader of blueprint 'admin' (blueprintapp.apps.admin)"
|
||||
) in text
|
||||
assert (
|
||||
"trying loader of blueprint 'frontend' (blueprintapp.apps.frontend)"
|
||||
) in text
|
||||
assert "Error: the template could not be found" in text
|
||||
assert (
|
||||
"looked up from an endpoint that belongs to the blueprint 'frontend'"
|
||||
) in text
|
||||
assert "See https://flask.palletsprojects.com/blueprints/#templates" in text
|
||||
|
||||
with app.test_client() as c:
|
||||
monkeypatch.setitem(app.config, "EXPLAIN_TEMPLATE_LOADING", True)
|
||||
monkeypatch.setattr(
|
||||
logging.getLogger("blueprintapp"), "handlers", [_TestHandler()]
|
||||
)
|
||||
|
||||
with pytest.raises(TemplateNotFound) as excinfo:
|
||||
c.get("/missing")
|
||||
|
||||
assert "missing_template.html" in str(excinfo.value)
|
||||
|
||||
assert len(called) == 1
|
||||
|
||||
|
||||
def test_custom_jinja_env():
|
||||
class CustomEnvironment(flask.templating.Environment):
|
||||
pass
|
||||
|
||||
class CustomFlask(flask.Flask):
|
||||
jinja_environment = CustomEnvironment
|
||||
|
||||
app = CustomFlask(__name__)
|
||||
assert isinstance(app.jinja_env, CustomEnvironment)
|
||||
|
|
@ -1,396 +0,0 @@
|
|||
import importlib.metadata
|
||||
|
||||
import click
|
||||
import pytest
|
||||
|
||||
import flask
|
||||
from flask import appcontext_popped
|
||||
from flask.cli import ScriptInfo
|
||||
from flask.globals import _cv_request
|
||||
from flask.json import jsonify
|
||||
from flask.testing import EnvironBuilder
|
||||
from flask.testing import FlaskCliRunner
|
||||
|
||||
|
||||
def test_environ_defaults_from_config(app, client):
|
||||
app.config["SERVER_NAME"] = "example.com:1234"
|
||||
app.config["APPLICATION_ROOT"] = "/foo"
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.request.url
|
||||
|
||||
ctx = app.test_request_context()
|
||||
assert ctx.request.url == "http://example.com:1234/foo/"
|
||||
|
||||
rv = client.get("/")
|
||||
assert rv.data == b"http://example.com:1234/foo/"
|
||||
|
||||
|
||||
def test_environ_defaults(app, client, app_ctx, req_ctx):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.request.url
|
||||
|
||||
ctx = app.test_request_context()
|
||||
assert ctx.request.url == "http://localhost/"
|
||||
with client:
|
||||
rv = client.get("/")
|
||||
assert rv.data == b"http://localhost/"
|
||||
|
||||
|
||||
def test_environ_base_default(app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
flask.g.remote_addr = flask.request.remote_addr
|
||||
flask.g.user_agent = flask.request.user_agent.string
|
||||
return ""
|
||||
|
||||
with client:
|
||||
client.get("/")
|
||||
assert flask.g.remote_addr == "127.0.0.1"
|
||||
assert flask.g.user_agent == (
|
||||
f"Werkzeug/{importlib.metadata.version('werkzeug')}"
|
||||
)
|
||||
|
||||
|
||||
def test_environ_base_modified(app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
flask.g.remote_addr = flask.request.remote_addr
|
||||
flask.g.user_agent = flask.request.user_agent.string
|
||||
return ""
|
||||
|
||||
client.environ_base["REMOTE_ADDR"] = "192.168.0.22"
|
||||
client.environ_base["HTTP_USER_AGENT"] = "Foo"
|
||||
|
||||
with client:
|
||||
client.get("/")
|
||||
assert flask.g.remote_addr == "192.168.0.22"
|
||||
assert flask.g.user_agent == "Foo"
|
||||
|
||||
|
||||
def test_client_open_environ(app, client, request):
|
||||
@app.route("/index")
|
||||
def index():
|
||||
return flask.request.remote_addr
|
||||
|
||||
builder = EnvironBuilder(app, path="/index", method="GET")
|
||||
request.addfinalizer(builder.close)
|
||||
|
||||
rv = client.open(builder)
|
||||
assert rv.data == b"127.0.0.1"
|
||||
|
||||
environ = builder.get_environ()
|
||||
client.environ_base["REMOTE_ADDR"] = "127.0.0.2"
|
||||
rv = client.open(environ)
|
||||
assert rv.data == b"127.0.0.2"
|
||||
|
||||
|
||||
def test_specify_url_scheme(app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return flask.request.url
|
||||
|
||||
ctx = app.test_request_context(url_scheme="https")
|
||||
assert ctx.request.url == "https://localhost/"
|
||||
|
||||
rv = client.get("/", url_scheme="https")
|
||||
assert rv.data == b"https://localhost/"
|
||||
|
||||
|
||||
def test_path_is_url(app):
|
||||
eb = EnvironBuilder(app, "https://example.com/")
|
||||
assert eb.url_scheme == "https"
|
||||
assert eb.host == "example.com"
|
||||
assert eb.script_root == ""
|
||||
assert eb.path == "/"
|
||||
|
||||
|
||||
def test_environbuilder_json_dumps(app):
|
||||
"""EnvironBuilder.json_dumps() takes settings from the app."""
|
||||
app.json.ensure_ascii = False
|
||||
eb = EnvironBuilder(app, json="\u20ac")
|
||||
assert eb.input_stream.read().decode("utf8") == '"\u20ac"'
|
||||
|
||||
|
||||
def test_blueprint_with_subdomain():
|
||||
app = flask.Flask(__name__, subdomain_matching=True)
|
||||
app.config["SERVER_NAME"] = "example.com:1234"
|
||||
app.config["APPLICATION_ROOT"] = "/foo"
|
||||
client = app.test_client()
|
||||
|
||||
bp = flask.Blueprint("company", __name__, subdomain="xxx")
|
||||
|
||||
@bp.route("/")
|
||||
def index():
|
||||
return flask.request.url
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
ctx = app.test_request_context("/", subdomain="xxx")
|
||||
assert ctx.request.url == "http://xxx.example.com:1234/foo/"
|
||||
|
||||
with ctx:
|
||||
assert ctx.request.blueprint == bp.name
|
||||
|
||||
rv = client.get("/", subdomain="xxx")
|
||||
assert rv.data == b"http://xxx.example.com:1234/foo/"
|
||||
|
||||
|
||||
def test_redirect_keep_session(app, client, app_ctx):
|
||||
@app.route("/", methods=["GET", "POST"])
|
||||
def index():
|
||||
if flask.request.method == "POST":
|
||||
return flask.redirect("/getsession")
|
||||
flask.session["data"] = "foo"
|
||||
return "index"
|
||||
|
||||
@app.route("/getsession")
|
||||
def get_session():
|
||||
return flask.session.get("data", "<missing>")
|
||||
|
||||
with client:
|
||||
rv = client.get("/getsession")
|
||||
assert rv.data == b"<missing>"
|
||||
|
||||
rv = client.get("/")
|
||||
assert rv.data == b"index"
|
||||
assert flask.session.get("data") == "foo"
|
||||
|
||||
rv = client.post("/", data={}, follow_redirects=True)
|
||||
assert rv.data == b"foo"
|
||||
assert flask.session.get("data") == "foo"
|
||||
|
||||
rv = client.get("/getsession")
|
||||
assert rv.data == b"foo"
|
||||
|
||||
|
||||
def test_session_transactions(app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return str(flask.session["foo"])
|
||||
|
||||
with client:
|
||||
with client.session_transaction() as sess:
|
||||
assert len(sess) == 0
|
||||
sess["foo"] = [42]
|
||||
assert len(sess) == 1
|
||||
rv = client.get("/")
|
||||
assert rv.data == b"[42]"
|
||||
with client.session_transaction() as sess:
|
||||
assert len(sess) == 1
|
||||
assert sess["foo"] == [42]
|
||||
|
||||
|
||||
def test_session_transactions_no_null_sessions():
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
with app.test_client() as c:
|
||||
with pytest.raises(RuntimeError) as e:
|
||||
with c.session_transaction():
|
||||
pass
|
||||
assert "Session backend did not open a session" in str(e.value)
|
||||
|
||||
|
||||
def test_session_transactions_keep_context(app, client, req_ctx):
|
||||
client.get("/")
|
||||
req = flask.request._get_current_object()
|
||||
assert req is not None
|
||||
with client.session_transaction():
|
||||
assert req is flask.request._get_current_object()
|
||||
|
||||
|
||||
def test_session_transaction_needs_cookies(app):
|
||||
c = app.test_client(use_cookies=False)
|
||||
|
||||
with pytest.raises(TypeError, match="Cookies are disabled."):
|
||||
with c.session_transaction():
|
||||
pass
|
||||
|
||||
|
||||
def test_test_client_context_binding(app, client):
|
||||
app.testing = False
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
flask.g.value = 42
|
||||
return "Hello World!"
|
||||
|
||||
@app.route("/other")
|
||||
def other():
|
||||
raise ZeroDivisionError
|
||||
|
||||
with client:
|
||||
resp = client.get("/")
|
||||
assert flask.g.value == 42
|
||||
assert resp.data == b"Hello World!"
|
||||
assert resp.status_code == 200
|
||||
|
||||
with client:
|
||||
resp = client.get("/other")
|
||||
assert not hasattr(flask.g, "value")
|
||||
assert b"Internal Server Error" in resp.data
|
||||
assert resp.status_code == 500
|
||||
flask.g.value = 23
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
flask.g.value # noqa: B018
|
||||
|
||||
|
||||
def test_reuse_client(client):
|
||||
c = client
|
||||
|
||||
with c:
|
||||
assert client.get("/").status_code == 404
|
||||
|
||||
with c:
|
||||
assert client.get("/").status_code == 404
|
||||
|
||||
|
||||
def test_full_url_request(app, client):
|
||||
@app.route("/action", methods=["POST"])
|
||||
def action():
|
||||
return "x"
|
||||
|
||||
with client:
|
||||
rv = client.post("http://domain.com/action?vodka=42", data={"gin": 43})
|
||||
assert rv.status_code == 200
|
||||
assert "gin" in flask.request.form
|
||||
assert "vodka" in flask.request.args
|
||||
|
||||
|
||||
def test_json_request_and_response(app, client):
|
||||
@app.route("/echo", methods=["POST"])
|
||||
def echo():
|
||||
return jsonify(flask.request.get_json())
|
||||
|
||||
with client:
|
||||
json_data = {"drink": {"gin": 1, "tonic": True}, "price": 10}
|
||||
rv = client.post("/echo", json=json_data)
|
||||
|
||||
# Request should be in JSON
|
||||
assert flask.request.is_json
|
||||
assert flask.request.get_json() == json_data
|
||||
|
||||
# Response should be in JSON
|
||||
assert rv.status_code == 200
|
||||
assert rv.is_json
|
||||
assert rv.get_json() == json_data
|
||||
|
||||
|
||||
def test_client_json_no_app_context(app, client):
|
||||
@app.route("/hello", methods=["POST"])
|
||||
def hello():
|
||||
return f"Hello, {flask.request.json['name']}!"
|
||||
|
||||
class Namespace:
|
||||
count = 0
|
||||
|
||||
def add(self, app):
|
||||
self.count += 1
|
||||
|
||||
ns = Namespace()
|
||||
|
||||
with appcontext_popped.connected_to(ns.add, app):
|
||||
rv = client.post("/hello", json={"name": "Flask"})
|
||||
|
||||
assert rv.get_data(as_text=True) == "Hello, Flask!"
|
||||
assert ns.count == 1
|
||||
|
||||
|
||||
def test_subdomain():
|
||||
app = flask.Flask(__name__, subdomain_matching=True)
|
||||
app.config["SERVER_NAME"] = "example.com"
|
||||
client = app.test_client()
|
||||
|
||||
@app.route("/", subdomain="<company_id>")
|
||||
def view(company_id):
|
||||
return company_id
|
||||
|
||||
with app.test_request_context():
|
||||
url = flask.url_for("view", company_id="xxx")
|
||||
|
||||
with client:
|
||||
response = client.get(url)
|
||||
|
||||
assert 200 == response.status_code
|
||||
assert b"xxx" == response.data
|
||||
|
||||
|
||||
def test_nosubdomain(app, client):
|
||||
app.config["SERVER_NAME"] = "example.com"
|
||||
|
||||
@app.route("/<company_id>")
|
||||
def view(company_id):
|
||||
return company_id
|
||||
|
||||
with app.test_request_context():
|
||||
url = flask.url_for("view", company_id="xxx")
|
||||
|
||||
with client:
|
||||
response = client.get(url)
|
||||
|
||||
assert 200 == response.status_code
|
||||
assert b"xxx" == response.data
|
||||
|
||||
|
||||
def test_cli_runner_class(app):
|
||||
runner = app.test_cli_runner()
|
||||
assert isinstance(runner, FlaskCliRunner)
|
||||
|
||||
class SubRunner(FlaskCliRunner):
|
||||
pass
|
||||
|
||||
app.test_cli_runner_class = SubRunner
|
||||
runner = app.test_cli_runner()
|
||||
assert isinstance(runner, SubRunner)
|
||||
|
||||
|
||||
def test_cli_invoke(app):
|
||||
@app.cli.command("hello")
|
||||
def hello_command():
|
||||
click.echo("Hello, World!")
|
||||
|
||||
runner = app.test_cli_runner()
|
||||
# invoke with command name
|
||||
result = runner.invoke(args=["hello"])
|
||||
assert "Hello" in result.output
|
||||
# invoke with command object
|
||||
result = runner.invoke(hello_command)
|
||||
assert "Hello" in result.output
|
||||
|
||||
|
||||
def test_cli_custom_obj(app):
|
||||
class NS:
|
||||
called = False
|
||||
|
||||
def create_app():
|
||||
NS.called = True
|
||||
return app
|
||||
|
||||
@app.cli.command("hello")
|
||||
def hello_command():
|
||||
click.echo("Hello, World!")
|
||||
|
||||
script_info = ScriptInfo(create_app=create_app)
|
||||
runner = app.test_cli_runner()
|
||||
runner.invoke(hello_command, obj=script_info)
|
||||
assert NS.called
|
||||
|
||||
|
||||
def test_client_pop_all_preserved(app, req_ctx, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
# stream_with_context pushes a third context, preserved by response
|
||||
return flask.stream_with_context("hello")
|
||||
|
||||
# req_ctx fixture pushed an initial context
|
||||
with client:
|
||||
# request pushes a second request context, preserved by client
|
||||
rv = client.get("/")
|
||||
|
||||
# close the response, releasing the context held by stream_with_context
|
||||
rv.close()
|
||||
# only req_ctx fixture should still be pushed
|
||||
assert _cv_request.get(None) is req_ctx
|
||||
|
|
@ -1,295 +0,0 @@
|
|||
import pytest
|
||||
from werkzeug.exceptions import Forbidden
|
||||
from werkzeug.exceptions import HTTPException
|
||||
from werkzeug.exceptions import InternalServerError
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
def test_error_handler_no_match(app, client):
|
||||
class CustomException(Exception):
|
||||
pass
|
||||
|
||||
@app.errorhandler(CustomException)
|
||||
def custom_exception_handler(e):
|
||||
assert isinstance(e, CustomException)
|
||||
return "custom"
|
||||
|
||||
with pytest.raises(TypeError) as exc_info:
|
||||
app.register_error_handler(CustomException(), None)
|
||||
|
||||
assert "CustomException() is an instance, not a class." in str(exc_info.value)
|
||||
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
app.register_error_handler(list, None)
|
||||
|
||||
assert "'list' is not a subclass of Exception." in str(exc_info.value)
|
||||
|
||||
@app.errorhandler(500)
|
||||
def handle_500(e):
|
||||
assert isinstance(e, InternalServerError)
|
||||
|
||||
if e.original_exception is not None:
|
||||
return f"wrapped {type(e.original_exception).__name__}"
|
||||
|
||||
return "direct"
|
||||
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
app.register_error_handler(999, None)
|
||||
|
||||
assert "Use a subclass of HTTPException" in str(exc_info.value)
|
||||
|
||||
@app.route("/custom")
|
||||
def custom_test():
|
||||
raise CustomException()
|
||||
|
||||
@app.route("/keyerror")
|
||||
def key_error():
|
||||
raise KeyError()
|
||||
|
||||
@app.route("/abort")
|
||||
def do_abort():
|
||||
flask.abort(500)
|
||||
|
||||
app.testing = False
|
||||
assert client.get("/custom").data == b"custom"
|
||||
assert client.get("/keyerror").data == b"wrapped KeyError"
|
||||
assert client.get("/abort").data == b"direct"
|
||||
|
||||
|
||||
def test_error_handler_subclass(app):
|
||||
class ParentException(Exception):
|
||||
pass
|
||||
|
||||
class ChildExceptionUnregistered(ParentException):
|
||||
pass
|
||||
|
||||
class ChildExceptionRegistered(ParentException):
|
||||
pass
|
||||
|
||||
@app.errorhandler(ParentException)
|
||||
def parent_exception_handler(e):
|
||||
assert isinstance(e, ParentException)
|
||||
return "parent"
|
||||
|
||||
@app.errorhandler(ChildExceptionRegistered)
|
||||
def child_exception_handler(e):
|
||||
assert isinstance(e, ChildExceptionRegistered)
|
||||
return "child-registered"
|
||||
|
||||
@app.route("/parent")
|
||||
def parent_test():
|
||||
raise ParentException()
|
||||
|
||||
@app.route("/child-unregistered")
|
||||
def unregistered_test():
|
||||
raise ChildExceptionUnregistered()
|
||||
|
||||
@app.route("/child-registered")
|
||||
def registered_test():
|
||||
raise ChildExceptionRegistered()
|
||||
|
||||
c = app.test_client()
|
||||
|
||||
assert c.get("/parent").data == b"parent"
|
||||
assert c.get("/child-unregistered").data == b"parent"
|
||||
assert c.get("/child-registered").data == b"child-registered"
|
||||
|
||||
|
||||
def test_error_handler_http_subclass(app):
|
||||
class ForbiddenSubclassRegistered(Forbidden):
|
||||
pass
|
||||
|
||||
class ForbiddenSubclassUnregistered(Forbidden):
|
||||
pass
|
||||
|
||||
@app.errorhandler(403)
|
||||
def code_exception_handler(e):
|
||||
assert isinstance(e, Forbidden)
|
||||
return "forbidden"
|
||||
|
||||
@app.errorhandler(ForbiddenSubclassRegistered)
|
||||
def subclass_exception_handler(e):
|
||||
assert isinstance(e, ForbiddenSubclassRegistered)
|
||||
return "forbidden-registered"
|
||||
|
||||
@app.route("/forbidden")
|
||||
def forbidden_test():
|
||||
raise Forbidden()
|
||||
|
||||
@app.route("/forbidden-registered")
|
||||
def registered_test():
|
||||
raise ForbiddenSubclassRegistered()
|
||||
|
||||
@app.route("/forbidden-unregistered")
|
||||
def unregistered_test():
|
||||
raise ForbiddenSubclassUnregistered()
|
||||
|
||||
c = app.test_client()
|
||||
|
||||
assert c.get("/forbidden").data == b"forbidden"
|
||||
assert c.get("/forbidden-unregistered").data == b"forbidden"
|
||||
assert c.get("/forbidden-registered").data == b"forbidden-registered"
|
||||
|
||||
|
||||
def test_error_handler_blueprint(app):
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
|
||||
@bp.errorhandler(500)
|
||||
def bp_exception_handler(e):
|
||||
return "bp-error"
|
||||
|
||||
@bp.route("/error")
|
||||
def bp_test():
|
||||
raise InternalServerError()
|
||||
|
||||
@app.errorhandler(500)
|
||||
def app_exception_handler(e):
|
||||
return "app-error"
|
||||
|
||||
@app.route("/error")
|
||||
def app_test():
|
||||
raise InternalServerError()
|
||||
|
||||
app.register_blueprint(bp, url_prefix="/bp")
|
||||
|
||||
c = app.test_client()
|
||||
|
||||
assert c.get("/error").data == b"app-error"
|
||||
assert c.get("/bp/error").data == b"bp-error"
|
||||
|
||||
|
||||
def test_default_error_handler():
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
|
||||
@bp.errorhandler(HTTPException)
|
||||
def bp_exception_handler(e):
|
||||
assert isinstance(e, HTTPException)
|
||||
assert isinstance(e, NotFound)
|
||||
return "bp-default"
|
||||
|
||||
@bp.errorhandler(Forbidden)
|
||||
def bp_forbidden_handler(e):
|
||||
assert isinstance(e, Forbidden)
|
||||
return "bp-forbidden"
|
||||
|
||||
@bp.route("/undefined")
|
||||
def bp_registered_test():
|
||||
raise NotFound()
|
||||
|
||||
@bp.route("/forbidden")
|
||||
def bp_forbidden_test():
|
||||
raise Forbidden()
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
@app.errorhandler(HTTPException)
|
||||
def catchall_exception_handler(e):
|
||||
assert isinstance(e, HTTPException)
|
||||
assert isinstance(e, NotFound)
|
||||
return "default"
|
||||
|
||||
@app.errorhandler(Forbidden)
|
||||
def catchall_forbidden_handler(e):
|
||||
assert isinstance(e, Forbidden)
|
||||
return "forbidden"
|
||||
|
||||
@app.route("/forbidden")
|
||||
def forbidden():
|
||||
raise Forbidden()
|
||||
|
||||
@app.route("/slash/")
|
||||
def slash():
|
||||
return "slash"
|
||||
|
||||
app.register_blueprint(bp, url_prefix="/bp")
|
||||
|
||||
c = app.test_client()
|
||||
assert c.get("/bp/undefined").data == b"bp-default"
|
||||
assert c.get("/bp/forbidden").data == b"bp-forbidden"
|
||||
assert c.get("/undefined").data == b"default"
|
||||
assert c.get("/forbidden").data == b"forbidden"
|
||||
# Don't handle RequestRedirect raised when adding slash.
|
||||
assert c.get("/slash", follow_redirects=True).data == b"slash"
|
||||
|
||||
|
||||
class TestGenericHandlers:
|
||||
"""Test how very generic handlers are dispatched to."""
|
||||
|
||||
class Custom(Exception):
|
||||
pass
|
||||
|
||||
@pytest.fixture()
|
||||
def app(self, app):
|
||||
@app.route("/custom")
|
||||
def do_custom():
|
||||
raise self.Custom()
|
||||
|
||||
@app.route("/error")
|
||||
def do_error():
|
||||
raise KeyError()
|
||||
|
||||
@app.route("/abort")
|
||||
def do_abort():
|
||||
flask.abort(500)
|
||||
|
||||
@app.route("/raise")
|
||||
def do_raise():
|
||||
raise InternalServerError()
|
||||
|
||||
app.config["PROPAGATE_EXCEPTIONS"] = False
|
||||
return app
|
||||
|
||||
def report_error(self, e):
|
||||
original = getattr(e, "original_exception", None)
|
||||
|
||||
if original is not None:
|
||||
return f"wrapped {type(original).__name__}"
|
||||
|
||||
return f"direct {type(e).__name__}"
|
||||
|
||||
@pytest.mark.parametrize("to_handle", (InternalServerError, 500))
|
||||
def test_handle_class_or_code(self, app, client, to_handle):
|
||||
"""``InternalServerError`` and ``500`` are aliases, they should
|
||||
have the same behavior. Both should only receive
|
||||
``InternalServerError``, which might wrap another error.
|
||||
"""
|
||||
|
||||
@app.errorhandler(to_handle)
|
||||
def handle_500(e):
|
||||
assert isinstance(e, InternalServerError)
|
||||
return self.report_error(e)
|
||||
|
||||
assert client.get("/custom").data == b"wrapped Custom"
|
||||
assert client.get("/error").data == b"wrapped KeyError"
|
||||
assert client.get("/abort").data == b"direct InternalServerError"
|
||||
assert client.get("/raise").data == b"direct InternalServerError"
|
||||
|
||||
def test_handle_generic_http(self, app, client):
|
||||
"""``HTTPException`` should only receive ``HTTPException``
|
||||
subclasses. It will receive ``404`` routing exceptions.
|
||||
"""
|
||||
|
||||
@app.errorhandler(HTTPException)
|
||||
def handle_http(e):
|
||||
assert isinstance(e, HTTPException)
|
||||
return str(e.code)
|
||||
|
||||
assert client.get("/error").data == b"500"
|
||||
assert client.get("/abort").data == b"500"
|
||||
assert client.get("/not-found").data == b"404"
|
||||
|
||||
def test_handle_generic(self, app, client):
|
||||
"""Generic ``Exception`` will handle all exceptions directly,
|
||||
including ``HTTPExceptions``.
|
||||
"""
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def handle_exception(e):
|
||||
return self.report_error(e)
|
||||
|
||||
assert client.get("/custom").data == b"direct Custom"
|
||||
assert client.get("/error").data == b"direct KeyError"
|
||||
assert client.get("/abort").data == b"direct InternalServerError"
|
||||
assert client.get("/not-found").data == b"direct NotFound"
|
||||
|
|
@ -1,260 +0,0 @@
|
|||
import pytest
|
||||
from werkzeug.http import parse_set_header
|
||||
|
||||
import flask.views
|
||||
|
||||
|
||||
def common_test(app):
|
||||
c = app.test_client()
|
||||
|
||||
assert c.get("/").data == b"GET"
|
||||
assert c.post("/").data == b"POST"
|
||||
assert c.put("/").status_code == 405
|
||||
meths = parse_set_header(c.open("/", method="OPTIONS").headers["Allow"])
|
||||
assert sorted(meths) == ["GET", "HEAD", "OPTIONS", "POST"]
|
||||
|
||||
|
||||
def test_basic_view(app):
|
||||
class Index(flask.views.View):
|
||||
methods = ["GET", "POST"]
|
||||
|
||||
def dispatch_request(self):
|
||||
return flask.request.method
|
||||
|
||||
app.add_url_rule("/", view_func=Index.as_view("index"))
|
||||
common_test(app)
|
||||
|
||||
|
||||
def test_method_based_view(app):
|
||||
class Index(flask.views.MethodView):
|
||||
def get(self):
|
||||
return "GET"
|
||||
|
||||
def post(self):
|
||||
return "POST"
|
||||
|
||||
app.add_url_rule("/", view_func=Index.as_view("index"))
|
||||
|
||||
common_test(app)
|
||||
|
||||
|
||||
def test_view_patching(app):
|
||||
class Index(flask.views.MethodView):
|
||||
def get(self):
|
||||
raise ZeroDivisionError
|
||||
|
||||
def post(self):
|
||||
raise ZeroDivisionError
|
||||
|
||||
class Other(Index):
|
||||
def get(self):
|
||||
return "GET"
|
||||
|
||||
def post(self):
|
||||
return "POST"
|
||||
|
||||
view = Index.as_view("index")
|
||||
view.view_class = Other
|
||||
app.add_url_rule("/", view_func=view)
|
||||
common_test(app)
|
||||
|
||||
|
||||
def test_view_inheritance(app, client):
|
||||
class Index(flask.views.MethodView):
|
||||
def get(self):
|
||||
return "GET"
|
||||
|
||||
def post(self):
|
||||
return "POST"
|
||||
|
||||
class BetterIndex(Index):
|
||||
def delete(self):
|
||||
return "DELETE"
|
||||
|
||||
app.add_url_rule("/", view_func=BetterIndex.as_view("index"))
|
||||
|
||||
meths = parse_set_header(client.open("/", method="OPTIONS").headers["Allow"])
|
||||
assert sorted(meths) == ["DELETE", "GET", "HEAD", "OPTIONS", "POST"]
|
||||
|
||||
|
||||
def test_view_decorators(app, client):
|
||||
def add_x_parachute(f):
|
||||
def new_function(*args, **kwargs):
|
||||
resp = flask.make_response(f(*args, **kwargs))
|
||||
resp.headers["X-Parachute"] = "awesome"
|
||||
return resp
|
||||
|
||||
return new_function
|
||||
|
||||
class Index(flask.views.View):
|
||||
decorators = [add_x_parachute]
|
||||
|
||||
def dispatch_request(self):
|
||||
return "Awesome"
|
||||
|
||||
app.add_url_rule("/", view_func=Index.as_view("index"))
|
||||
rv = client.get("/")
|
||||
assert rv.headers["X-Parachute"] == "awesome"
|
||||
assert rv.data == b"Awesome"
|
||||
|
||||
|
||||
def test_view_provide_automatic_options_attr():
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
class Index1(flask.views.View):
|
||||
provide_automatic_options = False
|
||||
|
||||
def dispatch_request(self):
|
||||
return "Hello World!"
|
||||
|
||||
app.add_url_rule("/", view_func=Index1.as_view("index"))
|
||||
c = app.test_client()
|
||||
rv = c.open("/", method="OPTIONS")
|
||||
assert rv.status_code == 405
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
class Index2(flask.views.View):
|
||||
methods = ["OPTIONS"]
|
||||
provide_automatic_options = True
|
||||
|
||||
def dispatch_request(self):
|
||||
return "Hello World!"
|
||||
|
||||
app.add_url_rule("/", view_func=Index2.as_view("index"))
|
||||
c = app.test_client()
|
||||
rv = c.open("/", method="OPTIONS")
|
||||
assert sorted(rv.allow) == ["OPTIONS"]
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
class Index3(flask.views.View):
|
||||
def dispatch_request(self):
|
||||
return "Hello World!"
|
||||
|
||||
app.add_url_rule("/", view_func=Index3.as_view("index"))
|
||||
c = app.test_client()
|
||||
rv = c.open("/", method="OPTIONS")
|
||||
assert "OPTIONS" in rv.allow
|
||||
|
||||
|
||||
def test_implicit_head(app, client):
|
||||
class Index(flask.views.MethodView):
|
||||
def get(self):
|
||||
return flask.Response("Blub", headers={"X-Method": flask.request.method})
|
||||
|
||||
app.add_url_rule("/", view_func=Index.as_view("index"))
|
||||
rv = client.get("/")
|
||||
assert rv.data == b"Blub"
|
||||
assert rv.headers["X-Method"] == "GET"
|
||||
rv = client.head("/")
|
||||
assert rv.data == b""
|
||||
assert rv.headers["X-Method"] == "HEAD"
|
||||
|
||||
|
||||
def test_explicit_head(app, client):
|
||||
class Index(flask.views.MethodView):
|
||||
def get(self):
|
||||
return "GET"
|
||||
|
||||
def head(self):
|
||||
return flask.Response("", headers={"X-Method": "HEAD"})
|
||||
|
||||
app.add_url_rule("/", view_func=Index.as_view("index"))
|
||||
rv = client.get("/")
|
||||
assert rv.data == b"GET"
|
||||
rv = client.head("/")
|
||||
assert rv.data == b""
|
||||
assert rv.headers["X-Method"] == "HEAD"
|
||||
|
||||
|
||||
def test_endpoint_override(app):
|
||||
app.debug = True
|
||||
|
||||
class Index(flask.views.View):
|
||||
methods = ["GET", "POST"]
|
||||
|
||||
def dispatch_request(self):
|
||||
return flask.request.method
|
||||
|
||||
app.add_url_rule("/", view_func=Index.as_view("index"))
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
app.add_url_rule("/", view_func=Index.as_view("index"))
|
||||
|
||||
# But these tests should still pass. We just log a warning.
|
||||
common_test(app)
|
||||
|
||||
|
||||
def test_methods_var_inheritance(app, client):
|
||||
class BaseView(flask.views.MethodView):
|
||||
methods = ["GET", "PROPFIND"]
|
||||
|
||||
class ChildView(BaseView):
|
||||
def get(self):
|
||||
return "GET"
|
||||
|
||||
def propfind(self):
|
||||
return "PROPFIND"
|
||||
|
||||
app.add_url_rule("/", view_func=ChildView.as_view("index"))
|
||||
|
||||
assert client.get("/").data == b"GET"
|
||||
assert client.open("/", method="PROPFIND").data == b"PROPFIND"
|
||||
assert ChildView.methods == {"PROPFIND", "GET"}
|
||||
|
||||
|
||||
def test_multiple_inheritance(app, client):
|
||||
class GetView(flask.views.MethodView):
|
||||
def get(self):
|
||||
return "GET"
|
||||
|
||||
class DeleteView(flask.views.MethodView):
|
||||
def delete(self):
|
||||
return "DELETE"
|
||||
|
||||
class GetDeleteView(GetView, DeleteView):
|
||||
pass
|
||||
|
||||
app.add_url_rule("/", view_func=GetDeleteView.as_view("index"))
|
||||
|
||||
assert client.get("/").data == b"GET"
|
||||
assert client.delete("/").data == b"DELETE"
|
||||
assert sorted(GetDeleteView.methods) == ["DELETE", "GET"]
|
||||
|
||||
|
||||
def test_remove_method_from_parent(app, client):
|
||||
class GetView(flask.views.MethodView):
|
||||
def get(self):
|
||||
return "GET"
|
||||
|
||||
class OtherView(flask.views.MethodView):
|
||||
def post(self):
|
||||
return "POST"
|
||||
|
||||
class View(GetView, OtherView):
|
||||
methods = ["GET"]
|
||||
|
||||
app.add_url_rule("/", view_func=View.as_view("index"))
|
||||
|
||||
assert client.get("/").data == b"GET"
|
||||
assert client.post("/").status_code == 405
|
||||
assert sorted(View.methods) == ["GET"]
|
||||
|
||||
|
||||
def test_init_once(app, client):
|
||||
n = 0
|
||||
|
||||
class CountInit(flask.views.View):
|
||||
init_every_request = False
|
||||
|
||||
def __init__(self):
|
||||
nonlocal n
|
||||
n += 1
|
||||
|
||||
def dispatch_request(self):
|
||||
return str(n)
|
||||
|
||||
app.add_url_rule("/", view_func=CountInit.as_view("index"))
|
||||
assert client.get("/").data == b"1"
|
||||
assert client.get("/").data == b"1"
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from flask import Flask
|
||||
from flask import Response
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.after_request
|
||||
def after_sync(response: Response) -> Response:
|
||||
return Response()
|
||||
|
||||
|
||||
@app.after_request
|
||||
async def after_async(response: Response) -> Response:
|
||||
return Response()
|
||||
|
||||
|
||||
@app.before_request
|
||||
def before_sync() -> None: ...
|
||||
|
||||
|
||||
@app.before_request
|
||||
async def before_async() -> None: ...
|
||||
|
||||
|
||||
@app.teardown_appcontext
|
||||
def teardown_sync(exc: BaseException | None) -> None: ...
|
||||
|
||||
|
||||
@app.teardown_appcontext
|
||||
async def teardown_async(exc: BaseException | None) -> None: ...
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from http import HTTPStatus
|
||||
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.errorhandler(400)
|
||||
@app.errorhandler(HTTPStatus.BAD_REQUEST)
|
||||
@app.errorhandler(BadRequest)
|
||||
def handle_400(e: BadRequest) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
@app.errorhandler(ValueError)
|
||||
def handle_custom(e: ValueError) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
@app.errorhandler(ValueError)
|
||||
def handle_accept_base(e: Exception) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
@app.errorhandler(BadRequest)
|
||||
@app.errorhandler(404)
|
||||
def handle_multiple(e: BadRequest | NotFound) -> str:
|
||||
return ""
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import typing as t
|
||||
from http import HTTPStatus
|
||||
|
||||
from flask import Flask
|
||||
from flask import jsonify
|
||||
from flask import stream_template
|
||||
from flask.templating import render_template
|
||||
from flask.views import View
|
||||
from flask.wrappers import Response
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/str")
|
||||
def hello_str() -> str:
|
||||
return "<p>Hello, World!</p>"
|
||||
|
||||
|
||||
@app.route("/bytes")
|
||||
def hello_bytes() -> bytes:
|
||||
return b"<p>Hello, World!</p>"
|
||||
|
||||
|
||||
@app.route("/json")
|
||||
def hello_json() -> Response:
|
||||
return jsonify("Hello, World!")
|
||||
|
||||
|
||||
@app.route("/json/dict")
|
||||
def hello_json_dict() -> dict[str, t.Any]:
|
||||
return {"response": "Hello, World!"}
|
||||
|
||||
|
||||
@app.route("/json/dict")
|
||||
def hello_json_list() -> list[t.Any]:
|
||||
return [{"message": "Hello"}, {"message": "World"}]
|
||||
|
||||
|
||||
class StatusJSON(t.TypedDict):
|
||||
status: str
|
||||
|
||||
|
||||
@app.route("/typed-dict")
|
||||
def typed_dict() -> StatusJSON:
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.route("/generator")
|
||||
def hello_generator() -> t.Generator[str, None, None]:
|
||||
def show() -> t.Generator[str, None, None]:
|
||||
for x in range(100):
|
||||
yield f"data:{x}\n\n"
|
||||
|
||||
return show()
|
||||
|
||||
|
||||
@app.route("/generator-expression")
|
||||
def hello_generator_expression() -> t.Iterator[bytes]:
|
||||
return (f"data:{x}\n\n".encode() for x in range(100))
|
||||
|
||||
|
||||
@app.route("/iterator")
|
||||
def hello_iterator() -> t.Iterator[str]:
|
||||
return iter([f"data:{x}\n\n" for x in range(100)])
|
||||
|
||||
|
||||
@app.route("/status")
|
||||
@app.route("/status/<int:code>")
|
||||
def tuple_status(code: int = 200) -> tuple[str, int]:
|
||||
return "hello", code
|
||||
|
||||
|
||||
@app.route("/status-enum")
|
||||
def tuple_status_enum() -> tuple[str, int]:
|
||||
return "hello", HTTPStatus.OK
|
||||
|
||||
|
||||
@app.route("/headers")
|
||||
def tuple_headers() -> tuple[str, dict[str, str]]:
|
||||
return "Hello, World!", {"Content-Type": "text/plain"}
|
||||
|
||||
|
||||
@app.route("/template")
|
||||
@app.route("/template/<name>")
|
||||
def return_template(name: str | None = None) -> str:
|
||||
return render_template("index.html", name=name)
|
||||
|
||||
|
||||
@app.route("/template")
|
||||
def return_template_stream() -> t.Iterator[str]:
|
||||
return stream_template("index.html", name="Hello")
|
||||
|
||||
|
||||
@app.route("/async")
|
||||
async def async_route() -> str:
|
||||
return "Hello"
|
||||
|
||||
|
||||
class RenderTemplateView(View):
|
||||
def __init__(self: RenderTemplateView, template_name: str) -> None:
|
||||
self.template_name = template_name
|
||||
|
||||
def dispatch_request(self: RenderTemplateView) -> str:
|
||||
return render_template(self.template_name)
|
||||
|
||||
|
||||
app.add_url_rule(
|
||||
"/about",
|
||||
view_func=RenderTemplateView.as_view("about_page", template_name="about.html"),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue