fresco 3.3.2__py3-none-any.whl → 3.3.4__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.
@@ -0,0 +1,1038 @@
1
+ # Copyright 2015 Oliver Cope
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ from wsgiref.util import setup_testing_defaults
16
+
17
+ from unittest.mock import Mock, call
18
+ import pytest
19
+
20
+ from fresco import Request, Response
21
+ from fresco import FrescoApp
22
+ from fresco import Route
23
+ from fresco import urlfor
24
+ from fresco import context
25
+ from fresco.routing import GET
26
+ from fresco.routing import POST
27
+ from fresco.routing import PUT
28
+ from fresco.routing import DELETE
29
+ from fresco.util.wsgi import ClosingIterator, make_environ
30
+ from . import fixtures
31
+
32
+
33
+ def create_env(**env):
34
+ setup_testing_defaults(env)
35
+ return env
36
+
37
+
38
+ class CustomException(Exception):
39
+ pass
40
+
41
+
42
+ class TestFrescoApp(object):
43
+ def test_route_operates_as_a_decorator(self):
44
+ app = FrescoApp()
45
+
46
+ @app.route("/", GET)
47
+ def view():
48
+ return Response([b"ok"])
49
+
50
+ with app.requestcontext("/"):
51
+ assert list(app.view().content_iterator) == [b"ok"]
52
+
53
+ def test_route_operates_as_a_function(self):
54
+ def view():
55
+ return Response([b"ok"])
56
+
57
+ app = FrescoApp()
58
+ app.route("/", GET, view)
59
+ with app.requestcontext("/"):
60
+ assert list(app.view().content_iterator) == [b"ok"]
61
+
62
+ def test_route_returns_route_instance(self):
63
+ def view():
64
+ return Response([b"ok"])
65
+
66
+ app = FrescoApp()
67
+ assert isinstance(app.route("/", GET, view), Route)
68
+
69
+ def test_route_http_methods(self):
70
+ def view():
71
+ return Response([context.request.environ["REQUEST_METHOD"]])
72
+
73
+ app = FrescoApp()
74
+ app.route("/get", GET, view)
75
+ app.route("/post", POST, view)
76
+
77
+ with app.requestcontext("/get", REQUEST_METHOD="GET"):
78
+ assert app.view().status_code == 200
79
+
80
+ with app.requestcontext("/get", REQUEST_METHOD="POST"):
81
+ assert app.view().status_code == 405
82
+
83
+ with app.requestcontext("/post", REQUEST_METHOD="GET"):
84
+ assert app.view().status_code == 405
85
+
86
+ with app.requestcontext("/post", REQUEST_METHOD="POST"):
87
+ assert app.view().status_code == 200
88
+
89
+ def test_HEAD_request_delegated_to_GET_view(self):
90
+ app = FrescoApp()
91
+
92
+ @app.route("/", GET)
93
+ def view():
94
+ return Response([], x_original_view="GET")
95
+
96
+ with app.requestcontext("/", REQUEST_METHOD="HEAD"):
97
+ assert app.view().get_header("X-Original-View") == "GET"
98
+
99
+ def test_NotFound_observed_when_raised_in_handler(self):
100
+ def app1():
101
+ from fresco.exceptions import NotFound
102
+
103
+ if "foo" in context.request.path_info:
104
+ raise NotFound()
105
+ return Response([b"app1"])
106
+
107
+ def app2():
108
+ return Response([b"app2"])
109
+
110
+ app = FrescoApp()
111
+ app.route_all("/", GET, app1)
112
+ app.route_all("/", GET, app2)
113
+
114
+ with app.requestcontext("/bar"):
115
+ assert list(app.view().content_iterator) == [b"app1"]
116
+
117
+ with app.requestcontext("/foo"):
118
+ assert list(app.view().content_iterator) == [b"app2"]
119
+
120
+ def test_NotFound_final_observed_when_raised_in_handler(self):
121
+ def app1():
122
+ from fresco.exceptions import NotFound
123
+
124
+ if "foo" in context.request.path_info:
125
+ raise NotFound(final=True)
126
+ return Response([b"app1"])
127
+
128
+ def app2():
129
+ return Response([b"app2"])
130
+
131
+ app = FrescoApp()
132
+ app.route_all("/", GET, app1)
133
+ app.route_all("/", GET, app2)
134
+
135
+ with app.requestcontext("/bar"):
136
+ assert list(app.view().content_iterator) == [b"app1"]
137
+
138
+ with app.requestcontext("/foo/"):
139
+ assert app.view().status_code == 404
140
+
141
+ def test_apps_called_in_correct_order(self):
142
+ def view(value=""):
143
+ return Response([value])
144
+
145
+ app = FrescoApp()
146
+ app.route_all("/f", GET, view, value=b"foo")
147
+ app.route_all("/", GET, view, value=b"bar")
148
+
149
+ with app.requestcontext("/f/bar"):
150
+ assert list(app.view().content_iterator) == [b"foo"]
151
+
152
+ with app.requestcontext("/b/bar"):
153
+ assert list(app.view().content_iterator) == [b"bar"]
154
+
155
+ def test_wsgi_app_handles_response_exceptions(self):
156
+ from fresco.exceptions import NotFound
157
+
158
+ def view():
159
+ raise NotFound()
160
+
161
+ app = FrescoApp()
162
+ app.route("/", GET, view)
163
+
164
+ with app.requestcontext("/"):
165
+ assert app.view().status_code == 404
166
+
167
+ def test_route_wsgi_app(self):
168
+ def wsgiapp(environ, start_response):
169
+ start_response("200 OK", [("Content-Type", "application/x-pachyderm")])
170
+ return [b"pretty pink elephants"]
171
+
172
+ app = FrescoApp()
173
+ app.route_wsgi("/", wsgiapp)
174
+
175
+ with app.requestcontext("/"):
176
+ assert list(app.view().content_iterator) == [b"pretty pink elephants"]
177
+ assert app.view().get_header("Content-Type") == "application/x-pachyderm"
178
+
179
+ def test_get_methods_matches_on_path(self):
180
+ app = FrescoApp()
181
+ app.route("/1", POST, lambda: None)
182
+ app.route("/1", PUT, lambda: None)
183
+ app.route("/2", GET, lambda: None)
184
+ app.route("/2", DELETE, lambda: None)
185
+
186
+ with app.requestcontext() as c:
187
+ assert app.get_methods(c.request, "/1") == set([POST, PUT])
188
+
189
+ with app.requestcontext() as c:
190
+ assert app.get_methods(c.request, "/2") == set([GET, DELETE])
191
+
192
+ def test_get_methods_matches_on_predicate(self):
193
+ p1 = Mock(return_value=True)
194
+ p2 = Mock(return_value=False)
195
+
196
+ app = FrescoApp()
197
+ app.route("/", POST, lambda: None, predicate=p1)
198
+ app.route("/", PUT, lambda: None, predicate=p2)
199
+
200
+ with app.requestcontext("/") as c:
201
+ assert app.get_methods(c.request, "/") == set([POST])
202
+ assert p1.call_args_list == [call(c.request)]
203
+ assert p2.call_args_list == [call(c.request)]
204
+
205
+ def test_invalid_path_encoding_triggers_bad_request(self):
206
+ app = FrescoApp()
207
+ with app.requestcontext(PATH_INFO=fixtures.misquoted_wsgi_unicode_path):
208
+ assert app.view().status_code == 400
209
+
210
+ def test_remove_middleware(self):
211
+ app = FrescoApp()
212
+ m1 = Mock()
213
+ m2 = Mock()
214
+ app.add_middleware(m1)
215
+ app.add_middleware(m2)
216
+ app.remove_middleware(m1)
217
+ app(create_env(), Mock()).close()
218
+ assert m1.call_count == 0
219
+ assert m2.call_count == 1
220
+
221
+ def test_insert_middleware(self):
222
+ calls = []
223
+
224
+ def middleware(app, name):
225
+ def middleware(env, start_response):
226
+ calls.append(name)
227
+ return app(env, start_response)
228
+
229
+ return middleware
230
+
231
+ app = FrescoApp()
232
+ app.add_middleware(middleware, "venus")
233
+ app.add_middleware(middleware, "mercury")
234
+ app.insert_middleware(0, middleware, "earth")
235
+
236
+ # Put a request through app to initialize the WSGI stack
237
+ with app.requestcontext("/"):
238
+ app.view()
239
+
240
+ # Middleware is called from the outside-in, so the item inserted in
241
+ # position 0 is last to be called
242
+ assert calls == ["mercury", "venus", "earth"]
243
+
244
+ def test_routing_falls_though_with_fallthrough_on(self):
245
+ app = FrescoApp()
246
+ app.route(
247
+ "/",
248
+ GET=lambda: Response(status="204 No Content"),
249
+ fallthrough_on={204},
250
+ )
251
+ app.route("/", GET=lambda: Response(status="200 OK"))
252
+
253
+ with app.requestcontext("/"):
254
+ response = app.view()
255
+
256
+ assert response.status_code == 200
257
+
258
+ def test_fallthrough_on_returns_last_error(self):
259
+ app = FrescoApp()
260
+ app.route("/", GET=lambda: Response(status="499"), fallthrough_on={499})
261
+
262
+ with app.requestcontext("/"):
263
+ response = app.view()
264
+ assert response.status_code == 499
265
+
266
+
267
+ class TestAppHooks(object):
268
+ def test_process_request_continues_on_none(self):
269
+ app = FrescoApp()
270
+ view = Mock(return_value=Response())
271
+ app.route("/", GET, view)
272
+
273
+ @app.process_request
274
+ def process_request(request):
275
+ assert isinstance(request, Request)
276
+ return None
277
+
278
+ with app.requestcontext("/"):
279
+ app.view()
280
+ assert view.call_count == 1
281
+
282
+ def test_process_request_replaces_response(self):
283
+ app = FrescoApp()
284
+ view = Mock(return_value=Response())
285
+ app.route("/", GET, view)
286
+ new_response = Response()
287
+
288
+ @app.process_request
289
+ def process_request(request):
290
+ return new_response
291
+
292
+ with app.requestcontext("/"):
293
+ assert app.view() is new_response
294
+ assert view.call_count == 0
295
+
296
+ def test_process_request_once_only_called_once(self):
297
+ app = FrescoApp()
298
+ view = Mock(return_value=Response())
299
+ app.route("/", GET, view)
300
+ new_response = Response()
301
+
302
+ @app.process_request_once
303
+ def process_request(request):
304
+ return new_response
305
+
306
+ with app.requestcontext("/"):
307
+ assert app.view() is new_response
308
+ assert view.call_count == 0
309
+ assert app.view() is not new_response
310
+ assert view.call_count == 1
311
+
312
+ def test_process_view_continues_on_none(self):
313
+ app = FrescoApp()
314
+ view = Mock(return_value=Response())
315
+ app.route("/", GET, view)
316
+
317
+ @app.process_view
318
+ def process_view(request, v, args, kwargs):
319
+ assert v is view
320
+ return None
321
+
322
+ with app.requestcontext("/"):
323
+ app.view()
324
+ assert view.call_count == 1
325
+
326
+ def test_process_view_replaces_view(self):
327
+ app = FrescoApp()
328
+ view = Mock(return_value=Response())
329
+ view2 = Mock(return_value=Response())
330
+ app.route("/", GET, view)
331
+
332
+ @app.process_view
333
+ def process_view(request, v, args, kwargs):
334
+ assert v is view
335
+ return view2
336
+
337
+ with app.requestcontext("/"):
338
+ app.view()
339
+ assert view.call_count == 0
340
+ assert view2.call_count == 1
341
+
342
+ def test_process_view_replaces_response(self):
343
+ app = FrescoApp()
344
+ new_response = Response()
345
+ view = Mock(return_value=Response())
346
+ app.route("/", GET, view)
347
+
348
+ @app.process_view
349
+ def process_view(request, v, args, kwargs):
350
+ assert v is view
351
+ return new_response
352
+
353
+ with app.requestcontext("/"):
354
+ assert app.view() is new_response
355
+ assert view.call_count == 0
356
+
357
+ def test_process_response_continues_on_none(self):
358
+ app = FrescoApp()
359
+ response = Response()
360
+ view = Mock(return_value=response)
361
+ app.route("/", GET, view)
362
+
363
+ @app.process_response
364
+ def process_response(req, res):
365
+ assert res is response
366
+ return None
367
+
368
+ with app.requestcontext("/"):
369
+ assert app.view() is response
370
+
371
+ def test_process_response_replaces_response(self):
372
+ app = FrescoApp()
373
+ new_response = Response()
374
+ view = Mock(return_value=Response())
375
+ app.route("/", GET, view)
376
+
377
+ @app.process_response
378
+ def process_response(req, res):
379
+ return new_response
380
+
381
+ with app.requestcontext("/"):
382
+ assert app.view() is new_response
383
+
384
+ def test_process_http_error_response_continues_on_none(self):
385
+ app = FrescoApp()
386
+ response = Response.not_found()
387
+ view = Mock(return_value=response)
388
+ app.route("/", GET, view)
389
+
390
+ @app.process_http_error_response
391
+ def process_http_error_response(req, res):
392
+ return None
393
+
394
+ with app.requestcontext("/"):
395
+ assert app.view() is response
396
+
397
+ def test_process_http_error_response_replaces_response(self):
398
+ app = FrescoApp()
399
+ new_response = Response()
400
+ view = Mock(return_value=Response.not_found())
401
+ app.route("/", GET, view)
402
+
403
+ @app.process_http_error_response
404
+ def process_http_error_response(req, res):
405
+ return new_response
406
+
407
+ with app.requestcontext("/"):
408
+ assert app.view() is new_response
409
+
410
+ def test_process_http_error_response_called_on_internal_errors(self):
411
+ app = FrescoApp()
412
+ new_response = Response()
413
+ view = Mock(return_value=Response())
414
+ app.route("/", GET, view)
415
+
416
+ hook = Mock(return_value=new_response)
417
+ app.process_http_error_response(hook)
418
+
419
+ # POSTing to a GET route should generate 405 method not supported
420
+ with app.requestcontext_post("/"):
421
+ assert app.view() is new_response
422
+ hook_args, hook_kwargs = hook.call_args
423
+ assert hook_args[1].status_code == 405
424
+
425
+ # Should generate a 404 not found
426
+ with app.requestcontext_post("/asdf"):
427
+ assert app.view() is new_response
428
+ hook_args, hook_kwargs = hook.call_args
429
+ assert hook_args[1].status_code == 404
430
+
431
+ def test_process_http_error_response_not_called_on_success(self):
432
+ app = FrescoApp()
433
+ ok_response = Response([""], status="200 OK")
434
+ redirect_response = Response([""], status="302 Found", location="/")
435
+ ok_view = Mock(return_value=ok_response)
436
+ redirect_view = Mock(return_value=redirect_response)
437
+ app.route("/ok", GET, ok_view)
438
+ app.route("/redirect", GET, redirect_view)
439
+
440
+ @app.process_http_error_response
441
+ def process_http_error_response(req, res):
442
+ assert False, "Hook should not be called"
443
+
444
+ with app.requestcontext("/ok"):
445
+ assert app.view() is ok_response
446
+
447
+ with app.requestcontext("/redirect"):
448
+ assert app.view() is redirect_response
449
+
450
+ def test_process_http_error_response_can_be_associated_with_status(self):
451
+ app = FrescoApp()
452
+ process_404 = Mock(return_value=None)
453
+ process_500 = Mock(return_value=None)
454
+ app.route("/404", GET, Response.not_found)
455
+ app.route("/500", GET, Response.internal_server_error)
456
+
457
+ app.process_http_error_response(404)(process_404)
458
+ app.process_http_error_response(500)(process_500)
459
+
460
+ with app.requestcontext("/404"):
461
+ app.view()
462
+ assert process_500.call_count == 0
463
+ assert process_404.call_count == 1
464
+
465
+ with app.requestcontext("/500"):
466
+ app.view()
467
+ assert process_500.call_count == 1
468
+ assert process_404.call_count == 1
469
+
470
+ def test_process_teardown_called_on_teardown(self):
471
+ app = FrescoApp()
472
+ fn = Mock()
473
+ app.process_teardown(fn)
474
+ with app.requestcontext("/"):
475
+ app.view()
476
+ assert fn.call_count == 0
477
+ assert fn.call_count == 1
478
+
479
+ def test_adding_process_teardown_raises_error_after_app_starts(self):
480
+ app = FrescoApp()
481
+ with app.requestcontext("/") as c:
482
+ iterator = app(c.request.environ, Mock())
483
+ iterator.close()
484
+ with pytest.raises(Exception):
485
+ app.process_teardown(Mock())
486
+
487
+ def test_exception_in_process_hooks_do_not_stop_processing(self):
488
+ for hook, view in [
489
+ ("process_request", Response),
490
+ ("process_view", Response),
491
+ ("process_response", Response),
492
+ ("process_exception", Mock(side_effect=CustomException())),
493
+ ("process_http_error_response", Response.not_found),
494
+ ("process_teardown", Response),
495
+ ]:
496
+ faulty_hook = Mock(side_effect=CustomException())
497
+ process_teardown = Mock(return_value=None)
498
+
499
+ app = FrescoApp()
500
+ app.route("/", GET, view) # type: ignore
501
+ getattr(app, hook)(faulty_hook)
502
+ app.process_teardown(process_teardown)
503
+
504
+ with app.requestcontext("/"):
505
+ app.view()
506
+
507
+ assert faulty_hook.call_count == 1, hook
508
+ assert process_teardown.call_count == 1
509
+
510
+ def test_it_raises_exception_in_route_resolution(self):
511
+ from fresco.routing import register_converter, StrConverter
512
+
513
+ @register_converter("bugs")
514
+ class Converter(StrConverter):
515
+ def from_string(self, s):
516
+ raise AssertionError("Oops!")
517
+
518
+ app = FrescoApp()
519
+ app.route("/<x:bugs>", GET=lambda: Response)
520
+ with app.requestcontext("/abc"):
521
+ with pytest.raises(AssertionError):
522
+ app.view()
523
+
524
+
525
+ class TestProcessException(object):
526
+ def exception_view(self):
527
+ raise CustomException()
528
+
529
+ def iterator_exception_view(self):
530
+ def content_iterator():
531
+ yield b"fish"
532
+ raise CustomException()
533
+ yield b"chips"
534
+
535
+ return Response(content_iterator())
536
+
537
+ def test_it_reraises_if_no_error_handlers_installed(self):
538
+ app = FrescoApp()
539
+
540
+ app.route("/", GET, self.exception_view)
541
+ with app.requestcontext("/"):
542
+ with pytest.raises(CustomException):
543
+ app.view()
544
+
545
+ def test_it_reraises_if_no_500_error_handler_installed(self):
546
+ app = FrescoApp()
547
+ app.process_http_error_response(lambda req, res: None, 404)
548
+
549
+ app.route("/", GET, self.exception_view)
550
+ with app.requestcontext("/"):
551
+ with pytest.raises(CustomException):
552
+ app.view()
553
+
554
+ def test_it_calls_process_exception_handlers(self):
555
+ app = FrescoApp()
556
+
557
+ app.route("/", GET, self.exception_view)
558
+ new_response = Response()
559
+ process_exception = Mock(return_value=new_response)
560
+ app.process_exception(process_exception)
561
+
562
+ with app.requestcontext("/"):
563
+ assert app.view() is new_response
564
+ assert process_exception.call_count == 1
565
+
566
+ def test_it_returns_first_not_none_process_exception_handler(self):
567
+ app = FrescoApp()
568
+
569
+ app.route("/", GET, self.exception_view)
570
+ new_response1 = Response()
571
+ new_response2 = Response()
572
+ process_exception1 = Mock(return_value=None)
573
+ process_exception2 = Mock(return_value=new_response1)
574
+ process_exception3 = Mock(return_value=new_response2)
575
+ app.process_exception(process_exception1)
576
+ app.process_exception(process_exception2)
577
+ app.process_exception(process_exception3)
578
+
579
+ with app.requestcontext("/"):
580
+ assert app.view() is new_response1
581
+ assert process_exception1.call_count == 1
582
+ assert process_exception2.call_count == 1
583
+ assert process_exception3.call_count == 0
584
+
585
+ def test_it_calls_process_exception_handlers_in_content_iteration(self):
586
+ app = FrescoApp()
587
+
588
+ app.route("/", GET, self.iterator_exception_view)
589
+ process_exception = Mock(return_value=None)
590
+ app.process_exception(process_exception)
591
+
592
+ with app.requestcontext("/") as c:
593
+ content_iterator = app(c.request.environ, Mock())
594
+ assert process_exception.call_count == 0
595
+ list(content_iterator)
596
+ assert process_exception.call_count == 1
597
+
598
+ def test_it_reraises_when_exc_info_returned(self):
599
+ app = FrescoApp()
600
+
601
+ app.route("/", GET, self.exception_view)
602
+ app.process_exception(lambda req, exc_info: exc_info)
603
+
604
+ with app.requestcontext("/"):
605
+ with pytest.raises(CustomException):
606
+ app.view()
607
+
608
+ def test_it_calls_error_handlers_in_middleware(self):
609
+ """
610
+ If a faulty middleware layer raises an exception it should
611
+ trigger error handling.
612
+ """
613
+
614
+ def faulty_middleware(app):
615
+ def middleware(environ, start_response):
616
+ raise CustomException()
617
+
618
+ return middleware
619
+
620
+ app = FrescoApp([Route("/", GET, lambda: Response("foo"))])
621
+ app.add_middleware(faulty_middleware)
622
+ process_exception = Mock(return_value=None)
623
+ app.process_exception(process_exception)
624
+ process_http_error_response = Mock(return_value=Response(b"bar"))
625
+ app.process_http_error_response(process_http_error_response, 500)
626
+
627
+ content = list(app(make_environ(), Mock()))
628
+ assert process_exception.call_count == 1
629
+ assert process_http_error_response.call_count == 1
630
+ assert content == [b"bar"]
631
+
632
+ def test_it_associates_with_given_exception(self):
633
+ def exception_view():
634
+ raise ValueError()
635
+
636
+ app = FrescoApp()
637
+ app.route("/", GET, exception_view)
638
+ process_TypeError = Mock(return_value=None)
639
+ process_ValueError = Mock(return_value=None)
640
+ app.process_exception(TypeError)(process_TypeError)
641
+ app.process_exception(ValueError)(process_ValueError)
642
+
643
+ with app.requestcontext("/"):
644
+ app.view()
645
+ assert process_TypeError.call_count == 0
646
+ assert process_ValueError.call_count == 1
647
+
648
+ def test_it_calls_http_error_handler(self):
649
+ app = FrescoApp()
650
+
651
+ app.route("/", GET, self.exception_view)
652
+ new_response = Response()
653
+ process_http_error_response = Mock(return_value=new_response)
654
+ app.process_http_error_response(process_http_error_response)
655
+
656
+ with app.requestcontext("/"):
657
+ assert app.view() is new_response
658
+ assert process_http_error_response.call_count == 1
659
+ req, res = process_http_error_response.call_args[0]
660
+ assert res.status_code == 500
661
+
662
+
663
+ class TestIncludeApp(object):
664
+ def test_it_routes_to_an_included_app(self):
665
+ app = FrescoApp()
666
+
667
+ @app.route("/", GET)
668
+ def view():
669
+ return Response([b"ok"])
670
+
671
+ app2 = FrescoApp()
672
+ app2.include("/app1", app)
673
+
674
+ with app2.requestcontext("/"):
675
+ assert app2.view().status_code == 404
676
+
677
+ with app2.requestcontext("/app1/"):
678
+ assert list(app2.view().content_iterator) == [b"ok"]
679
+
680
+ def test_included_app_can_use_urlfor(self):
681
+ def view():
682
+ url = urlfor(view)
683
+ return Response(url)
684
+
685
+ app = FrescoApp()
686
+ app.route("/", GET, view)
687
+ app2 = FrescoApp()
688
+ app2.include("/app1", app)
689
+
690
+ with app2.requestcontext("/app1/"):
691
+ assert list(app2.view().content_iterator) == [b"http://localhost/app1/"]
692
+
693
+
694
+ class TestTrailingSlashes(object):
695
+ """\
696
+ The general principle is that if a GET or HEAD request is received for a
697
+ URL without a trailing slash and no match is found, the app will look for a
698
+ URL with a trailing slash, and redirect the client if such a route exists.
699
+ """
700
+
701
+ def test_no_trailing_slash(self):
702
+ def foo():
703
+ return Response(["foo"])
704
+
705
+ app = FrescoApp()
706
+ app.route("/foo/", GET, foo)
707
+ app.route("/", GET, foo)
708
+
709
+ with app.requestcontext("/foo"):
710
+ assert app.view().status_code == 301
711
+ assert app.view().get_header("location") == "http://localhost/foo/"
712
+
713
+ with app.requestcontext(""):
714
+ assert app.view().status_code == 301
715
+ assert app.view().get_header("location") == "http://localhost/"
716
+
717
+
718
+ class TestViewCollection(object):
719
+ def test_appdef(self):
720
+ app = FrescoApp()
721
+ app.include("/", fixtures.CBV("bananas!"))
722
+ with app.requestcontext("/"):
723
+ assert list(app.view().content_iterator) == [b"bananas!"]
724
+
725
+ def test_appdef_url_generation(self):
726
+ foo = fixtures.CBV("foo!")
727
+ bar = fixtures.CBV("bar!")
728
+ baz = fixtures.CBV("baz!")
729
+
730
+ app = FrescoApp(views=foo)
731
+ app.include("/bar", bar)
732
+ app.include("/baz", baz)
733
+
734
+ with app.requestcontext():
735
+ assert urlfor(foo.index_html) == "http://localhost/"
736
+ assert urlfor(bar.index_html) == "http://localhost/bar/"
737
+ assert urlfor(baz.index_html) == "http://localhost/baz/"
738
+
739
+ def test_instance_available_in_context(self):
740
+ s = []
741
+
742
+ class MyCBV(fixtures.CBV):
743
+ def index_html(self):
744
+ from fresco import context
745
+
746
+ s.append(context.view_self)
747
+ return Response([])
748
+
749
+ instance = MyCBV("foo!")
750
+ app = FrescoApp(views=instance)
751
+
752
+ with app.requestcontext("/"):
753
+ app.view()
754
+ assert s[0] is instance
755
+
756
+
757
+ class TestContextAttributes(object):
758
+ def test_app_is_set(self):
759
+ def check_app(expected):
760
+ assert context.app is expected
761
+ return Response([])
762
+
763
+ app1 = FrescoApp()
764
+ app2 = FrescoApp()
765
+
766
+ app1.route("/", GET, check_app, {"expected": app1})
767
+ app2.route("/", GET, check_app, {"expected": app2})
768
+
769
+ with app1.requestcontext("/"):
770
+ app1.view()
771
+
772
+ with app2.requestcontext("/"):
773
+ app2.view()
774
+
775
+
776
+ class TestAppRequestContext(object):
777
+ def _middleware(self, app):
778
+ def middleware(environ, start_response):
779
+ environ["sausages"] = 1
780
+ return app(environ, start_response)
781
+
782
+ return middleware
783
+
784
+ def test_creates_isolated_context(self):
785
+ app = FrescoApp()
786
+ with app.requestcontext():
787
+ context.request = "foo"
788
+
789
+ with app.requestcontext():
790
+ context.request = "bar"
791
+ assert context.request == "bar"
792
+
793
+ assert context.request == "foo"
794
+
795
+ def test_parses_full_url(self):
796
+ with FrescoApp().requestcontext("https://arthur@example.org:123/?x=y"):
797
+ assert context.request.environ["HTTPS"] == "on"
798
+ assert context.request.environ["REMOTE_USER"] == "arthur"
799
+ assert context.request.environ["HTTP_HOST"] == "example.org:123"
800
+ assert context.request.environ["SCRIPT_NAME"] == ""
801
+ assert context.request.environ["PATH_INFO"] == "/"
802
+ assert context.request.environ["QUERY_STRING"] == "x=y"
803
+
804
+ def test_it_posts_data(self):
805
+ with FrescoApp().requestcontext_post(data={"foo": "bar"}) as c:
806
+ assert c.request.method == "POST"
807
+ assert list(c.request.form.allitems()) == [("foo", "bar")]
808
+ assert list(c.request.query.allitems()) == []
809
+
810
+ def test_it_puts_data(self):
811
+ with FrescoApp().requestcontext_put(data={"foo": "bar"}) as c:
812
+ assert c.request.method == "PUT"
813
+ assert list(c.request.form.allitems()) == [("foo", "bar")]
814
+
815
+ def test_it_posts_data_multipart(self):
816
+ with FrescoApp().requestcontext_post(
817
+ files=[("foo", "foo.txt", "text/plain", b"bar")]
818
+ ) as c:
819
+ assert c.request.files["foo"].filename == "foo.txt"
820
+ assert c.request.files["foo"].file.read() == b"bar"
821
+
822
+ def test_it_posts_raw_data(self):
823
+ with FrescoApp().requestcontext_post(
824
+ data=b"xyzzy", content_type="text/spell"
825
+ ) as c:
826
+ assert c.request.body == "xyzzy"
827
+
828
+ def test_it_converts_header_names(self):
829
+ with FrescoApp().requestcontext(user_agent="foo"):
830
+ assert context.request.environ["HTTP_USER_AGENT"] == "foo"
831
+
832
+ def test_it_converts_wsgi_keys(self):
833
+ # CONTENT_TYPE is both a WSGI core environ key and a standard request
834
+ # header. The WSGI key must win.
835
+ with FrescoApp().requestcontext(content_type="foo"):
836
+ assert context.request.environ["CONTENT_TYPE"] == "foo"
837
+
838
+ def test_it_invokes_middleware(self):
839
+ app = FrescoApp()
840
+ app.add_middleware(self._middleware)
841
+ with app.requestcontext() as c:
842
+ assert "sausages" in c.request.environ
843
+
844
+ def test_it_skips_middleware(self):
845
+ app = FrescoApp()
846
+ app.add_middleware(self._middleware)
847
+ with app.requestcontext(middleware=False) as c:
848
+ assert "sausages" not in c.request.environ
849
+
850
+ def test_it_closes_middleware(self):
851
+ close = Mock()
852
+
853
+ def middleware(app):
854
+ def middleware(environ, start_response):
855
+ return ClosingIterator(app(environ, start_response), close)
856
+
857
+ return middleware
858
+
859
+ app = FrescoApp()
860
+ app.add_middleware(middleware)
861
+ with app.requestcontext():
862
+ pass
863
+ assert close.call_count == 1
864
+
865
+ def test_it_calls_first_iteration(self):
866
+ """
867
+ Some middleware waits until the first iteration to do things, so make
868
+ sure we trigger this
869
+ """
870
+ from itertools import count
871
+
872
+ counter = count()
873
+
874
+ def middleware(app):
875
+ def middleware(environ, start_response):
876
+ iterator = app(environ, start_response)
877
+ for item in iterator:
878
+ yield next(counter)
879
+
880
+ return middleware
881
+
882
+ app = FrescoApp()
883
+ app.add_middleware(middleware)
884
+ with app.requestcontext():
885
+ assert next(counter) == 1
886
+
887
+ def test_it_calls_late_added_middleware(self):
888
+ calls = []
889
+
890
+ def middleware(app):
891
+ calls.append("middleware initialized")
892
+
893
+ def middleware(env, start_response):
894
+ calls.append("middleware called")
895
+ return app(env, start_response)
896
+
897
+ return middleware
898
+
899
+ app = FrescoApp()
900
+ app.route("/", GET, lambda: Response())
901
+
902
+ # Put a request through app to initialize the WSGI stack
903
+ with app.requestcontext("/"):
904
+ app.view()
905
+
906
+ app.add_middleware(middleware)
907
+
908
+ # The request should cause the WSGI app to be rebuilt with the new
909
+ # middleware included
910
+ with app.requestcontext("/"):
911
+ app.view()
912
+
913
+ assert calls == ["middleware initialized", "middleware called"]
914
+
915
+ def test_it_passes_a_valid_start_response_to_middleware(self):
916
+ calls = []
917
+
918
+ def middleware(app):
919
+ def middleware(env, start_response):
920
+ calls.append("middleware called")
921
+ write = start_response("200 OK", [])
922
+ assert callable(write)
923
+ return app(env, lambda *a, **k: lambda s: None)
924
+
925
+ return middleware
926
+
927
+ app = FrescoApp()
928
+ app.add_middleware(middleware)
929
+ app.route("/", GET, lambda: Response())
930
+
931
+ with app.requestcontext("/"):
932
+ app.view()
933
+
934
+ assert calls == ["middleware called"]
935
+
936
+
937
+ class TestUrlfor(object):
938
+ def test_urlfor_on_aliased_functions(self):
939
+ def view():
940
+ return None
941
+
942
+ setattr(fixtures, "aliased_view", view)
943
+
944
+ app = FrescoApp()
945
+ app.route("/", GET, view)
946
+ with app.requestcontext():
947
+ assert urlfor(view) == "http://localhost/"
948
+ assert urlfor("fresco.tests.fixtures.aliased_view") == "http://localhost/"
949
+
950
+ delattr(fixtures, "aliased_view")
951
+
952
+ def test_urlfor_with_view_function(self):
953
+ def view():
954
+ return Response()
955
+
956
+ app = FrescoApp()
957
+ app.route("/foo", GET, view)
958
+ with app.requestcontext():
959
+ assert urlfor(view) == "http://localhost/foo"
960
+
961
+ def test_urlfor_allows_script_name(self):
962
+ def view():
963
+ return Response()
964
+
965
+ app = FrescoApp()
966
+ app.route("/foo", GET, view)
967
+ with app.requestcontext():
968
+ assert urlfor(view, _script_name="/abc") == "http://localhost/abc/foo"
969
+
970
+ def test_urlfor_with_string(self):
971
+ app = FrescoApp()
972
+ app.route("/myviewfunc", GET, fixtures.module_level_function)
973
+ with app.requestcontext():
974
+ assert (
975
+ urlfor("fresco.tests.fixtures.module_level_function")
976
+ == "http://localhost/myviewfunc"
977
+ )
978
+
979
+ def test_urlfor_drops_query(self):
980
+ def myviewfunc():
981
+ return Response()
982
+
983
+ app = FrescoApp()
984
+ app.route("/", GET, myviewfunc)
985
+ with app.requestcontext():
986
+ assert urlfor(myviewfunc) == "http://localhost/"
987
+
988
+ def test_urlfor_generates_first_route(self):
989
+ def myviewfunc():
990
+ return Response()
991
+
992
+ app = FrescoApp()
993
+ app.route("/1", GET, myviewfunc)
994
+ app.route("/2", GET, myviewfunc)
995
+ with app.requestcontext():
996
+ assert urlfor(myviewfunc) == "http://localhost/1"
997
+
998
+ def test_urlfor_with_class_based_view_spec(self):
999
+ app = FrescoApp()
1000
+ app.include("/foo", fixtures.CBV("x"))
1001
+ with app.requestcontext():
1002
+ assert (
1003
+ urlfor("fresco.tests.fixtures.CBV.index_html")
1004
+ == "http://localhost/foo/"
1005
+ )
1006
+
1007
+ def test_it_uses_values_from_path_defaults(self):
1008
+ app = FrescoApp()
1009
+ app.route("/<test:int>", GET, lambda: None, name="test", test_default=1)
1010
+ with app.requestcontext():
1011
+ assert urlfor("test") == "http://localhost/1"
1012
+
1013
+ def test_it_uses_callable_values_from_path_defaults(self):
1014
+ generate_value = Mock(return_value=1)
1015
+ app = FrescoApp()
1016
+ app.route(
1017
+ "/<test:int>",
1018
+ GET,
1019
+ lambda: None,
1020
+ name="test",
1021
+ test_default=generate_value,
1022
+ )
1023
+ with app.requestcontext() as c:
1024
+ assert urlfor("test") == "http://localhost/1"
1025
+ assert generate_value.call_args_list == [call(c.request)]
1026
+
1027
+ def test_urlfor_adds_query(self):
1028
+ def f():
1029
+ return Response()
1030
+
1031
+ app = FrescoApp()
1032
+ app.route("/", GET, f)
1033
+ with app.requestcontext():
1034
+ assert urlfor(f, _query="a=1") == "http://localhost/?a=1"
1035
+ assert urlfor(f, _query={"a": "1"}) == "http://localhost/?a=1"
1036
+ assert (
1037
+ urlfor(f, _query=[("a", "1"), ("a", 2)]) == "http://localhost/?a=1&a=2"
1038
+ )