1import pytest
2from werkzeug.http import parse_set_header
3
4import flask.views
5
6
7def common_test(app):
8    c = app.test_client()
9
10    assert c.get("/").data == b"GET"
11    assert c.post("/").data == b"POST"
12    assert c.put("/").status_code == 405
13    meths = parse_set_header(c.open("/", method="OPTIONS").headers["Allow"])
14    assert sorted(meths) == ["GET", "HEAD", "OPTIONS", "POST"]
15
16
17def test_basic_view(app):
18    class Index(flask.views.View):
19        methods = ["GET", "POST"]
20
21        def dispatch_request(self):
22            return flask.request.method
23
24    app.add_url_rule("/", view_func=Index.as_view("index"))
25    common_test(app)
26
27
28def test_method_based_view(app):
29    class Index(flask.views.MethodView):
30        def get(self):
31            return "GET"
32
33        def post(self):
34            return "POST"
35
36    app.add_url_rule("/", view_func=Index.as_view("index"))
37
38    common_test(app)
39
40
41def test_view_patching(app):
42    class Index(flask.views.MethodView):
43        def get(self):
44            1 // 0
45
46        def post(self):
47            1 // 0
48
49    class Other(Index):
50        def get(self):
51            return "GET"
52
53        def post(self):
54            return "POST"
55
56    view = Index.as_view("index")
57    view.view_class = Other
58    app.add_url_rule("/", view_func=view)
59    common_test(app)
60
61
62def test_view_inheritance(app, client):
63    class Index(flask.views.MethodView):
64        def get(self):
65            return "GET"
66
67        def post(self):
68            return "POST"
69
70    class BetterIndex(Index):
71        def delete(self):
72            return "DELETE"
73
74    app.add_url_rule("/", view_func=BetterIndex.as_view("index"))
75
76    meths = parse_set_header(client.open("/", method="OPTIONS").headers["Allow"])
77    assert sorted(meths) == ["DELETE", "GET", "HEAD", "OPTIONS", "POST"]
78
79
80def test_view_decorators(app, client):
81    def add_x_parachute(f):
82        def new_function(*args, **kwargs):
83            resp = flask.make_response(f(*args, **kwargs))
84            resp.headers["X-Parachute"] = "awesome"
85            return resp
86
87        return new_function
88
89    class Index(flask.views.View):
90        decorators = [add_x_parachute]
91
92        def dispatch_request(self):
93            return "Awesome"
94
95    app.add_url_rule("/", view_func=Index.as_view("index"))
96    rv = client.get("/")
97    assert rv.headers["X-Parachute"] == "awesome"
98    assert rv.data == b"Awesome"
99
100
101def test_view_provide_automatic_options_attr():
102    app = flask.Flask(__name__)
103
104    class Index1(flask.views.View):
105        provide_automatic_options = False
106
107        def dispatch_request(self):
108            return "Hello World!"
109
110    app.add_url_rule("/", view_func=Index1.as_view("index"))
111    c = app.test_client()
112    rv = c.open("/", method="OPTIONS")
113    assert rv.status_code == 405
114
115    app = flask.Flask(__name__)
116
117    class Index2(flask.views.View):
118        methods = ["OPTIONS"]
119        provide_automatic_options = True
120
121        def dispatch_request(self):
122            return "Hello World!"
123
124    app.add_url_rule("/", view_func=Index2.as_view("index"))
125    c = app.test_client()
126    rv = c.open("/", method="OPTIONS")
127    assert sorted(rv.allow) == ["OPTIONS"]
128
129    app = flask.Flask(__name__)
130
131    class Index3(flask.views.View):
132        def dispatch_request(self):
133            return "Hello World!"
134
135    app.add_url_rule("/", view_func=Index3.as_view("index"))
136    c = app.test_client()
137    rv = c.open("/", method="OPTIONS")
138    assert "OPTIONS" in rv.allow
139
140
141def test_implicit_head(app, client):
142    class Index(flask.views.MethodView):
143        def get(self):
144            return flask.Response("Blub", headers={"X-Method": flask.request.method})
145
146    app.add_url_rule("/", view_func=Index.as_view("index"))
147    rv = client.get("/")
148    assert rv.data == b"Blub"
149    assert rv.headers["X-Method"] == "GET"
150    rv = client.head("/")
151    assert rv.data == b""
152    assert rv.headers["X-Method"] == "HEAD"
153
154
155def test_explicit_head(app, client):
156    class Index(flask.views.MethodView):
157        def get(self):
158            return "GET"
159
160        def head(self):
161            return flask.Response("", headers={"X-Method": "HEAD"})
162
163    app.add_url_rule("/", view_func=Index.as_view("index"))
164    rv = client.get("/")
165    assert rv.data == b"GET"
166    rv = client.head("/")
167    assert rv.data == b""
168    assert rv.headers["X-Method"] == "HEAD"
169
170
171def test_endpoint_override(app):
172    app.debug = True
173
174    class Index(flask.views.View):
175        methods = ["GET", "POST"]
176
177        def dispatch_request(self):
178            return flask.request.method
179
180    app.add_url_rule("/", view_func=Index.as_view("index"))
181
182    with pytest.raises(AssertionError):
183        app.add_url_rule("/", view_func=Index.as_view("index"))
184
185    # But these tests should still pass. We just log a warning.
186    common_test(app)
187
188
189def test_methods_var_inheritance(app, client):
190    class BaseView(flask.views.MethodView):
191        methods = ["GET", "PROPFIND"]
192
193    class ChildView(BaseView):
194        def get(self):
195            return "GET"
196
197        def propfind(self):
198            return "PROPFIND"
199
200    app.add_url_rule("/", view_func=ChildView.as_view("index"))
201
202    assert client.get("/").data == b"GET"
203    assert client.open("/", method="PROPFIND").data == b"PROPFIND"
204    assert ChildView.methods == {"PROPFIND", "GET"}
205
206
207def test_multiple_inheritance(app, client):
208    class GetView(flask.views.MethodView):
209        def get(self):
210            return "GET"
211
212    class DeleteView(flask.views.MethodView):
213        def delete(self):
214            return "DELETE"
215
216    class GetDeleteView(GetView, DeleteView):
217        pass
218
219    app.add_url_rule("/", view_func=GetDeleteView.as_view("index"))
220
221    assert client.get("/").data == b"GET"
222    assert client.delete("/").data == b"DELETE"
223    assert sorted(GetDeleteView.methods) == ["DELETE", "GET"]
224
225
226def test_remove_method_from_parent(app, client):
227    class GetView(flask.views.MethodView):
228        def get(self):
229            return "GET"
230
231    class OtherView(flask.views.MethodView):
232        def post(self):
233            return "POST"
234
235    class View(GetView, OtherView):
236        methods = ["GET"]
237
238    app.add_url_rule("/", view_func=View.as_view("index"))
239
240    assert client.get("/").data == b"GET"
241    assert client.post("/").status_code == 405
242    assert sorted(View.methods) == ["GET"]
243