272 lines
7 KiB
Python
272 lines
7 KiB
Python
import pytest
|
|
from werkzeug.http import parse_set_header
|
|
|
|
import flask.views
|
|
from flask.testing import FlaskClient
|
|
|
|
|
|
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_disable(
|
|
app: flask.Flask, client: FlaskClient
|
|
) -> None:
|
|
"""Automatic options can be disabled by the view class attribute."""
|
|
|
|
class Index(flask.views.View):
|
|
provide_automatic_options = False
|
|
|
|
def dispatch_request(self):
|
|
return "Hello World!"
|
|
|
|
app.add_url_rule("/", view_func=Index.as_view("index"))
|
|
rv = client.options()
|
|
assert rv.status_code == 405
|
|
|
|
|
|
def test_view_provide_automatic_options_attr_enable(
|
|
app: flask.Flask, client: FlaskClient
|
|
) -> None:
|
|
"""When default automatic options is disabled in config, it can still be
|
|
enabled by the view class attribute.
|
|
"""
|
|
app.config["PROVIDE_AUTOMATIC_OPTIONS"] = False
|
|
|
|
class Index(flask.views.View):
|
|
provide_automatic_options = True
|
|
|
|
def dispatch_request(self):
|
|
return "Hello World!"
|
|
|
|
app.add_url_rule("/", view_func=Index.as_view("index"))
|
|
rv = client.options("/")
|
|
assert rv.allow == {"GET", "HEAD", "OPTIONS"}
|
|
|
|
|
|
def test_provide_automatic_options_method_disable(
|
|
app: flask.Flask, client: FlaskClient
|
|
) -> None:
|
|
"""Automatic options is ignored if the route handles options."""
|
|
|
|
class Index(flask.views.View):
|
|
methods = ["OPTIONS"]
|
|
|
|
def dispatch_request(self):
|
|
return "", {"X-Test": "test"}
|
|
|
|
app.add_url_rule("/", view_func=Index.as_view("index"))
|
|
rv = client.options()
|
|
assert rv.headers["X-Test"] == "test"
|
|
|
|
|
|
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("/other", 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"
|