fresco 3.3.1__py3-none-any.whl → 3.3.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fresco might be problematic. Click here for more details.
- fresco/__init__.py +1 -1
- fresco/request.py +2 -2
- fresco/response.py +1 -1
- fresco/routing.py +1 -0
- fresco/tests/__init__.py +0 -0
- fresco/tests/fixtures.py +67 -0
- fresco/tests/test_cookie.py +59 -0
- fresco/tests/test_core.py +1038 -0
- fresco/tests/test_decorators.py +40 -0
- fresco/tests/test_exceptions.py +30 -0
- fresco/tests/test_middleware.py +92 -0
- fresco/tests/test_multidict.py +234 -0
- fresco/tests/test_options.py +314 -0
- fresco/tests/test_request.py +448 -0
- fresco/tests/test_requestcontext.py +107 -0
- fresco/tests/test_response.py +224 -0
- fresco/tests/test_routeargs.py +223 -0
- fresco/tests/test_routing.py +1126 -0
- fresco/tests/test_static.py +124 -0
- fresco/tests/test_subrequests.py +236 -0
- fresco/tests/util/__init__.py +0 -0
- fresco/tests/util/form_data.py +79 -0
- fresco/tests/util/test_common.py +34 -0
- fresco/tests/util/test_http.py +323 -0
- fresco/tests/util/test_security.py +34 -0
- fresco/tests/util/test_urls.py +176 -0
- fresco/tests/util/test_wsgi.py +107 -0
- fresco/util/contentencodings.py +2 -1
- fresco/util/http.py +3 -1
- fresco/util/wsgi.py +1 -1
- {fresco-3.3.1.dist-info → fresco-3.3.3.dist-info}/METADATA +5 -6
- fresco-3.3.3.dist-info/RECORD +57 -0
- {fresco-3.3.1.dist-info → fresco-3.3.3.dist-info}/WHEEL +1 -1
- fresco-3.3.1.dist-info/RECORD +0 -34
- {fresco-3.3.1.dist-info → fresco-3.3.3.dist-info}/LICENSE.txt +0 -0
- {fresco-3.3.1.dist-info → fresco-3.3.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1126 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# Copyright 2015 Oliver Cope
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
from copy import copy
|
|
17
|
+
from functools import wraps
|
|
18
|
+
from functools import partial
|
|
19
|
+
from unittest.mock import Mock, call
|
|
20
|
+
import typing as t
|
|
21
|
+
|
|
22
|
+
import pytest
|
|
23
|
+
import tms
|
|
24
|
+
|
|
25
|
+
from fresco import FrescoApp
|
|
26
|
+
from fresco.core import urlfor
|
|
27
|
+
from fresco.exceptions import NotFound
|
|
28
|
+
from fresco.response import Response
|
|
29
|
+
from fresco.routing import ALL_METHODS
|
|
30
|
+
from fresco.routing import GET
|
|
31
|
+
from fresco.routing import POST
|
|
32
|
+
from fresco.routing import (
|
|
33
|
+
Route,
|
|
34
|
+
DelegateRoute,
|
|
35
|
+
RouteCollection,
|
|
36
|
+
routefor,
|
|
37
|
+
RouteTraversal,
|
|
38
|
+
RouteNotFound,
|
|
39
|
+
TraversedCollection,
|
|
40
|
+
register_converter,
|
|
41
|
+
Converter,
|
|
42
|
+
)
|
|
43
|
+
from . import fixtures
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def assert_method_bound_to(method, ob):
|
|
47
|
+
try:
|
|
48
|
+
assert method.__self__ is ob
|
|
49
|
+
except AttributeError:
|
|
50
|
+
assert method.im_self is ob
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestMethodDispatch(object):
|
|
54
|
+
def test_route_is_dispatched_to_correct_method(self):
|
|
55
|
+
getview = Mock(return_value=Response())
|
|
56
|
+
postview = Mock(return_value=Response())
|
|
57
|
+
app = FrescoApp()
|
|
58
|
+
app.route("/", GET, getview)
|
|
59
|
+
app.route("/", POST, postview)
|
|
60
|
+
|
|
61
|
+
with app.requestcontext("/"):
|
|
62
|
+
app.view()
|
|
63
|
+
assert getview.call_count == 1
|
|
64
|
+
assert postview.call_count == 0
|
|
65
|
+
|
|
66
|
+
with app.requestcontext_post("/"):
|
|
67
|
+
app.view()
|
|
68
|
+
assert getview.call_count == 1
|
|
69
|
+
assert postview.call_count == 1
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TestRouteConstructor(object):
|
|
73
|
+
def test_multiple_views_can_be_associated_with_a_route(self):
|
|
74
|
+
app = FrescoApp()
|
|
75
|
+
v1 = Mock(return_value=Response())
|
|
76
|
+
v2 = Mock(return_value=Response())
|
|
77
|
+
app.route("/", GET=v1, POST=v2)
|
|
78
|
+
|
|
79
|
+
with app.requestcontext():
|
|
80
|
+
app.view()
|
|
81
|
+
assert v1.call_count == 1
|
|
82
|
+
assert v2.call_count == 0
|
|
83
|
+
|
|
84
|
+
with app.requestcontext_post():
|
|
85
|
+
app.view()
|
|
86
|
+
assert v1.call_count == 1
|
|
87
|
+
assert v2.call_count == 1
|
|
88
|
+
|
|
89
|
+
def test_kwargs_take_precedence(self):
|
|
90
|
+
app = FrescoApp()
|
|
91
|
+
v1 = Mock(return_value=Response())
|
|
92
|
+
v2 = Mock(return_value=Response())
|
|
93
|
+
app.route("/", ALL_METHODS, v1, POST=v2)
|
|
94
|
+
|
|
95
|
+
with app.requestcontext():
|
|
96
|
+
app.view()
|
|
97
|
+
assert v1.call_count == 1
|
|
98
|
+
assert v2.call_count == 0
|
|
99
|
+
|
|
100
|
+
with app.requestcontext_post():
|
|
101
|
+
app.view()
|
|
102
|
+
assert v1.call_count == 1
|
|
103
|
+
assert v2.call_count == 1
|
|
104
|
+
|
|
105
|
+
def test_it_catches_invalid_methods(self):
|
|
106
|
+
# Route must be a string or an iterable
|
|
107
|
+
with pytest.raises(TypeError):
|
|
108
|
+
Route("/", object(), lambda: None)
|
|
109
|
+
|
|
110
|
+
# Route must be a valid HTTP method
|
|
111
|
+
with pytest.raises(ValueError):
|
|
112
|
+
Route("/", "FOO", lambda: None)
|
|
113
|
+
|
|
114
|
+
with pytest.raises(ValueError):
|
|
115
|
+
Route("/", ["GET", "FOO"], lambda: None)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TestRouteBeforeHooks(object):
|
|
119
|
+
def test_hook_is_called(self):
|
|
120
|
+
mock = Mock(return_value=None)
|
|
121
|
+
app = FrescoApp([Route("/", GET=Response).before(lambda: mock())])
|
|
122
|
+
|
|
123
|
+
with app.requestcontext("/"):
|
|
124
|
+
app.view()
|
|
125
|
+
assert mock.call_count == 1
|
|
126
|
+
|
|
127
|
+
def test_hook_is_applied_with_decorator_syntax(self):
|
|
128
|
+
mock = Mock(return_value=None)
|
|
129
|
+
|
|
130
|
+
@Route.before(lambda: mock())
|
|
131
|
+
def view():
|
|
132
|
+
return Response("foo")
|
|
133
|
+
|
|
134
|
+
app = FrescoApp([Route("/", GET=view)])
|
|
135
|
+
with app.requestcontext("/"):
|
|
136
|
+
app.view()
|
|
137
|
+
assert mock.call_count == 1
|
|
138
|
+
|
|
139
|
+
def test_it_calls_view_if_none_returned(self):
|
|
140
|
+
viewmock = Mock(return_value=Response("view response"))
|
|
141
|
+
hookmock = Mock(return_value=None)
|
|
142
|
+
app = FrescoApp([Route("/", GET=viewmock).before(hookmock)])
|
|
143
|
+
with app.requestcontext("/"):
|
|
144
|
+
assert list(app.view().content_iterator) == [b"view response"]
|
|
145
|
+
assert viewmock.call_count == 1
|
|
146
|
+
|
|
147
|
+
def test_it_aborts_view_if_value_returned(self):
|
|
148
|
+
viewmock = Mock(return_value=Response("view response"))
|
|
149
|
+
hookmock = Mock(return_value=Response("hook response"))
|
|
150
|
+
app = FrescoApp([Route("/", GET=viewmock).before(hookmock)])
|
|
151
|
+
with app.requestcontext("/"):
|
|
152
|
+
assert list(app.view().content_iterator) == [b"hook response"]
|
|
153
|
+
|
|
154
|
+
assert viewmock.call_count == 0
|
|
155
|
+
|
|
156
|
+
def test_it_passes_args_to_hook(self):
|
|
157
|
+
mock = Mock(return_value=None)
|
|
158
|
+
app = FrescoApp([Route("/", GET=Response, foo="bar").before(mock)])
|
|
159
|
+
with app.requestcontext("/"):
|
|
160
|
+
app.view()
|
|
161
|
+
assert mock.call_args_list == [call(foo="bar")]
|
|
162
|
+
|
|
163
|
+
def test_it_applies_chained_hook_calls_in_order(self):
|
|
164
|
+
mock = Mock(return_value=None)
|
|
165
|
+
|
|
166
|
+
@Route.before(mock, 1)
|
|
167
|
+
@Route.before(mock, 2)
|
|
168
|
+
def view():
|
|
169
|
+
return Response()
|
|
170
|
+
|
|
171
|
+
app = FrescoApp()
|
|
172
|
+
app.route("/", GET, view).before(mock, 3).before(mock, 4)
|
|
173
|
+
|
|
174
|
+
with app.requestcontext("/"):
|
|
175
|
+
app.view()
|
|
176
|
+
assert mock.call_args_list == [call(1), call(2), call(3), call(4)]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class TestRouteViewFilters(object):
|
|
180
|
+
def exclaim(self, response):
|
|
181
|
+
return response.replace(content=[b"".join(response.content_iterator) + b"!"])
|
|
182
|
+
|
|
183
|
+
def ask(self, response):
|
|
184
|
+
return response.replace(content=[b"".join(response.content_iterator) + b"?"])
|
|
185
|
+
|
|
186
|
+
def test_filter_is_applied(self):
|
|
187
|
+
views = fixtures.CBV("test")
|
|
188
|
+
app = FrescoApp()
|
|
189
|
+
app.route("/", GET, views.index_html).filter(self.ask)
|
|
190
|
+
with app.requestcontext("/"):
|
|
191
|
+
assert list(app.view().content_iterator) == [b"test?"]
|
|
192
|
+
|
|
193
|
+
def test_filter_is_applied_as_route_kwargs(self):
|
|
194
|
+
views = fixtures.CBV("test")
|
|
195
|
+
app = FrescoApp()
|
|
196
|
+
app.route("/", GET, views.index_html, filters=[self.ask])
|
|
197
|
+
with app.requestcontext("/"):
|
|
198
|
+
assert list(app.view().content_iterator) == [b"test?"]
|
|
199
|
+
|
|
200
|
+
def test_it_passes_args_to_filter(self):
|
|
201
|
+
app = FrescoApp()
|
|
202
|
+
views = fixtures.CBV("test")
|
|
203
|
+
|
|
204
|
+
def filter_func(r, s):
|
|
205
|
+
return r.replace(content=r.content + [s])
|
|
206
|
+
|
|
207
|
+
app.route("/", GET, views.index_html).filter(filter_func, s="foo")
|
|
208
|
+
with app.requestcontext("/"):
|
|
209
|
+
assert list(app.view().content_iterator) == [b"test", b"foo"]
|
|
210
|
+
|
|
211
|
+
def test_it_applies_chained_filter_calls_in_order(self):
|
|
212
|
+
app = FrescoApp()
|
|
213
|
+
views = fixtures.CBV("test")
|
|
214
|
+
app.route("/", GET, views.index_html).filter(self.ask).filter(self.exclaim)
|
|
215
|
+
with app.requestcontext("/"):
|
|
216
|
+
assert list(app.view().content_iterator) == [b"test?!"]
|
|
217
|
+
|
|
218
|
+
def test_filter_is_applied_with_decorator_syntax(self):
|
|
219
|
+
@Route.filter(lambda c: Response(c + "!"))
|
|
220
|
+
def view():
|
|
221
|
+
return "foo"
|
|
222
|
+
|
|
223
|
+
app = FrescoApp()
|
|
224
|
+
app.route("/", GET, view)
|
|
225
|
+
with app.requestcontext("/"):
|
|
226
|
+
assert list(app.view().content_iterator) == [b"foo!"]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class TestRouteDecorators(object):
|
|
230
|
+
def exclaim(self, func):
|
|
231
|
+
@wraps(func)
|
|
232
|
+
def exclaim(*args, **kwargs):
|
|
233
|
+
response = func(*args, **kwargs)
|
|
234
|
+
return response.replace(
|
|
235
|
+
content=[b"".join(response.content_iterator) + b"!"]
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return exclaim
|
|
239
|
+
|
|
240
|
+
def test_decorator_is_applied(self):
|
|
241
|
+
views = fixtures.CBV("test")
|
|
242
|
+
|
|
243
|
+
app = FrescoApp()
|
|
244
|
+
app.route("/decorated", GET, views.index_html, decorators=[self.exclaim])
|
|
245
|
+
app.route("/plain", GET, views.index_html)
|
|
246
|
+
|
|
247
|
+
with app.requestcontext("/decorated"):
|
|
248
|
+
assert list(app.view().content_iterator) == [b"test!"]
|
|
249
|
+
|
|
250
|
+
with app.requestcontext("/plain"):
|
|
251
|
+
assert list(app.view().content_iterator) == [b"test"]
|
|
252
|
+
|
|
253
|
+
def test_decorator_is_applied_with_wrap_method(self):
|
|
254
|
+
views = fixtures.CBV("test")
|
|
255
|
+
|
|
256
|
+
app = FrescoApp()
|
|
257
|
+
app.route("/decorated", GET, views.index_html).wrap(self.exclaim)
|
|
258
|
+
app.route("/plain", GET, views.index_html)
|
|
259
|
+
|
|
260
|
+
with app.requestcontext("/decorated"):
|
|
261
|
+
assert list(app.view().content_iterator) == [b"test!"]
|
|
262
|
+
|
|
263
|
+
with app.requestcontext("/plain"):
|
|
264
|
+
assert list(app.view().content_iterator) == [b"test"]
|
|
265
|
+
|
|
266
|
+
def test_decorator_is_applied_with_decorator_syntax(self):
|
|
267
|
+
@Route.decorate(self.exclaim)
|
|
268
|
+
def f():
|
|
269
|
+
return Response(["test"])
|
|
270
|
+
|
|
271
|
+
class A(object):
|
|
272
|
+
__routes__ = [Route("/", GET, "view")]
|
|
273
|
+
|
|
274
|
+
@Route.decorate(self.exclaim)
|
|
275
|
+
def view(self):
|
|
276
|
+
return Response(["test"])
|
|
277
|
+
|
|
278
|
+
app = FrescoApp()
|
|
279
|
+
app.route("/", GET, f)
|
|
280
|
+
app.include("/a", A())
|
|
281
|
+
with app.requestcontext("/"):
|
|
282
|
+
assert list(app.view().content_iterator) == [b"test!"]
|
|
283
|
+
with app.requestcontext("/a/"):
|
|
284
|
+
assert list(app.view().content_iterator) == [b"test!"]
|
|
285
|
+
|
|
286
|
+
def test_decorator_works_with_urlfor(self):
|
|
287
|
+
views = fixtures.CBV("test")
|
|
288
|
+
app = FrescoApp()
|
|
289
|
+
app.route("/decorated", GET, views.index_html, decorators=[self.exclaim])
|
|
290
|
+
with app.requestcontext():
|
|
291
|
+
assert urlfor(views.index_html, _app=app) == "http://localhost/decorated"
|
|
292
|
+
|
|
293
|
+
def test_using_wraps_with_viewspec_doesnt_raise_AttributeError(self):
|
|
294
|
+
def decorator(func):
|
|
295
|
+
@wraps(func)
|
|
296
|
+
def decorator(*args, **kwargs):
|
|
297
|
+
return func(*args, **kwargs)
|
|
298
|
+
|
|
299
|
+
return decorator
|
|
300
|
+
|
|
301
|
+
class Views(object):
|
|
302
|
+
__routes__ = (Route("/", GET, "index_html", decorators=[decorator]),)
|
|
303
|
+
|
|
304
|
+
def index_html(self):
|
|
305
|
+
return Response(["hello"])
|
|
306
|
+
|
|
307
|
+
app = FrescoApp()
|
|
308
|
+
app.include("/", Views())
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class TestPredicates(object):
|
|
312
|
+
def test_predicate_match(self):
|
|
313
|
+
def v1():
|
|
314
|
+
return Response([b"x"])
|
|
315
|
+
|
|
316
|
+
def v2():
|
|
317
|
+
return Response([b"y"])
|
|
318
|
+
|
|
319
|
+
app = FrescoApp()
|
|
320
|
+
app.route("/", GET, v1, predicate=lambda request: "x" in request.query)
|
|
321
|
+
app.route("/", GET, v2, predicate=lambda request: "y" in request.query)
|
|
322
|
+
|
|
323
|
+
with app.requestcontext("/?x=1"):
|
|
324
|
+
assert b"".join(app.view().content) == b"x"
|
|
325
|
+
with app.requestcontext("/?y=1"):
|
|
326
|
+
assert b"".join(app.view().content) == b"y"
|
|
327
|
+
with app.requestcontext("/"):
|
|
328
|
+
assert app.view().status_code == 404
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class TestRouteNames(object):
|
|
332
|
+
def test_name_present_in_route_keys(self):
|
|
333
|
+
r = Route("/", GET, None, name="foo")
|
|
334
|
+
assert "foo" in list(r.route_keys())
|
|
335
|
+
|
|
336
|
+
def test_name_with_other_kwargs(self):
|
|
337
|
+
r = Route("/", GET, None, name="foo", x="bar")
|
|
338
|
+
assert "foo" in list(r.route_keys())
|
|
339
|
+
|
|
340
|
+
def test_name_cannot_contain_colon(self):
|
|
341
|
+
with pytest.raises(ValueError):
|
|
342
|
+
Route("/", GET, None, name="foo:bar")
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class TestRouteCollection(object):
|
|
346
|
+
def test_it_adds_routes_from_constructor(self):
|
|
347
|
+
r1 = Route("/1", GET, None, name="1")
|
|
348
|
+
r2 = Route("/2", POST, None, name="2")
|
|
349
|
+
rc = RouteCollection([r1, r2])
|
|
350
|
+
assert [r.name for r in rc] == ["1", "2"]
|
|
351
|
+
|
|
352
|
+
def test_it_adds_routecollections_from_constructor(self):
|
|
353
|
+
r1 = Route("/", GET, None, name="1")
|
|
354
|
+
r2 = Route("/", POST, None, name="2")
|
|
355
|
+
r3 = Route("/", POST, None, name="3")
|
|
356
|
+
rc = RouteCollection([r1, RouteCollection([r2, r3])])
|
|
357
|
+
assert [r.name for r in rc] == ["1", "2", "3"]
|
|
358
|
+
|
|
359
|
+
def test_it_adds_dunderroutes_from_constructor(self):
|
|
360
|
+
r1 = Route("/", GET, None, name="1")
|
|
361
|
+
r2 = Route("/", POST, None, name="2")
|
|
362
|
+
r3 = Route("/", POST, None, name="3")
|
|
363
|
+
|
|
364
|
+
class A:
|
|
365
|
+
__routes__ = [r2, r3]
|
|
366
|
+
|
|
367
|
+
rc = RouteCollection([r1, A()])
|
|
368
|
+
assert [r.name for r in rc] == ["1", "2", "3"]
|
|
369
|
+
|
|
370
|
+
def test_get_routes_matches_on_method(self):
|
|
371
|
+
r_get = Route("/", GET, None)
|
|
372
|
+
r_post = Route("/", POST, None)
|
|
373
|
+
|
|
374
|
+
rc = RouteCollection([r_post, r_get])
|
|
375
|
+
|
|
376
|
+
assert [r.route for r in rc.get_route_traversals("/", GET)] == [r_get]
|
|
377
|
+
assert [r.route for r in rc.get_route_traversals("/", POST)] == [r_post]
|
|
378
|
+
|
|
379
|
+
def test_get_routes_matches_on_path(self):
|
|
380
|
+
r1 = Route("/1", GET, None)
|
|
381
|
+
r2 = Route("/2", GET, None)
|
|
382
|
+
|
|
383
|
+
rc = RouteCollection([r1, r2])
|
|
384
|
+
|
|
385
|
+
assert [r.route for r in rc.get_route_traversals("/1", GET)] == [r1]
|
|
386
|
+
assert [r.route for r in rc.get_route_traversals("/2", GET)] == [r2]
|
|
387
|
+
|
|
388
|
+
def test_get_routes_can_match_all_methods(self):
|
|
389
|
+
r1 = Route("/1", GET, None)
|
|
390
|
+
r2 = Route("/1", POST, None)
|
|
391
|
+
|
|
392
|
+
rc = RouteCollection([r1, r2])
|
|
393
|
+
assert [r.route for r in rc.get_route_traversals("/1", None)] == [
|
|
394
|
+
r1,
|
|
395
|
+
r2,
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
def test_route_returns_traversal_information_on_nested_routes(self):
|
|
399
|
+
a = RouteCollection()
|
|
400
|
+
b = RouteCollection()
|
|
401
|
+
|
|
402
|
+
a_route = Route("/harvey", GET, lambda: None)
|
|
403
|
+
b_route = Route("/harvey", GET, lambda: None)
|
|
404
|
+
|
|
405
|
+
a.add_route(a_route)
|
|
406
|
+
b.add_route(b_route)
|
|
407
|
+
|
|
408
|
+
a_delegate_route = DelegateRoute("/rabbit", b)
|
|
409
|
+
b_delegate_route = DelegateRoute("/hole", a)
|
|
410
|
+
|
|
411
|
+
a.add_route(a_delegate_route)
|
|
412
|
+
b.add_route(b_delegate_route)
|
|
413
|
+
|
|
414
|
+
r = next(a.get_route_traversals("/rabbit/hole/rabbit/harvey", None))
|
|
415
|
+
|
|
416
|
+
assert r.collections_traversed == [
|
|
417
|
+
(a, "", a_delegate_route, (), {}, (), {}),
|
|
418
|
+
(b, "/rabbit", b_delegate_route, (), {}, (), {}),
|
|
419
|
+
(a, "/rabbit/hole", a_delegate_route, (), {}, (), {}),
|
|
420
|
+
(b, "/rabbit/hole/rabbit", b_route, (), {}, (), {}),
|
|
421
|
+
]
|
|
422
|
+
|
|
423
|
+
def test_pathfor_works_with_positional_args(self):
|
|
424
|
+
view = Mock(return_value=Response())
|
|
425
|
+
rc = RouteCollection([Route("/<:str>", GET, view)])
|
|
426
|
+
assert rc.pathfor(view, "x") == "/x"
|
|
427
|
+
|
|
428
|
+
def test_replace_raises_route_not_found(self):
|
|
429
|
+
a = RouteCollection()
|
|
430
|
+
view = Mock(return_value=Response())
|
|
431
|
+
a.route("/harvey", GET, view, name="harvey")
|
|
432
|
+
with pytest.raises(RouteNotFound):
|
|
433
|
+
a.replace("rabbit", None)
|
|
434
|
+
|
|
435
|
+
def test_replace_selects_routes_by_name(self):
|
|
436
|
+
a = RouteCollection()
|
|
437
|
+
oldroute = Route("/", GET, Mock(), name="harvey")
|
|
438
|
+
newroute = Route("/", GET, Mock())
|
|
439
|
+
a.add_route(oldroute)
|
|
440
|
+
a.replace("harvey", newroute)
|
|
441
|
+
assert a.__routes__ == [newroute]
|
|
442
|
+
|
|
443
|
+
def test_replace_selects_routes_by_view(self):
|
|
444
|
+
a = RouteCollection()
|
|
445
|
+
view = Mock(return_value=Response())
|
|
446
|
+
oldroute = Route("/", GET, view)
|
|
447
|
+
newroute = Route("/", GET, Mock())
|
|
448
|
+
a.add_route(oldroute)
|
|
449
|
+
a.replace(view, newroute)
|
|
450
|
+
assert a.__routes__ == [newroute]
|
|
451
|
+
|
|
452
|
+
def test_can_add_a_list_to_a_routecollection(self):
|
|
453
|
+
r1 = Route("/", GET, Mock())
|
|
454
|
+
r2 = Route("/", GET, Mock())
|
|
455
|
+
assert (RouteCollection([r1]) + [r2]).__routes__ == [r1, r2]
|
|
456
|
+
|
|
457
|
+
def test_can_add_route_to_routecollection(self):
|
|
458
|
+
r1 = Route("/", GET, Mock())
|
|
459
|
+
r2 = Route("/", GET, Mock())
|
|
460
|
+
assert (RouteCollection([r1]) + r2).__routes__ == [r1, r2]
|
|
461
|
+
|
|
462
|
+
def test_can_add_routecollection_to_route(self):
|
|
463
|
+
r1 = Route("/", GET, Mock())
|
|
464
|
+
r2 = Route("/", GET, Mock())
|
|
465
|
+
assert (r1 + RouteCollection([r2])).__routes__ == [r1, r2]
|
|
466
|
+
|
|
467
|
+
def test_can_add_routecollections(self):
|
|
468
|
+
r1 = Route("/", GET, Mock())
|
|
469
|
+
r2 = Route("/", GET, Mock())
|
|
470
|
+
assert (RouteCollection([r1]) + RouteCollection([r2])).__routes__ == [
|
|
471
|
+
r1,
|
|
472
|
+
r2,
|
|
473
|
+
]
|
|
474
|
+
|
|
475
|
+
def test_routecollections_can_be_used_in_classes(self):
|
|
476
|
+
class MyViews(object):
|
|
477
|
+
__routes__ = RouteCollection([Route("/", GET, "view")])
|
|
478
|
+
|
|
479
|
+
def view(self):
|
|
480
|
+
return Response()
|
|
481
|
+
|
|
482
|
+
v = MyViews()
|
|
483
|
+
app = FrescoApp()
|
|
484
|
+
app.include("/", v)
|
|
485
|
+
assert [r.route.getview(GET) for r in app.get_route_traversals("/", GET)] == [
|
|
486
|
+
v.view
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
def test_routecollections_in_classes_can_be_manipulated(self):
|
|
490
|
+
class MyViews(object):
|
|
491
|
+
__routes__ = RouteCollection([Route("/", GET, "view")])
|
|
492
|
+
|
|
493
|
+
def view(self):
|
|
494
|
+
return Response()
|
|
495
|
+
|
|
496
|
+
class MyOtherViews(MyViews):
|
|
497
|
+
__routes__ = copy(MyViews.__routes__)
|
|
498
|
+
__routes__.replace("view", Route("/", GET, "another_view"))
|
|
499
|
+
|
|
500
|
+
def another_view(self):
|
|
501
|
+
return Response()
|
|
502
|
+
|
|
503
|
+
v = MyOtherViews()
|
|
504
|
+
app = FrescoApp()
|
|
505
|
+
app.include("/", v)
|
|
506
|
+
assert [r.route.getview(GET) for r in app.get_route_traversals("/", GET)] == [
|
|
507
|
+
v.another_view
|
|
508
|
+
]
|
|
509
|
+
|
|
510
|
+
def test_add_prefix_returns_prefixed_collection(self):
|
|
511
|
+
rc = RouteCollection([Route("/fish", GET, None), Route("/beans", GET, None)])
|
|
512
|
+
prefixed = rc.add_prefix("/jelly")
|
|
513
|
+
assert [str(r.pattern) for r in prefixed] == [
|
|
514
|
+
"/jelly/fish",
|
|
515
|
+
"/jelly/beans",
|
|
516
|
+
]
|
|
517
|
+
|
|
518
|
+
def test_it_binds_routes_to_an_instance(self):
|
|
519
|
+
views = fixtures.CBV("test")
|
|
520
|
+
rc = RouteCollection([views])
|
|
521
|
+
view = next(rc.get_route_traversals("/", GET)).route.getview(GET)
|
|
522
|
+
assert_method_bound_to(view, views)
|
|
523
|
+
|
|
524
|
+
def test_it_binds_routes_to_an_instance_via_include(self):
|
|
525
|
+
views = fixtures.CBV("test")
|
|
526
|
+
rc = RouteCollection([])
|
|
527
|
+
rc.include("/", views)
|
|
528
|
+
view = next(rc.get_route_traversals("/", GET)).route.getview(GET)
|
|
529
|
+
assert_method_bound_to(view, views)
|
|
530
|
+
|
|
531
|
+
def test_including_twice_does_not_rebind_instance(self):
|
|
532
|
+
views = fixtures.CBV("test")
|
|
533
|
+
rc = RouteCollection([])
|
|
534
|
+
rc.include("/", views)
|
|
535
|
+
rc2 = RouteCollection([])
|
|
536
|
+
rc2.include("/", rc)
|
|
537
|
+
view = next(rc2.get_route_traversals("/", GET)).route.getview(GET)
|
|
538
|
+
assert_method_bound_to(view, views)
|
|
539
|
+
|
|
540
|
+
def test_it_binds_routes_via_a_string(self):
|
|
541
|
+
rc = RouteCollection()
|
|
542
|
+
rc.route("/", GET, "fresco.tests.fixtures.module_level_function")
|
|
543
|
+
view = next(rc.get_route_traversals("/", GET)).route.getview(GET)
|
|
544
|
+
assert view is fixtures.module_level_function
|
|
545
|
+
|
|
546
|
+
def test_it_inserts_route(self):
|
|
547
|
+
rc = RouteCollection([])
|
|
548
|
+
a = Route("/", GET=lambda: None)
|
|
549
|
+
b = Route("/", GET=lambda: None)
|
|
550
|
+
rc.insert(0, a)
|
|
551
|
+
rc.insert(0, b)
|
|
552
|
+
assert list(rc) == [b, a]
|
|
553
|
+
|
|
554
|
+
def test_len(self):
|
|
555
|
+
assert len(RouteCollection()) == 0
|
|
556
|
+
assert len(RouteCollection([Route("/", GET=lambda: None)])) == 1
|
|
557
|
+
|
|
558
|
+
def test_getitem(self):
|
|
559
|
+
a = Route("/", GET=lambda: None)
|
|
560
|
+
b = Route("/", GET=lambda: None)
|
|
561
|
+
c = Route("/", GET=lambda: None)
|
|
562
|
+
rc = RouteCollection([a, b, c])
|
|
563
|
+
assert rc[0] is a
|
|
564
|
+
assert rc[1] is b
|
|
565
|
+
assert rc[-1] is c
|
|
566
|
+
assert rc[1:3].__routes__ == [b, c]
|
|
567
|
+
|
|
568
|
+
def test_setitem(self):
|
|
569
|
+
a = Route("/", GET=lambda: None)
|
|
570
|
+
b = Route("/", GET=lambda: None)
|
|
571
|
+
c = Route("/", GET=lambda: None)
|
|
572
|
+
d = Route("/", GET=lambda: None)
|
|
573
|
+
rc = RouteCollection([a, b])
|
|
574
|
+
rc[0] = c
|
|
575
|
+
assert rc.__routes__ == [c, b]
|
|
576
|
+
rc[0:2] = [d]
|
|
577
|
+
assert rc.__routes__ == [d]
|
|
578
|
+
|
|
579
|
+
def test_it_routes_to_a_wsgi_app(self):
|
|
580
|
+
def test_wsgi_app(
|
|
581
|
+
mountpoint,
|
|
582
|
+
path,
|
|
583
|
+
expected_script_name,
|
|
584
|
+
expected_path_info,
|
|
585
|
+
rewrite_script_name=True,
|
|
586
|
+
):
|
|
587
|
+
def wsgiapp(environ, start_response):
|
|
588
|
+
wsgiapp.called = True # type: ignore
|
|
589
|
+
assert environ["SCRIPT_NAME"] == expected_script_name
|
|
590
|
+
assert environ["PATH_INFO"] == expected_path_info
|
|
591
|
+
start_response("204 no content", [])
|
|
592
|
+
return []
|
|
593
|
+
|
|
594
|
+
app = FrescoApp()
|
|
595
|
+
app.route_wsgi(mountpoint, wsgiapp, rewrite_script_name=rewrite_script_name)
|
|
596
|
+
with app.requestcontext(path):
|
|
597
|
+
app.view()
|
|
598
|
+
assert wsgiapp.called is True # type: ignore
|
|
599
|
+
|
|
600
|
+
test_wsgi_app("/prefix", "/prefix", "/prefix", "")
|
|
601
|
+
test_wsgi_app("/prefix", "/prefix/", "/prefix", "/")
|
|
602
|
+
test_wsgi_app("/", "/", "", "/")
|
|
603
|
+
test_wsgi_app("/", "/foo", "", "/foo")
|
|
604
|
+
test_wsgi_app("/prefix", "/prefix", "", "/prefix", rewrite_script_name=False)
|
|
605
|
+
|
|
606
|
+
def test_it_routes_to_a_wsgi_app_by_dotted_path(self):
|
|
607
|
+
app = FrescoApp()
|
|
608
|
+
app.route_wsgi("/", "fresco.tests.fixtures.wsgi_app")
|
|
609
|
+
with app.requestcontext("/"):
|
|
610
|
+
response = app.view()
|
|
611
|
+
assert list(response.content_iterator) == [b"ok"]
|
|
612
|
+
|
|
613
|
+
def test_it_isolates_request_for_wsgi_app(self):
|
|
614
|
+
|
|
615
|
+
params: t.Dict[str, t.Any] = {}
|
|
616
|
+
outerapp = FrescoApp()
|
|
617
|
+
innerapp = FrescoApp()
|
|
618
|
+
innerapp.route("/y", GET=Response)
|
|
619
|
+
outerapp.route_wsgi("/x", innerapp)
|
|
620
|
+
|
|
621
|
+
innerapp.process_request(partial(params.__setitem__, 'inner_request'))
|
|
622
|
+
outerapp.process_request(partial(params.__setitem__, 'outer_request'))
|
|
623
|
+
|
|
624
|
+
with outerapp.requestcontext("/x/y"):
|
|
625
|
+
outerapp.view()
|
|
626
|
+
assert params["inner_request"] is not params["outer_request"]
|
|
627
|
+
assert params["inner_request"].environ is not params["outer_request"].environ
|
|
628
|
+
|
|
629
|
+
def test_fallthrough_can_be_applied_to_collection(self):
|
|
630
|
+
a = Route("/fee", GET=lambda: Response(status=204))
|
|
631
|
+
a = Route("/fie", GET=lambda: Response(status=204))
|
|
632
|
+
b = Route("/foe", GET=lambda: Response(status=204))
|
|
633
|
+
rc = RouteCollection([a, b])
|
|
634
|
+
rc = rc.fallthrough_on(["204"])
|
|
635
|
+
assert all(r.fallthrough_statuses == {204} for r in rc.__routes__)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
class TestRoutefor(object):
|
|
639
|
+
def test_routefor_with_view_function(self):
|
|
640
|
+
def view():
|
|
641
|
+
return Response(["ok"])
|
|
642
|
+
|
|
643
|
+
app = FrescoApp()
|
|
644
|
+
route = app.route("/foo", GET, view)
|
|
645
|
+
|
|
646
|
+
with app.requestcontext():
|
|
647
|
+
assert routefor(view) == route
|
|
648
|
+
|
|
649
|
+
def test_routefor_with_string(self):
|
|
650
|
+
app = FrescoApp()
|
|
651
|
+
route = app.route("/myviewfunc", GET, fixtures.module_level_function)
|
|
652
|
+
with app.requestcontext():
|
|
653
|
+
assert routefor("fresco.tests.fixtures.module_level_function") == route
|
|
654
|
+
|
|
655
|
+
def test_routefor_generates_first_route(self):
|
|
656
|
+
def myviewfunc():
|
|
657
|
+
return Response()
|
|
658
|
+
|
|
659
|
+
app = FrescoApp()
|
|
660
|
+
r1 = app.route("/1", GET, myviewfunc)
|
|
661
|
+
app.route("/2", GET, myviewfunc)
|
|
662
|
+
with app.requestcontext():
|
|
663
|
+
assert routefor(myviewfunc) == r1
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
class TestDelegatedRoutes(object):
|
|
667
|
+
def test_dispatch_to_delegated_route(self):
|
|
668
|
+
def hello():
|
|
669
|
+
return Response([b"hello"])
|
|
670
|
+
|
|
671
|
+
inner = FrescoApp()
|
|
672
|
+
inner.route("/hello", GET, hello)
|
|
673
|
+
|
|
674
|
+
outer = FrescoApp()
|
|
675
|
+
outer.delegate("/say", inner)
|
|
676
|
+
|
|
677
|
+
with outer.requestcontext("/say/hello"):
|
|
678
|
+
assert b"".join(outer.view().content) == b"hello"
|
|
679
|
+
|
|
680
|
+
def test_can_delegate_to_plain_old_class(self):
|
|
681
|
+
class Views(object):
|
|
682
|
+
__routes__ = [Route("/hello", GET, "index")]
|
|
683
|
+
|
|
684
|
+
def index(self):
|
|
685
|
+
return Response(b"OK")
|
|
686
|
+
|
|
687
|
+
app = FrescoApp()
|
|
688
|
+
app.delegate("/foo", Views())
|
|
689
|
+
|
|
690
|
+
with app.requestcontext("/foo/hello"):
|
|
691
|
+
assert app.view().content == b"OK"
|
|
692
|
+
|
|
693
|
+
def test_url_variables_are_passed(self):
|
|
694
|
+
hello = Mock(return_value=Response())
|
|
695
|
+
|
|
696
|
+
inner = FrescoApp()
|
|
697
|
+
inner.route("/<i:str>", GET, hello)
|
|
698
|
+
|
|
699
|
+
outer = FrescoApp()
|
|
700
|
+
outer.delegate("/<o:str>", inner)
|
|
701
|
+
|
|
702
|
+
with outer.requestcontext("/foo/bar"):
|
|
703
|
+
outer.view()
|
|
704
|
+
assert hello.call_args_list == [call(i="bar", o="foo")]
|
|
705
|
+
|
|
706
|
+
def test_delegation_to_dynamic_routes(self):
|
|
707
|
+
result = []
|
|
708
|
+
|
|
709
|
+
class MyRoutes(object):
|
|
710
|
+
__routes__ = [Route("/<inner:int>", GET, "view")]
|
|
711
|
+
|
|
712
|
+
def __init__(self, **kwargs):
|
|
713
|
+
self.kwargs = kwargs
|
|
714
|
+
|
|
715
|
+
def view(self, **kwargs):
|
|
716
|
+
result.append((self, kwargs))
|
|
717
|
+
return Response()
|
|
718
|
+
|
|
719
|
+
app = FrescoApp()
|
|
720
|
+
app.delegate("/<outer:str>", MyRoutes, dynamic=True)
|
|
721
|
+
with app.requestcontext("/one/2"):
|
|
722
|
+
app.view()
|
|
723
|
+
instance, inner_kwargs = result[0]
|
|
724
|
+
assert instance.kwargs == {"outer": "one"}
|
|
725
|
+
assert inner_kwargs == {"inner": 2}
|
|
726
|
+
|
|
727
|
+
def test_dynamic_routes_are_never_shared(self):
|
|
728
|
+
result = []
|
|
729
|
+
|
|
730
|
+
class MyRoutes(object):
|
|
731
|
+
__routes__ = [Route("", GET, "view")]
|
|
732
|
+
|
|
733
|
+
def __init__(self, value):
|
|
734
|
+
self.value = value
|
|
735
|
+
|
|
736
|
+
def view(self):
|
|
737
|
+
result.append(self.value)
|
|
738
|
+
return Response()
|
|
739
|
+
|
|
740
|
+
app = FrescoApp()
|
|
741
|
+
app.delegate("/<value:str>", MyRoutes, dynamic=True)
|
|
742
|
+
with app.requestcontext("/one"):
|
|
743
|
+
app.view()
|
|
744
|
+
v1 = result.pop()
|
|
745
|
+
with app.requestcontext("/two"):
|
|
746
|
+
app.view()
|
|
747
|
+
v2 = result.pop()
|
|
748
|
+
assert v1 == "one"
|
|
749
|
+
assert v2 == "two", v2
|
|
750
|
+
|
|
751
|
+
def test_pathfor_with_delegated_route(self):
|
|
752
|
+
inner = FrescoApp()
|
|
753
|
+
inner.route("/<i:str>", GET, lambda: None, name="inner-route")
|
|
754
|
+
|
|
755
|
+
outer = FrescoApp()
|
|
756
|
+
outer.delegate("/<o:str>", inner, name="delegation")
|
|
757
|
+
|
|
758
|
+
with outer.requestcontext("/foo/bar"):
|
|
759
|
+
assert outer.pathfor("delegation:inner-route", o="x", i="y") == "/x/y"
|
|
760
|
+
|
|
761
|
+
def test_pathfor_with_dynamic_delegated_route(self):
|
|
762
|
+
view = Mock(return_value=Response())
|
|
763
|
+
|
|
764
|
+
def routecollectionfactory(*args, **kwargs):
|
|
765
|
+
return RouteCollection([Route("/<i:str>", GET, view, name="inner-route")])
|
|
766
|
+
|
|
767
|
+
rc = RouteCollection()
|
|
768
|
+
rc.delegate("/<o:str>", routecollectionfactory, name="delegation", dynamic=True)
|
|
769
|
+
|
|
770
|
+
assert rc.pathfor("delegation:inner-route", o="x", i="y") == "/x/y"
|
|
771
|
+
|
|
772
|
+
def test_pathfor_with_dynamic_delegated_route_uses_default_args(self):
|
|
773
|
+
view = Mock(return_value=Response())
|
|
774
|
+
|
|
775
|
+
def routecollectionfactory(factoryarg1, factoryarg2):
|
|
776
|
+
return RouteCollection([Route("/<i:str>", GET, view, name="inner-route")])
|
|
777
|
+
|
|
778
|
+
rc = RouteCollection()
|
|
779
|
+
rc.delegate(
|
|
780
|
+
"/<factoryarg1:str>/<factoryarg2:str>",
|
|
781
|
+
routecollectionfactory,
|
|
782
|
+
factoryarg1_default="foo",
|
|
783
|
+
factoryarg2_default=lambda r: "bar",
|
|
784
|
+
name="delegation",
|
|
785
|
+
dynamic=True,
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
assert rc.pathfor("delegation:inner-route", i="y") == "/foo/bar/y"
|
|
789
|
+
|
|
790
|
+
def test_urlfor_with_dynamic_delegated_route_and_view_self(self):
|
|
791
|
+
result = []
|
|
792
|
+
|
|
793
|
+
class MyRoutes(object):
|
|
794
|
+
__routes__ = [Route("/<inner:int>/view", GET, "view")]
|
|
795
|
+
|
|
796
|
+
def __init__(self, **kwargs):
|
|
797
|
+
self.kwargs = kwargs
|
|
798
|
+
|
|
799
|
+
def view(self, **kwargs):
|
|
800
|
+
result.append(urlfor(self.view, inner=3))
|
|
801
|
+
return Response()
|
|
802
|
+
|
|
803
|
+
app = FrescoApp()
|
|
804
|
+
app.delegate("/<outer:str>", MyRoutes, dynamic=True)
|
|
805
|
+
with app.requestcontext("/two/2/view"):
|
|
806
|
+
app.view()
|
|
807
|
+
assert result == ["http://localhost/two/3/view"]
|
|
808
|
+
|
|
809
|
+
def test_urlgeneration_with_dynamic_routes(self):
|
|
810
|
+
class Routable(object):
|
|
811
|
+
__routes__ = [Route("/<b:int>", GET, "view", name="y")]
|
|
812
|
+
|
|
813
|
+
def __init__(self, a):
|
|
814
|
+
pass
|
|
815
|
+
|
|
816
|
+
def view(self, b):
|
|
817
|
+
return Response()
|
|
818
|
+
|
|
819
|
+
app = FrescoApp()
|
|
820
|
+
app.delegate("/<a:str>", Routable, dynamic=True, name="x")
|
|
821
|
+
with app.requestcontext("/two/2/view"):
|
|
822
|
+
assert urlfor("x:y", a="a", b=1) == "http://localhost/a/1"
|
|
823
|
+
|
|
824
|
+
def test_delegated_routes_can_be_included(self):
|
|
825
|
+
view = Mock(return_value=Response())
|
|
826
|
+
|
|
827
|
+
inner = RouteCollection([Route("/baz", GET, view)])
|
|
828
|
+
middle = RouteCollection([DelegateRoute("/bar", inner)])
|
|
829
|
+
outer = FrescoApp()
|
|
830
|
+
outer.include("/foo", middle)
|
|
831
|
+
with outer.requestcontext("/foo/bar/baz"):
|
|
832
|
+
outer.view()
|
|
833
|
+
assert view.call_count == 1
|
|
834
|
+
|
|
835
|
+
def test_not_found_is_returned(self):
|
|
836
|
+
def inner():
|
|
837
|
+
raise NotFound()
|
|
838
|
+
|
|
839
|
+
outer = FrescoApp()
|
|
840
|
+
outer.delegate("/foo", inner, dynamic=True)
|
|
841
|
+
with outer.requestcontext("/foo/bar/baz"):
|
|
842
|
+
response = outer.view()
|
|
843
|
+
assert response.status_code == 404
|
|
844
|
+
|
|
845
|
+
def test_not_found_causes_next_route_to_be_tried(self):
|
|
846
|
+
def inner():
|
|
847
|
+
raise NotFound()
|
|
848
|
+
|
|
849
|
+
view = Mock(return_value=Response())
|
|
850
|
+
|
|
851
|
+
outer = FrescoApp()
|
|
852
|
+
outer.delegate("/foo", inner, dynamic=True)
|
|
853
|
+
outer.route("/foo", GET, view)
|
|
854
|
+
with outer.requestcontext("/foo"):
|
|
855
|
+
outer.view()
|
|
856
|
+
assert view.call_count == 1
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
class TestConverters(object):
|
|
860
|
+
def test_str_converter_returns_unicode(self):
|
|
861
|
+
from fresco.routing import StrConverter
|
|
862
|
+
|
|
863
|
+
s = str("abc")
|
|
864
|
+
assert isinstance(StrConverter().from_string(s), str)
|
|
865
|
+
|
|
866
|
+
def test_register_coverter_acts_as_decorator(self):
|
|
867
|
+
from fresco.routing import Converter, register_converter
|
|
868
|
+
|
|
869
|
+
@register_converter("testconverter")
|
|
870
|
+
class MyConverter(Converter):
|
|
871
|
+
def from_string(self, s):
|
|
872
|
+
return "bar"
|
|
873
|
+
|
|
874
|
+
view = Mock(return_value=Response())
|
|
875
|
+
|
|
876
|
+
app = FrescoApp()
|
|
877
|
+
app.route("/<:testconverter>", GET, view)
|
|
878
|
+
with app.requestcontext("/foo"):
|
|
879
|
+
app.view()
|
|
880
|
+
assert view.call_args == (("bar",), {}), view.call_args
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
class TestRRoute:
|
|
884
|
+
def test_it_passes_the_request(self):
|
|
885
|
+
from fresco.routing import RRoute
|
|
886
|
+
|
|
887
|
+
view = Mock(return_value=Response())
|
|
888
|
+
app = FrescoApp()
|
|
889
|
+
app.add_route(RRoute("/", GET=view))
|
|
890
|
+
with app.requestcontext("/") as c:
|
|
891
|
+
app.view()
|
|
892
|
+
assert view.call_args == ((c.request,), {})
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
class TestViewArgs(object):
|
|
896
|
+
def test_it_uses_args(self):
|
|
897
|
+
routes = RouteCollection([Route("/", GET, None, args=(1, 2))])
|
|
898
|
+
assert list(routes.get_route_traversals("/", GET)) == [
|
|
899
|
+
tms.InstanceOf(RouteTraversal, args=(1, 2))
|
|
900
|
+
]
|
|
901
|
+
|
|
902
|
+
def test_it_uses_view_args(self):
|
|
903
|
+
routes = RouteCollection([Route("/", GET, None, view_args=(1, 2))])
|
|
904
|
+
assert list(routes.get_route_traversals("/", GET)) == [
|
|
905
|
+
tms.InstanceOf(RouteTraversal, args=(1, 2))
|
|
906
|
+
]
|
|
907
|
+
|
|
908
|
+
def test_it_appends_args_extracted_from_path(self):
|
|
909
|
+
routes = RouteCollection([Route("/<:int>", GET, None, view_args=(1, 2))])
|
|
910
|
+
assert list(routes.get_route_traversals("/3", GET)) == [
|
|
911
|
+
tms.InstanceOf(RouteTraversal, args=(1, 2, 3))
|
|
912
|
+
]
|
|
913
|
+
|
|
914
|
+
def test_it_keeps_traversal_args_separate(self):
|
|
915
|
+
routes = RouteCollection([Route("/<:int>", GET, None, view_args=(1,))])
|
|
916
|
+
assert list(routes.get_route_traversals("/2", GET)) == [
|
|
917
|
+
tms.InstanceOf(
|
|
918
|
+
RouteTraversal,
|
|
919
|
+
args=(1, 2),
|
|
920
|
+
collections_traversed=[
|
|
921
|
+
tms.InstanceOf(
|
|
922
|
+
TraversedCollection, args=(1, 2), traversal_args=(2,)
|
|
923
|
+
)
|
|
924
|
+
],
|
|
925
|
+
)
|
|
926
|
+
]
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
class TestViewKwargs(object):
|
|
930
|
+
def test_it_reads_from_route_kwargs(self):
|
|
931
|
+
routes = RouteCollection([Route("/", GET, None, x=1)])
|
|
932
|
+
assert list(routes.get_route_traversals("/", GET)) == [
|
|
933
|
+
tms.InstanceOf(RouteTraversal, kwargs={"x": 1})
|
|
934
|
+
]
|
|
935
|
+
|
|
936
|
+
def test_it_reads_from_kwargs(self):
|
|
937
|
+
routes = RouteCollection([Route("/", GET, None, kwargs={"x": 1})])
|
|
938
|
+
assert list(routes.get_route_traversals("/", GET)) == [
|
|
939
|
+
tms.InstanceOf(RouteTraversal, kwargs={"x": 1})
|
|
940
|
+
]
|
|
941
|
+
|
|
942
|
+
def test_it_reads_from_view_kwargs(self):
|
|
943
|
+
routes = RouteCollection([Route("/", GET, None, view_kwargs={"x": 1})])
|
|
944
|
+
assert list(routes.get_route_traversals("/", GET)) == [
|
|
945
|
+
tms.InstanceOf(RouteTraversal, kwargs={"x": 1})
|
|
946
|
+
]
|
|
947
|
+
|
|
948
|
+
def test_it_keeps_traversal_kwargs_separate(self):
|
|
949
|
+
routes = RouteCollection([Route("/<x:int>", GET, None, view_kwargs={"y": 1})])
|
|
950
|
+
assert list(routes.get_route_traversals("/2", GET)) == [
|
|
951
|
+
tms.InstanceOf(
|
|
952
|
+
RouteTraversal,
|
|
953
|
+
kwargs={"x": 2, "y": 1},
|
|
954
|
+
collections_traversed=[
|
|
955
|
+
tms.InstanceOf(
|
|
956
|
+
TraversedCollection,
|
|
957
|
+
kwargs={"y": 1, "x": 2},
|
|
958
|
+
traversal_kwargs={"x": 2},
|
|
959
|
+
)
|
|
960
|
+
],
|
|
961
|
+
)
|
|
962
|
+
]
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
class TestRouteKwargs:
|
|
966
|
+
def test_path_defaults_removed_from_view_kwargs(self):
|
|
967
|
+
app = FrescoApp()
|
|
968
|
+
view = Mock(return_value=Response())
|
|
969
|
+
app.route("/<test:int>", GET, view, name="test", test_default=1)
|
|
970
|
+
with app.requestcontext("/2"):
|
|
971
|
+
app.view()
|
|
972
|
+
assert view.call_args_list == [call(test=2)]
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
class TestRouteClassIsPluggable(object):
|
|
976
|
+
class CustomRoute(Route):
|
|
977
|
+
pass
|
|
978
|
+
|
|
979
|
+
def test_it_defaults_to_Route(self):
|
|
980
|
+
routes = RouteCollection()
|
|
981
|
+
assert routes.route_class is Route
|
|
982
|
+
|
|
983
|
+
def test_it_accepts_route_class_arg(self):
|
|
984
|
+
routes = RouteCollection(route_class=self.CustomRoute)
|
|
985
|
+
assert routes.route_class is self.CustomRoute
|
|
986
|
+
|
|
987
|
+
def test_it_uses_route_class_in_route_method(self):
|
|
988
|
+
def myview():
|
|
989
|
+
pass
|
|
990
|
+
|
|
991
|
+
routes = RouteCollection(route_class=self.CustomRoute)
|
|
992
|
+
routes.route("/", GET, myview)
|
|
993
|
+
|
|
994
|
+
assert list(routes.get_route_traversals("/", GET)) == [
|
|
995
|
+
tms.InstanceOf(RouteTraversal, route=tms.InstanceOf(self.CustomRoute))
|
|
996
|
+
]
|
|
997
|
+
|
|
998
|
+
def test_it_uses_route_class_in_decorator(self):
|
|
999
|
+
routes = RouteCollection(route_class=self.CustomRoute)
|
|
1000
|
+
|
|
1001
|
+
@routes.route("/", GET)
|
|
1002
|
+
def myview():
|
|
1003
|
+
pass
|
|
1004
|
+
|
|
1005
|
+
assert list(routes.get_route_traversals("/", GET)) == [
|
|
1006
|
+
tms.InstanceOf(RouteTraversal, route=tms.InstanceOf(self.CustomRoute))
|
|
1007
|
+
]
|
|
1008
|
+
|
|
1009
|
+
def test_custom_route_class_survives_include(self):
|
|
1010
|
+
routes = RouteCollection(route_class=self.CustomRoute)
|
|
1011
|
+
|
|
1012
|
+
@routes.route("/", GET)
|
|
1013
|
+
def myview():
|
|
1014
|
+
pass
|
|
1015
|
+
|
|
1016
|
+
routes2 = RouteCollection()
|
|
1017
|
+
routes2.include("/incl", routes)
|
|
1018
|
+
|
|
1019
|
+
assert list(routes2.get_route_traversals("/incl/", GET)) == [
|
|
1020
|
+
tms.InstanceOf(RouteTraversal, route=tms.InstanceOf(self.CustomRoute))
|
|
1021
|
+
]
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
class TestRouteTraversal(object):
|
|
1025
|
+
routes = RouteCollection()
|
|
1026
|
+
inner = RouteCollection()
|
|
1027
|
+
inner2 = RouteCollection()
|
|
1028
|
+
|
|
1029
|
+
view = object()
|
|
1030
|
+
inner2.route("/<i:int>", GET=view, name="c")
|
|
1031
|
+
inner.delegate("/<i:int>", inner2, name="b")
|
|
1032
|
+
routes.delegate("/<i:int>", lambda i, r=inner: r, dynamic=True, name="a")
|
|
1033
|
+
|
|
1034
|
+
def test_it_reconstructs_the_path(self):
|
|
1035
|
+
traversal = next(self.routes.get_route_traversals("/1/2/3", GET))
|
|
1036
|
+
|
|
1037
|
+
# Do we get the original path back?
|
|
1038
|
+
assert traversal.build_path() == "/1/2/3"
|
|
1039
|
+
|
|
1040
|
+
def test_it_modifies_the_path(self):
|
|
1041
|
+
traversal = next(self.routes.get_route_traversals("/1/2/3", GET))
|
|
1042
|
+
|
|
1043
|
+
# Modify the traversal and check we get a modified path back
|
|
1044
|
+
assert traversal.replace("a", {"i": 0}).build_path() == "/0/2/3"
|
|
1045
|
+
|
|
1046
|
+
assert traversal.replace("b", {"i": 0}).build_path() == "/1/0/3"
|
|
1047
|
+
assert traversal.replace("a:b", {"i": 0}).build_path() == "/1/0/3"
|
|
1048
|
+
|
|
1049
|
+
assert traversal.replace("a:b:c", {"i": 0}).build_path() == "/1/2/0"
|
|
1050
|
+
assert traversal.replace("a:c", {"i": 0}).build_path() == "/1/2/0"
|
|
1051
|
+
assert traversal.replace("c", {"i": 0}).build_path() == "/1/2/0"
|
|
1052
|
+
assert traversal.replace(self.view, {"i": 0}).build_path() == "/1/2/0"
|
|
1053
|
+
|
|
1054
|
+
# Passing no arguments should create a new traversal
|
|
1055
|
+
assert traversal.replace("a") is not traversal
|
|
1056
|
+
|
|
1057
|
+
# ...identical to the original
|
|
1058
|
+
assert traversal.replace("a") == traversal
|
|
1059
|
+
|
|
1060
|
+
def test_replace_raises_routenotfound(self):
|
|
1061
|
+
traversal = next(self.routes.get_route_traversals("/1/2/3", GET))
|
|
1062
|
+
with pytest.raises(RouteNotFound):
|
|
1063
|
+
traversal.replace("x")
|
|
1064
|
+
|
|
1065
|
+
with pytest.raises(RouteNotFound):
|
|
1066
|
+
traversal.replace("a:x")
|
|
1067
|
+
|
|
1068
|
+
with pytest.raises(RouteNotFound):
|
|
1069
|
+
traversal.replace("b:a")
|
|
1070
|
+
|
|
1071
|
+
with pytest.raises(RouteNotFound):
|
|
1072
|
+
traversal.replace("a:b:c:d")
|
|
1073
|
+
|
|
1074
|
+
with pytest.raises(RouteNotFound):
|
|
1075
|
+
traversal.replace("")
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
class TestRouteCache(object):
|
|
1079
|
+
def test_it_raises_exceptions(self):
|
|
1080
|
+
class CustomException(Exception):
|
|
1081
|
+
pass
|
|
1082
|
+
|
|
1083
|
+
@register_converter("raises_exception")
|
|
1084
|
+
class RaisesException(Converter):
|
|
1085
|
+
"""
|
|
1086
|
+
Convert latitude-longitude pairs into a ``LatLng`` named tuple.
|
|
1087
|
+
"""
|
|
1088
|
+
|
|
1089
|
+
pattern = r".*"
|
|
1090
|
+
|
|
1091
|
+
def from_string(self, s):
|
|
1092
|
+
raise CustomException()
|
|
1093
|
+
|
|
1094
|
+
rc = RouteCollection(
|
|
1095
|
+
[
|
|
1096
|
+
Route("/bacon", GET, Response),
|
|
1097
|
+
Route("/eggs/<eggs:raises_exception>", GET, Response),
|
|
1098
|
+
],
|
|
1099
|
+
cache=True,
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
# Prime the cache
|
|
1103
|
+
list(rc.get_route_traversals("/bacon", GET))
|
|
1104
|
+
|
|
1105
|
+
with pytest.raises(CustomException):
|
|
1106
|
+
list(rc.get_route_traversals("/eggs/x", GET))
|
|
1107
|
+
|
|
1108
|
+
with pytest.raises(CustomException):
|
|
1109
|
+
list(rc.get_route_traversals("/eggs/x", GET))
|
|
1110
|
+
|
|
1111
|
+
# Check a subsequent call to get_routes doesn't hang onto the exception
|
|
1112
|
+
list(rc.get_route_traversals("/bacon", GET))
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
class TestRouteAll:
|
|
1116
|
+
|
|
1117
|
+
def test_route_all_matches_on_separator(self):
|
|
1118
|
+
def view():
|
|
1119
|
+
return Response()
|
|
1120
|
+
|
|
1121
|
+
app = FrescoApp()
|
|
1122
|
+
app.route_all("/x", GET, view)
|
|
1123
|
+
|
|
1124
|
+
assert len(list(app.get_route_traversals("/x", GET))) == 1
|
|
1125
|
+
assert len(list(app.get_route_traversals("/x/y", GET))) == 1
|
|
1126
|
+
assert len(list(app.get_route_traversals("/xy", GET))) == 0
|