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,124 @@
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
+ import sys
16
+ import os
17
+ import re
18
+ from calendar import timegm
19
+ from email.utils import formatdate, parsedate
20
+ from unittest.mock import Mock
21
+ from tempfile import mkstemp
22
+ from fresco import FrescoApp
23
+ from fresco.static import serve_static_file
24
+ from fresco.util.wsgi import ClosingIterator
25
+ from fresco.util.wsgi import apply_request
26
+
27
+
28
+ class TestServeStaticFile:
29
+ def setup_method(self):
30
+ fh, tmpname = mkstemp()
31
+ self.tmpname = tmpname
32
+ self.mtime = int(os.path.getmtime(self.tmpname))
33
+ os.close(fh)
34
+
35
+ def teardown_method(self):
36
+ os.unlink(self.tmpname)
37
+
38
+ def parse_lmh(self, last_modified):
39
+ return timegm(parsedate(last_modified)) # type: ignore
40
+
41
+ def make_ims(self, since, tz=0):
42
+ return {"HTTP_IF_MODIFIED_SINCE": formatdate(since, tz)}
43
+
44
+ def test_sets_last_modified_header(self):
45
+ with FrescoApp().requestcontext():
46
+ response = serve_static_file(self.tmpname)
47
+ response.content_iterator.close()
48
+
49
+ last_modified = response.get_header("Last-Modified")
50
+ assert self.parse_lmh(last_modified) == self.mtime
51
+
52
+ def test_full_response_when_if_modified_before_mtime(self):
53
+ with FrescoApp().requestcontext(**self.make_ims(0)):
54
+ response = serve_static_file(self.tmpname)
55
+ response.content_iterator.close()
56
+
57
+ last_modified = response.get_header("Last-Modified")
58
+ assert response.status_code == 200
59
+ assert self.parse_lmh(last_modified) == self.mtime
60
+
61
+ def test_304_when_if_modified_after_mtime(self):
62
+ with FrescoApp().requestcontext(**self.make_ims(self.mtime + 1)):
63
+ response = serve_static_file(self.tmpname)
64
+ assert response.status_code == 304
65
+
66
+ def test_304_when_if_modified_after_mtime_with_tz1(self):
67
+ with FrescoApp().requestcontext(**self.make_ims(self.mtime + 1, +1)):
68
+ response = serve_static_file(self.tmpname)
69
+ assert response.status_code == 304
70
+
71
+ def test_304_when_if_modified_after_mtime_with_tz2(self):
72
+ with FrescoApp().requestcontext(**self.make_ims(self.mtime + 1, -1)):
73
+ response = serve_static_file(self.tmpname)
74
+ assert response.status_code == 304
75
+
76
+ def test_it_doesnt_fail_with_an_out_of_range_modified_since_value1(self):
77
+ year = sys.maxsize
78
+ with FrescoApp().requestcontext(
79
+ HTTP_IF_MODIFIED_SINCE='"Wed, 09 Feb %s 04:46:40 GMT"' % year
80
+ ):
81
+ response = serve_static_file(self.tmpname)
82
+ assert response.status_code == 400
83
+
84
+ def test_it_doesnt_fail_with_an_out_of_range_modified_since_value2(self):
85
+ with FrescoApp().requestcontext(
86
+ HTTP_IF_MODIFIED_SINCE='"Wed, 01 Feb -2001 04:46:40 GMT"'
87
+ ):
88
+ response = serve_static_file(self.tmpname)
89
+ assert response.status_code == 400
90
+
91
+ def test_last_modified_format_is_correct(self):
92
+ with FrescoApp().requestcontext():
93
+ response = serve_static_file(self.tmpname)
94
+ response.content_iterator.close()
95
+
96
+ assert re.match(
97
+ r"^\w{3}, \d{1,2} \w{3} \d{4} \d\d:\d\d:\d\d GMT",
98
+ response.get_header("last-modified"),
99
+ )
100
+
101
+ def test_filewrapper_used_if_present(self):
102
+ with FrescoApp().requestcontext() as c:
103
+
104
+ def closefile():
105
+ file_wrapper.call_args.args[0].close()
106
+
107
+ file_wrapper = Mock()
108
+ file_wrapper.return_value = ClosingIterator([], closefile)
109
+ c.request.environ["wsgi.file_wrapper"] = file_wrapper
110
+ response = serve_static_file(self.tmpname)
111
+ apply_request(c.request, response)
112
+
113
+ assert file_wrapper.call_count == 1
114
+
115
+ def test_it_returns_not_found(self):
116
+ with FrescoApp().requestcontext():
117
+ response = serve_static_file(self.tmpname + "x")
118
+ assert response.status_code == 404
119
+
120
+ def test_it_returns_forbidden(self):
121
+ with FrescoApp().requestcontext():
122
+ os.chmod(self.tmpname, 0)
123
+ response = serve_static_file(self.tmpname)
124
+ assert response.status_code == 403
@@ -0,0 +1,236 @@
1
+ # encoding: UTF-8
2
+ from fresco.core import FrescoApp
3
+ from fresco.routing import GET
4
+ from fresco.routing import Route
5
+ from fresco.requestcontext import context
6
+ from fresco.response import Response
7
+ from fresco.routeargs import GetArg, routearg
8
+ from fresco.subrequests import subrequest
9
+ from fresco.subrequests import subrequest_bytes
10
+ from fresco.subrequests import subrequest_raw
11
+ from fresco.subrequests import subrequest_str
12
+
13
+ from unittest.mock import Mock
14
+ import typing as t
15
+ import pytest
16
+
17
+
18
+ class TestSubRequest(object):
19
+ def test_it_raises_exception_if_decoding_impossible(self):
20
+ def view_bytes():
21
+ return Response(b"caf\xe9", content_type="text/plain")
22
+
23
+ with FrescoApp().requestcontext():
24
+ with pytest.raises(ValueError):
25
+ subrequest(view_bytes)
26
+
27
+ def test_it_decodes_response_content(self):
28
+ def view_latin1():
29
+ return Response(
30
+ "café".encode("latin1"),
31
+ content_type="text/plain; charset=Latin-1",
32
+ )
33
+
34
+ def view_utf8():
35
+ return Response(
36
+ "café".encode("UTF-8"),
37
+ content_type="text/plain; charset=UTF-8",
38
+ )
39
+
40
+ def view_string():
41
+ return Response("café", content_type="text/plain; charset=UTF-8")
42
+
43
+ def view_list_of_strings():
44
+ return Response(["café"], content_type="text/plain; charset=UTF-8")
45
+
46
+ with FrescoApp().requestcontext():
47
+ assert subrequest(view_utf8) == "café"
48
+ assert subrequest(view_latin1) == "café"
49
+ assert subrequest(view_string) == "café"
50
+ assert subrequest(view_list_of_strings) == "café"
51
+
52
+ def test_it_returns_a_markup_string(self):
53
+ def view_html():
54
+ return Response("<html>", content_type="text/html; charset=UTF-8")
55
+
56
+ def view_text():
57
+ return Response("text", content_type="text/plain; charset=UTF-8")
58
+
59
+ with FrescoApp().requestcontext():
60
+ assert hasattr(subrequest(view_html), "__html__")
61
+ assert not hasattr(subrequest(view_text), "__html__")
62
+
63
+ def test_it_returns_raw_response(self):
64
+ r = Response("foo")
65
+ with FrescoApp().requestcontext():
66
+ assert subrequest_raw(lambda: r) is r
67
+
68
+ def test_it_returns_content_as_bytes(self):
69
+ def view():
70
+ return Response(
71
+ "café".encode("latin1"),
72
+ content_type="text/plain; charset=Latin-1",
73
+ )
74
+
75
+ with FrescoApp().requestcontext():
76
+ assert subrequest_bytes(view) == b"caf\xe9"
77
+
78
+ def test_it_calls_response_onclose(self):
79
+ m = Mock()
80
+ r = Response(onclose=m)
81
+ with FrescoApp().requestcontext():
82
+ assert subrequest(lambda: r) == ""
83
+ assert m.call_count == 1
84
+
85
+ def test_viewspec_resolves(self):
86
+ def view():
87
+ return Response("foo")
88
+
89
+ app = FrescoApp()
90
+ app.route("/view", GET, view, name="named view")
91
+ with app.requestcontext():
92
+ assert subrequest("named view") == "foo"
93
+
94
+ def test_viewspec_can_be_a_path(self):
95
+ def view():
96
+ return Response("foo")
97
+
98
+ app = FrescoApp()
99
+ app.route("/view", GET, view, name="named view")
100
+ with app.requestcontext():
101
+ assert subrequest("/view") == "foo"
102
+
103
+ def test_viewspec_uses_routeargs(self):
104
+ def view(a, b):
105
+ return Response(["*" + a + b + "*"])
106
+
107
+ app = FrescoApp()
108
+ app.route("/view", GET, view, a=GetArg(), b=routearg(lambda r: r.method))
109
+
110
+ with app.requestcontext("/view?a=foo"):
111
+ assert subrequest(view, _resolve=True) == "*fooGET*"
112
+ assert subrequest(view, _resolve=True, a="bar", b="baz") == "*barbaz*"
113
+
114
+ def test_viewspec_resolves_dynamic(self):
115
+ class Views(object):
116
+ def __init__(self, a):
117
+ self.a = a
118
+
119
+ def view(self, b):
120
+ return Response("*" + self.a + b + "*")
121
+
122
+ __routes__ = [Route("/<b:str>", GET, "view", name="named view")]
123
+
124
+ app = FrescoApp()
125
+ app.delegate("/<a:str>", Views, dynamic=True, name="named collection")
126
+
127
+ with app.requestcontext():
128
+ assert (
129
+ subrequest("named collection:named view", a="foo", b="bar")
130
+ == "*foobar*"
131
+ )
132
+
133
+ def test_it_does_a_full_request(self):
134
+ request_hook = Mock(return_value=None)
135
+ view = Mock(return_value=Response())
136
+
137
+ app = FrescoApp()
138
+ app.route("/view", GET, view)
139
+ app.process_request(request_hook)
140
+ with app.requestcontext():
141
+ subrequest(view)
142
+ assert view.call_count == 1
143
+ assert request_hook.call_count == 0
144
+
145
+ subrequest(view, _full=True)
146
+ assert view.call_count == 2
147
+ assert request_hook.call_count == 1
148
+
149
+ def test_it_maintains_the_environ(self):
150
+ subreq_environ = None
151
+
152
+ def view():
153
+ nonlocal subreq_environ
154
+ subreq_environ = context.request.environ
155
+ return Response()
156
+
157
+ app = FrescoApp()
158
+ app.route("/view", GET, view)
159
+
160
+ with app.requestcontext("/xyzzy") as c:
161
+ original_environ = c.request.environ
162
+ subrequest(view)
163
+ assert subreq_environ is original_environ
164
+
165
+ def test_it_passes_original_environ_values(self):
166
+ subreq_environ = None
167
+
168
+ def view():
169
+ nonlocal subreq_environ
170
+ subreq_environ = context.request.environ
171
+ return Response()
172
+
173
+ app = FrescoApp()
174
+ app.route("/view", GET, view)
175
+ environ_copied = {
176
+ "HTTP_COOKIE": "1234",
177
+ "REMOTE_IP": "192.168.0.1",
178
+ "REMOTE_USER": "username",
179
+ }
180
+ environ_not_copied = {
181
+ "PATH_INFO": "/not-copied",
182
+ "QUERY_STRING": "not-copied",
183
+ }
184
+
185
+ with app.requestcontext("/xyzzy") as c:
186
+ original_environ = c.request.environ
187
+ c.request.environ.update(environ_copied)
188
+ c.request.environ.update(environ_not_copied)
189
+ subrequest(view, _full=True, _query="test")
190
+ assert isinstance(subreq_environ, dict)
191
+ assert subreq_environ is not original_environ
192
+ assert all(subreq_environ[k] == environ_copied[k] for k in environ_copied)
193
+ assert all(
194
+ subreq_environ[k] != environ_not_copied[k] for k in environ_not_copied
195
+ )
196
+ assert subreq_environ["PATH_INFO"] == "/view"
197
+ assert subreq_environ["QUERY_STRING"] == "test"
198
+
199
+ def test_it_merges_custom_environ_keys(self):
200
+ subreq_environ: t.Optional[dict] = None
201
+
202
+ def view():
203
+ nonlocal subreq_environ
204
+ subreq_environ = context.request.environ
205
+ return Response()
206
+
207
+ app = FrescoApp()
208
+ app.route("/view", GET, view)
209
+
210
+ with app.requestcontext("/xyzzy") as c:
211
+ original_environ = c.request.environ
212
+ subrequest(view, _full=True, _env={"hey.there": True})
213
+ assert subreq_environ is not None
214
+ assert "hey.there" in subreq_environ
215
+ assert "hey.there" not in original_environ
216
+
217
+ def test_it_exhausts_content_iterator_before_closing_response(self):
218
+ tracker = ["view inited"]
219
+
220
+ def view():
221
+ def responder():
222
+ tracker.append("responder started")
223
+ yield ",".join(tracker)
224
+
225
+ r = Response(responder())
226
+ r = r.add_onclose(lambda: tracker.append("response closed"))
227
+ return r
228
+
229
+ app = FrescoApp()
230
+ app.route("/", GET, view)
231
+
232
+ with app.requestcontext("/"):
233
+ result = subrequest_str(view, _full=True)
234
+ assert ",".join(tracker) == "view inited,responder started,response closed"
235
+
236
+ assert result == "view inited,responder started"
File without changes
@@ -0,0 +1,79 @@
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
+ # flake8: noqa
16
+ from base64 import b64decode
17
+
18
+ multipart_samples = []
19
+ formencoded_samples = []
20
+
21
+ # 1k block of random bytes
22
+ FILE_UPLOAD_DATA = b64decode(
23
+ b"""
24
+ QJHX77oVa5GoHaIthmjSe8i8r6AhNIpIYx9Yw46gDqifTyd14aE/hWGoTCMR0hjFcE0GAQ7KE/S7
25
+ wnpoaAMManjCR4wD39lzhUsUPLy1N/ANwAEo0fk93y8ggEhbN8lEi0zl5rIecC1ZsEA8WG4dw3gh
26
+ 61FRZdZ/fG8sojnfAjWM9UnSdhkd6idvF8qwKGBRpFPFUT4gRNu+di4i9r6+XQ7IiXbc2KXiXyy0
27
+ 7AsrcOsOOiincLkw/IvDVYUgZmUiTqs0XKHfsFexpznIVIKubYGGlGpIyBdq04VmtPoQvj9weqZh
28
+ di5F9jlGBJ479vHNS+6xwbnxqpSNkAkzsBOQ6a68Ae9in1R0t+UIlUlRKG6jsUelkbJlq3+qnzqt
29
+ RFBuOneh/vdlFEhuXQSxZ4a7oPl+wuv94fE6AsWyiV4urZR16CaioAbeozrd5MOqBkKk7Q2TyWX/
30
+ hHA05mlv6g4/34lh5N4UWH8YTLEjgL9ykMJ6zodmGujmvmYwIiVQuhIcjsZ5n0m0zYBlDP4BDspV
31
+ bQsTxPa+0ptiQJ+qTStqPWCDaNFO6Fwdia7jxs7asPAuCwIcgUL2ztmCfafvlniMSwC8lmjs77Zt
32
+ gvgeCWr6IoASWOso1oEFsCgk6HMEU+XW5bLD5h5cva0/on/K1wwpFF+FQlQEqvd2cAEFZrZ8Dk4E
33
+ zKK+tdU3qK5maooKTIwpj8llk3ENpRAZXJUYRpUzvdb3m4kDg+sHCbZw5t8bpC5Oph89AHytc+Op
34
+ zY6+/UUHiUuIuf0hflVqQgb9uangMAkHkMyjw6o7dFPPanfu2j0DouqGF2QKRczdcs9itbWh65/O
35
+ PX1KbztEqAAxTqZixNI9qkG8A9rW24lDX6QFSws/0Wjh/UUvixAq9u/Dobbqn+hUgsQl2xqoMtci
36
+ sRJajasOPIyIOgwpLP5ul2DMIqZUDmAii3OcOQL4y4b0vgUHk26iNp9omPpzenJuAECoyIE8KnEf
37
+ SK1Cv9mQOU6Gbl3gyLsMp9fdsespzLFv8M+VrlaLCDTgL6OL2DW9SwOH4FWKH8qAjJfTsWnhqpbS
38
+ PBRsHlujXX1lAZC5eKCA4ekug2+aW7g+Mpse2Rt1XP4w8UZ231bxCYmYsKrzsB5f/0Pfp/vAjYHm
39
+ GYtj6RBhRoTym2Sm/A1VOOOBYSRPZXeqdx2WoIrGtSlkEZnH2ySM5z1Se6v9y4uI0TN5Oa5q691G
40
+ UegOAO9Yx4oa1L+7BXs+lkIqz94bVCLNI6gwLC+QOhz6iC96LyagKZtcYyagLeiQGhh91oJXWCCm
41
+ sz4EbqNbiEtfTa6LhAs+mkLSOfrAsyWLr/reTu4uHwJXsGzgn17Uhe/+VxV74/yEuwTLTbHHgA==
42
+ """
43
+ )
44
+
45
+ # Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.9.0.11) Gecko/2009061410 Firefox/3.0.11
46
+ multipart_samples.append(
47
+ {
48
+ "content_length": "1638",
49
+ "data": b'-----------------------------1282332249122276511499183892\r\nContent-Disposition: form-data; name="empty-text-input"\r\n\r\n\r\n-----------------------------1282332249122276511499183892\r\nContent-Disposition: form-data; name="text-input-ascii"\r\n\r\nabcdef\r\n-----------------------------1282332249122276511499183892\r\nContent-Disposition: form-data; name="text-input-unicode"\r\n\r\n\xce\xb1\xce\xb2\xce\xb3\xce\xb4\r\n-----------------------------1282332249122276511499183892\r\nContent-Disposition: form-data; name="file-upload"; filename="test.data"\r\nContent-Type: application/octet-stream\r\n\r\n@\x91\xd7\xef\xba\x15k\x91\xa8\x1d\xa2-\x86h\xd2{\xc8\xbc\xaf\xa0!4\x8aHc\x1fX\xc3\x8e\xa0\x0e\xa8\x9fO\'u\xe1\xa1?\x85a\xa8L#\x11\xd2\x18\xc5pM\x06\x01\x0e\xca\x13\xf4\xbb\xc2zhh\x03\x0cjx\xc2G\x8c\x03\xdf\xd9s\x85K\x14<\xbc\xb57\xf0\r\xc0\x01(\xd1\xf9=\xdf/ \x80H[7\xc9D\x8bL\xe5\xe6\xb2\x1ep-Y\xb0@<Xn\x1d\xc3x!\xebQQe\xd6\x7f|o,\xa29\xdf\x025\x8c\xf5I\xd2v\x19\x1d\xea\'o\x17\xca\xb0(`Q\xa4S\xc5Q> D\xdb\xbev."\xf6\xbe\xbe]\x0e\xc8\x89v\xdc\xd8\xa5\xe2_,\xb4\xec\x0b+p\xeb\x0e:(\xa7p\xb90\xfc\x8b\xc3U\x85 fe"N\xab4\\\xa1\xdf\xb0W\xb1\xa79\xc8T\x82\xaem\x81\x86\x94jH\xc8\x17j\xd3\x85f\xb4\xfa\x10\xbe?pz\xa6av.E\xf69F\x04\x9e;\xf6\xf1\xcdK\xee\xb1\xc1\xb9\xf1\xaa\x94\x8d\x90\t3\xb0\x13\x90\xe9\xae\xbc\x01\xefb\x9fTt\xb7\xe5\x08\x95IQ(n\xa3\xb1G\xa5\x91\xb2e\xab\x7f\xaa\x9f:\xadDPn:w\xa1\xfe\xf7e\x14Hn]\x04\xb1g\x86\xbb\xa0\xf9~\xc2\xeb\xfd\xe1\xf1:\x02\xc5\xb2\x89^.\xad\x94u\xe8&\xa2\xa0\x06\xde\xa3:\xdd\xe4\xc3\xaa\x06B\xa4\xed\r\x93\xc9e\xff\x84p4\xe6io\xea\x0e?\xdf\x89a\xe4\xde\x14X\x7f\x18L\xb1#\x80\xbfr\x90\xc2z\xce\x87f\x1a\xe8\xe6\xbef0"%P\xba\x12\x1c\x8e\xc6y\x9fI\xb4\xcd\x80e\x0c\xfe\x01\x0e\xcaUm\x0b\x13\xc4\xf6\xbe\xd2\x9bb@\x9f\xaaM+j=`\x83h\xd1N\xe8\\\x1d\x89\xae\xe3\xc6\xce\xda\xb0\xf0.\x0b\x02\x1c\x81B\xf6\xce\xd9\x82}\xa7\xef\x96x\x8cK\x00\xbc\x96h\xec\xef\xb6m\x82\xf8\x1e\tj\xfa"\x80\x12X\xeb(\xd6\x81\x05\xb0($\xe8s\x04S\xe5\xd6\xe5\xb2\xc3\xe6\x1e\\\xbd\xad?\xa2\x7f\xca\xd7\x0c)\x14_\x85BT\x04\xaa\xf7vp\x01\x05f\xb6|\x0eN\x04\xcc\xa2\xbe\xb5\xd57\xa8\xaefj\x8a\nL\x8c)\x8f\xc9e\x93q\r\xa5\x10\x19\\\x95\x18F\x953\xbd\xd6\xf7\x9b\x89\x03\x83\xeb\x07\t\xb6p\xe6\xdf\x1b\xa4.N\xa6\x1f=\x00|\xads\xe3\xa9\xcd\x8e\xbe\xfdE\x07\x89K\x88\xb9\xfd!~UjB\x06\xfd\xb9\xa9\xe00\t\x07\x90\xcc\xa3\xc3\xaa;tS\xcfjw\xee\xda=\x03\xa2\xea\x86\x17d\nE\xcc\xddr\xcfb\xb5\xb5\xa1\xeb\x9f\xce=}Jo;D\xa8\x001N\xa6b\xc4\xd2=\xaaA\xbc\x03\xda\xd6\xdb\x89C_\xa4\x05K\x0b?\xd1h\xe1\xfdE/\x8b\x10*\xf6\xef\xc3\xa1\xb6\xea\x9f\xe8T\x82\xc4%\xdb\x1a\xa82\xd7"\xb1\x12Z\x8d\xab\x0e<\x8c\x88:\x0c),\xfen\x97`\xcc"\xa6T\x0e`"\x8bs\x9c9\x02\xf8\xcb\x86\xf4\xbe\x05\x07\x93n\xa26\x9fh\x98\xfaszrn\x00@\xa8\xc8\x81<*q\x1fH\xadB\xbf\xd9\x909N\x86n]\xe0\xc8\xbb\x0c\xa7\xd7\xdd\xb1\xeb)\xcc\xb1o\xf0\xcf\x95\xaeV\x8b\x084\xe0/\xa3\x8b\xd85\xbdK\x03\x87\xe0U\x8a\x1f\xca\x80\x8c\x97\xd3\xb1i\xe1\xaa\x96\xd2<\x14l\x1e[\xa3]}e\x01\x90\xb9x\xa0\x80\xe1\xe9.\x83o\x9a[\xb8>2\x9b\x1e\xd9\x1bu\\\xfe0\xf1Fv\xdfV\xf1\t\x89\x98\xb0\xaa\xf3\xb0\x1e_\xffC\xdf\xa7\xfb\xc0\x8d\x81\xe6\x19\x8bc\xe9\x10aF\x84\xf2\x9bd\xa6\xfc\rU8\xe3\x81a$Oew\xaaw\x1d\x96\xa0\x8a\xc6\xb5)d\x11\x99\xc7\xdb$\x8c\xe7=R{\xab\xfd\xcb\x8b\x88\xd13y9\xaej\xeb\xddFQ\xe8\x0e\x00\xefX\xc7\x8a\x1a\xd4\xbf\xbb\x05{>\x96B*\xcf\xde\x1bT"\xcd#\xa80,/\x90:\x1c\xfa\x88/z/&\xa0)\x9b\\c&\xa0-\xe8\x90\x1a\x18}\xd6\x82WX \xa6\xb3>\x04n\xa3[\x88K_M\xae\x8b\x84\x0b>\x9aB\xd29\xfa\xc0\xb3%\x8b\xaf\xfa\xdeN\xee.\x1f\x02W\xb0l\xe0\x9f^\xd4\x85\xef\xfeW\x15{\xe3\xfc\x84\xbb\x04\xcbM\xb1\xc7\x80\r\n-----------------------------1282332249122276511499183892--\r\n',
50
+ "content_type": "multipart/form-data; boundary=---------------------------1282332249122276511499183892",
51
+ }
52
+ )
53
+
54
+ # Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.9.0.11) Gecko/2009061410 Firefox/3.0.11
55
+ formencoded_samples.append(
56
+ {
57
+ "content_length": "85",
58
+ "data": b"empty-text-input=&text-input-ascii=abcdef&text-input-unicode=%CE%B1%CE%B2%CE%B3%CE%B4",
59
+ "content_type": "application/x-www-form-urlencoded",
60
+ }
61
+ )
62
+
63
+ # Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; InfoPath.2)
64
+ multipart_samples.append(
65
+ {
66
+ "content_length": "1616",
67
+ "data": b'-----------------------------7d91772e200be\r\nContent-Disposition: form-data; name="empty-text-input"\r\n\r\n\r\n-----------------------------7d91772e200be\r\nContent-Disposition: form-data; name="text-input-ascii"\r\n\r\nabcdef\r\n-----------------------------7d91772e200be\r\nContent-Disposition: form-data; name="text-input-unicode"\r\n\r\n\xce\xb1\xce\xb2\xce\xb3\xce\xb4\r\n-----------------------------7d91772e200be\r\nContent-Disposition: form-data; name="file-upload"; filename="C:\\Documents and Settings\\Administrator\\My Documents\\test.data"\r\nContent-Type: application/octet-stream\r\n\r\n@\x91\xd7\xef\xba\x15k\x91\xa8\x1d\xa2-\x86h\xd2{\xc8\xbc\xaf\xa0!4\x8aHc\x1fX\xc3\x8e\xa0\x0e\xa8\x9fO\'u\xe1\xa1?\x85a\xa8L#\x11\xd2\x18\xc5pM\x06\x01\x0e\xca\x13\xf4\xbb\xc2zhh\x03\x0cjx\xc2G\x8c\x03\xdf\xd9s\x85K\x14<\xbc\xb57\xf0\r\xc0\x01(\xd1\xf9=\xdf/ \x80H[7\xc9D\x8bL\xe5\xe6\xb2\x1ep-Y\xb0@<Xn\x1d\xc3x!\xebQQe\xd6\x7f|o,\xa29\xdf\x025\x8c\xf5I\xd2v\x19\x1d\xea\'o\x17\xca\xb0(`Q\xa4S\xc5Q> D\xdb\xbev."\xf6\xbe\xbe]\x0e\xc8\x89v\xdc\xd8\xa5\xe2_,\xb4\xec\x0b+p\xeb\x0e:(\xa7p\xb90\xfc\x8b\xc3U\x85 fe"N\xab4\\\xa1\xdf\xb0W\xb1\xa79\xc8T\x82\xaem\x81\x86\x94jH\xc8\x17j\xd3\x85f\xb4\xfa\x10\xbe?pz\xa6av.E\xf69F\x04\x9e;\xf6\xf1\xcdK\xee\xb1\xc1\xb9\xf1\xaa\x94\x8d\x90\t3\xb0\x13\x90\xe9\xae\xbc\x01\xefb\x9fTt\xb7\xe5\x08\x95IQ(n\xa3\xb1G\xa5\x91\xb2e\xab\x7f\xaa\x9f:\xadDPn:w\xa1\xfe\xf7e\x14Hn]\x04\xb1g\x86\xbb\xa0\xf9~\xc2\xeb\xfd\xe1\xf1:\x02\xc5\xb2\x89^.\xad\x94u\xe8&\xa2\xa0\x06\xde\xa3:\xdd\xe4\xc3\xaa\x06B\xa4\xed\r\x93\xc9e\xff\x84p4\xe6io\xea\x0e?\xdf\x89a\xe4\xde\x14X\x7f\x18L\xb1#\x80\xbfr\x90\xc2z\xce\x87f\x1a\xe8\xe6\xbef0"%P\xba\x12\x1c\x8e\xc6y\x9fI\xb4\xcd\x80e\x0c\xfe\x01\x0e\xcaUm\x0b\x13\xc4\xf6\xbe\xd2\x9bb@\x9f\xaaM+j=`\x83h\xd1N\xe8\\\x1d\x89\xae\xe3\xc6\xce\xda\xb0\xf0.\x0b\x02\x1c\x81B\xf6\xce\xd9\x82}\xa7\xef\x96x\x8cK\x00\xbc\x96h\xec\xef\xb6m\x82\xf8\x1e\tj\xfa"\x80\x12X\xeb(\xd6\x81\x05\xb0($\xe8s\x04S\xe5\xd6\xe5\xb2\xc3\xe6\x1e\\\xbd\xad?\xa2\x7f\xca\xd7\x0c)\x14_\x85BT\x04\xaa\xf7vp\x01\x05f\xb6|\x0eN\x04\xcc\xa2\xbe\xb5\xd57\xa8\xaefj\x8a\nL\x8c)\x8f\xc9e\x93q\r\xa5\x10\x19\\\x95\x18F\x953\xbd\xd6\xf7\x9b\x89\x03\x83\xeb\x07\t\xb6p\xe6\xdf\x1b\xa4.N\xa6\x1f=\x00|\xads\xe3\xa9\xcd\x8e\xbe\xfdE\x07\x89K\x88\xb9\xfd!~UjB\x06\xfd\xb9\xa9\xe00\t\x07\x90\xcc\xa3\xc3\xaa;tS\xcfjw\xee\xda=\x03\xa2\xea\x86\x17d\nE\xcc\xddr\xcfb\xb5\xb5\xa1\xeb\x9f\xce=}Jo;D\xa8\x001N\xa6b\xc4\xd2=\xaaA\xbc\x03\xda\xd6\xdb\x89C_\xa4\x05K\x0b?\xd1h\xe1\xfdE/\x8b\x10*\xf6\xef\xc3\xa1\xb6\xea\x9f\xe8T\x82\xc4%\xdb\x1a\xa82\xd7"\xb1\x12Z\x8d\xab\x0e<\x8c\x88:\x0c),\xfen\x97`\xcc"\xa6T\x0e`"\x8bs\x9c9\x02\xf8\xcb\x86\xf4\xbe\x05\x07\x93n\xa26\x9fh\x98\xfaszrn\x00@\xa8\xc8\x81<*q\x1fH\xadB\xbf\xd9\x909N\x86n]\xe0\xc8\xbb\x0c\xa7\xd7\xdd\xb1\xeb)\xcc\xb1o\xf0\xcf\x95\xaeV\x8b\x084\xe0/\xa3\x8b\xd85\xbdK\x03\x87\xe0U\x8a\x1f\xca\x80\x8c\x97\xd3\xb1i\xe1\xaa\x96\xd2<\x14l\x1e[\xa3]}e\x01\x90\xb9x\xa0\x80\xe1\xe9.\x83o\x9a[\xb8>2\x9b\x1e\xd9\x1bu\\\xfe0\xf1Fv\xdfV\xf1\t\x89\x98\xb0\xaa\xf3\xb0\x1e_\xffC\xdf\xa7\xfb\xc0\x8d\x81\xe6\x19\x8bc\xe9\x10aF\x84\xf2\x9bd\xa6\xfc\rU8\xe3\x81a$Oew\xaaw\x1d\x96\xa0\x8a\xc6\xb5)d\x11\x99\xc7\xdb$\x8c\xe7=R{\xab\xfd\xcb\x8b\x88\xd13y9\xaej\xeb\xddFQ\xe8\x0e\x00\xefX\xc7\x8a\x1a\xd4\xbf\xbb\x05{>\x96B*\xcf\xde\x1bT"\xcd#\xa80,/\x90:\x1c\xfa\x88/z/&\xa0)\x9b\\c&\xa0-\xe8\x90\x1a\x18}\xd6\x82WX \xa6\xb3>\x04n\xa3[\x88K_M\xae\x8b\x84\x0b>\x9aB\xd29\xfa\xc0\xb3%\x8b\xaf\xfa\xdeN\xee.\x1f\x02W\xb0l\xe0\x9f^\xd4\x85\xef\xfeW\x15{\xe3\xfc\x84\xbb\x04\xcbM\xb1\xc7\x80\r\n-----------------------------7d91772e200be--\r\n',
68
+ "content_type": "multipart/form-data; boundary=---------------------------7d91772e200be",
69
+ }
70
+ )
71
+
72
+ # Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; InfoPath.2)
73
+ formencoded_samples.append(
74
+ {
75
+ "content_length": "85",
76
+ "data": b"empty-text-input=&text-input-ascii=abcdef&text-input-unicode=%CE%B1%CE%B2%CE%B3%CE%B4",
77
+ "content_type": "application/x-www-form-urlencoded",
78
+ }
79
+ )
@@ -0,0 +1,34 @@
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 fresco.util.common import object_or_404
16
+ from fresco.exceptions import NotFound
17
+ import pytest
18
+
19
+
20
+ class TestObjectOr404(object):
21
+ def test_it_returns_the_object(self):
22
+ ob = object()
23
+ assert object_or_404(ob) is ob
24
+
25
+ def test_it_raises_NotFound(self):
26
+ with pytest.raises(NotFound):
27
+ object_or_404(None)
28
+
29
+ def test_it_raises_custom_exception(self):
30
+ class Foo(Exception):
31
+ pass
32
+
33
+ with pytest.raises(Foo):
34
+ object_or_404(None, exception=Foo)