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.
- fresco/__init__.py +1 -1
- fresco/options.py +11 -3
- fresco/request.py +2 -2
- fresco/response.py +1 -1
- fresco/routing.py +1 -1
- 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 +319 -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.2.dist-info → fresco-3.3.4.dist-info}/METADATA +5 -6
- fresco-3.3.4.dist-info/RECORD +57 -0
- {fresco-3.3.2.dist-info → fresco-3.3.4.dist-info}/WHEEL +1 -1
- fresco-3.3.2.dist-info/RECORD +0 -34
- {fresco-3.3.2.dist-info → fresco-3.3.4.dist-info}/LICENSE.txt +0 -0
- {fresco-3.3.2.dist-info → fresco-3.3.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,448 @@
|
|
|
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 io import BytesIO
|
|
17
|
+
import datetime
|
|
18
|
+
|
|
19
|
+
import pytest
|
|
20
|
+
|
|
21
|
+
from fresco import FrescoApp, exceptions
|
|
22
|
+
from fresco.tests import fixtures
|
|
23
|
+
|
|
24
|
+
context = FrescoApp().requestcontext
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestRequestProperties(object):
|
|
28
|
+
def test_url_script_name_only(self):
|
|
29
|
+
with context(SCRIPT_NAME="/foo/bar", PATH_INFO="") as c:
|
|
30
|
+
assert c.request.url == "http://localhost/foo/bar"
|
|
31
|
+
|
|
32
|
+
def test_url_script_name_path_info(self):
|
|
33
|
+
with context(SCRIPT_NAME="/foo/bar", PATH_INFO="/baz") as c:
|
|
34
|
+
assert c.request.url == "http://localhost/foo/bar/baz"
|
|
35
|
+
|
|
36
|
+
def test_url_normalizes_host_port(self):
|
|
37
|
+
with context(HTTP_HOST="localhost:80") as c:
|
|
38
|
+
assert c.request.url == "http://localhost/"
|
|
39
|
+
with context(HTTP_HOST="localhost:81") as c:
|
|
40
|
+
assert c.request.url == "http://localhost:81/"
|
|
41
|
+
|
|
42
|
+
def test_url_normalizes_host_ssl_port(self):
|
|
43
|
+
with context(
|
|
44
|
+
environ={"wsgi.url_scheme": "https"}, HTTP_HOST="localhost:443"
|
|
45
|
+
) as c:
|
|
46
|
+
assert c.request.url == "https://localhost/"
|
|
47
|
+
|
|
48
|
+
def test_url_ignores_server_port_if_host_header_present(self):
|
|
49
|
+
with context(
|
|
50
|
+
environ={"wsgi.url_scheme": "https", "SERVER_PORT": "81"},
|
|
51
|
+
HTTP_HOST="localhost",
|
|
52
|
+
) as c:
|
|
53
|
+
assert c.request.url == "https://localhost/"
|
|
54
|
+
|
|
55
|
+
def test_as_string(self):
|
|
56
|
+
with context("http://example.org/foo?bar=baz") as c:
|
|
57
|
+
assert str(c.request) == "<Request GET http://example.org/foo?bar=baz>"
|
|
58
|
+
|
|
59
|
+
def test_url_returns_full_url(self):
|
|
60
|
+
with context("http://example.org/foo?bar=baz") as c:
|
|
61
|
+
assert c.request.url == "http://example.org/foo?bar=baz"
|
|
62
|
+
|
|
63
|
+
def test_path_returns_correct_path_when_script_name_empty(self):
|
|
64
|
+
with context(SCRIPT_NAME="", PATH_INFO="/foo/bar") as c:
|
|
65
|
+
assert c.request.path == "/foo/bar"
|
|
66
|
+
|
|
67
|
+
def test_path_returns_correct_path(self):
|
|
68
|
+
with context(SCRIPT_NAME="/foo", PATH_INFO="/bar") as c:
|
|
69
|
+
assert c.request.path == "/foo/bar"
|
|
70
|
+
|
|
71
|
+
def test_query_decodes_unicode(self):
|
|
72
|
+
with context("/?q=%C3%A0") as c:
|
|
73
|
+
assert c.request.query["q"] == b"\xc3\xa0".decode("utf8")
|
|
74
|
+
|
|
75
|
+
def test_form_decodes_unicode(self):
|
|
76
|
+
with context("/?q=%C3%A0") as c:
|
|
77
|
+
assert c.request.form["q"] == b"\xc3\xa0".decode("utf8")
|
|
78
|
+
|
|
79
|
+
def test_body_decodes_unicode(self):
|
|
80
|
+
with context(
|
|
81
|
+
wsgi_input=b"\xc3\xa0",
|
|
82
|
+
CONTENT_LENGTH="2",
|
|
83
|
+
CONTENT_TYPE="text/plain; charset=UTF-8",
|
|
84
|
+
) as c:
|
|
85
|
+
assert c.request.body == b"\xc3\xa0".decode("UTF-8")
|
|
86
|
+
|
|
87
|
+
def test_body_raises_bad_request_for_invalid_encoding(self):
|
|
88
|
+
with context(
|
|
89
|
+
wsgi_input=b"\xc3a",
|
|
90
|
+
CONTENT_LENGTH="2",
|
|
91
|
+
CONTENT_TYPE="text/plain; charset=UTF-8",
|
|
92
|
+
) as c:
|
|
93
|
+
with pytest.raises(exceptions.BadRequest):
|
|
94
|
+
c.request.body
|
|
95
|
+
|
|
96
|
+
def test_body_bytes_does_not_decode(self):
|
|
97
|
+
with context(
|
|
98
|
+
wsgi_input=b"\xc3a",
|
|
99
|
+
CONTENT_LENGTH="2",
|
|
100
|
+
CONTENT_TYPE="text/plain; charset=UTF-8",
|
|
101
|
+
) as c:
|
|
102
|
+
c.request.body_bytes == b"\xc3a"
|
|
103
|
+
|
|
104
|
+
def test_get_json_decodes_json(self):
|
|
105
|
+
with context(
|
|
106
|
+
wsgi_input=b'{"foo": "bar"}',
|
|
107
|
+
CONTENT_LENGTH="14",
|
|
108
|
+
CONTENT_TYPE="application/json",
|
|
109
|
+
) as c:
|
|
110
|
+
c.request.get_json() == {"foo": "bar"}
|
|
111
|
+
|
|
112
|
+
def test_get_json_ignores_mime_type(self):
|
|
113
|
+
with context(
|
|
114
|
+
wsgi_input=b'{"foo": "bar"}',
|
|
115
|
+
CONTENT_LENGTH="14",
|
|
116
|
+
CONTENT_TYPE="application/broken",
|
|
117
|
+
) as c:
|
|
118
|
+
c.request.get_json() == {"foo": "bar"}
|
|
119
|
+
|
|
120
|
+
def test_get_json_passes_args_to_decoder(self):
|
|
121
|
+
with context(
|
|
122
|
+
wsgi_input=b'{"foo": 1}',
|
|
123
|
+
CONTENT_LENGTH="10",
|
|
124
|
+
CONTENT_TYPE="application/broken",
|
|
125
|
+
) as c:
|
|
126
|
+
c.request.get_json(parse_int=lambda s: s + "!") == {"foo": "1!"}
|
|
127
|
+
|
|
128
|
+
def test_get_does_type_conversion(self):
|
|
129
|
+
with context(
|
|
130
|
+
QUERY_STRING="x=10",
|
|
131
|
+
CONTENT_LENGTH="10",
|
|
132
|
+
CONTENT_TYPE="application/broken",
|
|
133
|
+
) as c:
|
|
134
|
+
assert c.request.get("x") == "10"
|
|
135
|
+
assert c.request.get("x", type=int) == 10
|
|
136
|
+
with pytest.raises(exceptions.BadRequest):
|
|
137
|
+
c.request.get("y", type=int)
|
|
138
|
+
assert c.request.get("y", None, type=int) is None
|
|
139
|
+
|
|
140
|
+
def test_is_secure_returns_correct_value(self):
|
|
141
|
+
with context("https://example.org/") as c:
|
|
142
|
+
assert c.request.is_secure is True
|
|
143
|
+
with context("http://example.org/") as c:
|
|
144
|
+
assert c.request.is_secure is False
|
|
145
|
+
|
|
146
|
+
def test_getint_returns_int(self):
|
|
147
|
+
with context("http://example.org/?a=4") as c:
|
|
148
|
+
assert c.request.getint("a") == 4
|
|
149
|
+
|
|
150
|
+
def test_getint_raises_badrequest(self):
|
|
151
|
+
with pytest.raises(exceptions.BadRequest):
|
|
152
|
+
with context("http://example.org/") as c:
|
|
153
|
+
c.request.getint("a")
|
|
154
|
+
|
|
155
|
+
with pytest.raises(exceptions.BadRequest):
|
|
156
|
+
with context("http://example.org/?a=four") as c:
|
|
157
|
+
c.request.getint("a")
|
|
158
|
+
|
|
159
|
+
with pytest.raises(exceptions.BadRequest):
|
|
160
|
+
with context("http://example.org/?a=four") as c:
|
|
161
|
+
assert c.request.getint("a", 0) == 0
|
|
162
|
+
|
|
163
|
+
def test_getint_returns_default(self):
|
|
164
|
+
with context("http://example.org/") as c:
|
|
165
|
+
assert c.request.getint("a", 0) == 0
|
|
166
|
+
|
|
167
|
+
def test_now(self):
|
|
168
|
+
with context("http://example.org/") as c:
|
|
169
|
+
now = c.request.now
|
|
170
|
+
assert now.tzinfo is datetime.timezone.utc
|
|
171
|
+
assert now is c.request.now
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class TestPathEncoding(object):
|
|
175
|
+
def test_url_is_quoted(self):
|
|
176
|
+
with context(SCRIPT_NAME=fixtures.wsgi_unicode_path, PATH_INFO="") as c:
|
|
177
|
+
assert c.request.url == "http://localhost" + fixtures.quoted_unicode_path
|
|
178
|
+
|
|
179
|
+
def test_application_url_is_quoted(self):
|
|
180
|
+
with context(SCRIPT_NAME=fixtures.wsgi_unicode_path, PATH_INFO="") as c:
|
|
181
|
+
assert (
|
|
182
|
+
c.request.application_url
|
|
183
|
+
== "http://localhost" + fixtures.quoted_unicode_path
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def test_parsed_url_is_quoted(self):
|
|
187
|
+
with context(SCRIPT_NAME=fixtures.wsgi_unicode_path, PATH_INFO="") as c:
|
|
188
|
+
assert c.request.parsed_url.path == fixtures.quoted_unicode_path
|
|
189
|
+
|
|
190
|
+
def test_path_is_unquoted(self):
|
|
191
|
+
with context(SCRIPT_NAME=fixtures.wsgi_unicode_path, PATH_INFO="") as c:
|
|
192
|
+
assert c.request.path == fixtures.unquoted_unicode_path
|
|
193
|
+
|
|
194
|
+
def test_script_name_is_unquoted(self):
|
|
195
|
+
with context(SCRIPT_NAME=fixtures.wsgi_unicode_path, PATH_INFO="") as c:
|
|
196
|
+
assert c.request.script_name == fixtures.unquoted_unicode_path
|
|
197
|
+
|
|
198
|
+
def test_path_info_is_unquoted(self):
|
|
199
|
+
with context(PATH_INFO=fixtures.wsgi_unicode_path) as c:
|
|
200
|
+
assert c.request.path_info == fixtures.unquoted_unicode_path
|
|
201
|
+
|
|
202
|
+
def test_invalid_path_info_encoding_raises_bad_request(self):
|
|
203
|
+
with context(PATH_INFO=fixtures.misquoted_wsgi_unicode_path) as c:
|
|
204
|
+
with pytest.raises(exceptions.BadRequest):
|
|
205
|
+
c.request.path_info
|
|
206
|
+
|
|
207
|
+
def test_invalid_script_name_encoding_raises_bad_request(self):
|
|
208
|
+
with context(SCRIPT_NAME=fixtures.misquoted_wsgi_unicode_path) as c:
|
|
209
|
+
with pytest.raises(exceptions.BadRequest):
|
|
210
|
+
c.request.script_name
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class TestMakeURL(object):
|
|
214
|
+
def assert_equal_with_query(self, url1, url2):
|
|
215
|
+
if "?" not in url1:
|
|
216
|
+
assert url1 == url2
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
base1, _, q1 = url1.partition("?")
|
|
220
|
+
base2, _, q2 = url2.partition("?")
|
|
221
|
+
assert base1 == base2
|
|
222
|
+
assert sorted(q1.split(";")) == sorted(q2.split(";"))
|
|
223
|
+
|
|
224
|
+
def test_it_returns_request_url(self):
|
|
225
|
+
with context(SCRIPT_NAME="/script", PATH_INFO="/pathinfo") as c:
|
|
226
|
+
assert c.request.make_url() == "http://localhost/script/pathinfo"
|
|
227
|
+
|
|
228
|
+
def test_it_doesnt_double_quote_request_url(self):
|
|
229
|
+
with context(SCRIPT_NAME="/script name", PATH_INFO="/path info") as c:
|
|
230
|
+
assert c.request.make_url() == "http://localhost/script%20name/path%20info"
|
|
231
|
+
|
|
232
|
+
def test_it_doesnt_double_quote_supplied_path_info(self):
|
|
233
|
+
with context() as c:
|
|
234
|
+
assert c.request.make_url(PATH_INFO="/x y") == "http://localhost/x%20y"
|
|
235
|
+
|
|
236
|
+
def test_can_replace_path(self):
|
|
237
|
+
with context(SCRIPT_NAME="/script", PATH_INFO="/pathinfo") as c:
|
|
238
|
+
assert c.request.make_url(path="/foo bar") == "http://localhost/foo%20bar"
|
|
239
|
+
|
|
240
|
+
def test_it_joins_path(self):
|
|
241
|
+
with context(SCRIPT_NAME="/script name", PATH_INFO="/path info/") as c:
|
|
242
|
+
assert (
|
|
243
|
+
c.request.make_url(path="a/b c")
|
|
244
|
+
== "http://localhost/script%20name/path%20info/a/b%20c"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def test_query_not_included_by_default(self):
|
|
248
|
+
with context(QUERY_STRING="query=foo") as c:
|
|
249
|
+
assert c.request.make_url() == "http://localhost/"
|
|
250
|
+
|
|
251
|
+
def test_query_dict(self):
|
|
252
|
+
with context() as c:
|
|
253
|
+
self.assert_equal_with_query(
|
|
254
|
+
c.request.make_url(query={"a": 1, "b": "2 3"}),
|
|
255
|
+
"http://localhost/?a=1&b=2+3",
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def test_query_drops_items_with_None_value(self):
|
|
259
|
+
with context() as c:
|
|
260
|
+
self.assert_equal_with_query(
|
|
261
|
+
c.request.make_url(query={"a": 1, "b": None}),
|
|
262
|
+
"http://localhost/?a=1",
|
|
263
|
+
)
|
|
264
|
+
self.assert_equal_with_query(
|
|
265
|
+
c.request.make_url(query=[("a", 1), ("b", None)]),
|
|
266
|
+
"http://localhost/?a=1",
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def test_query_kwargs(self):
|
|
270
|
+
with context() as c:
|
|
271
|
+
self.assert_equal_with_query(
|
|
272
|
+
c.request.make_url(query={"a": 1}, b=2),
|
|
273
|
+
"http://localhost/?a=1&b=2",
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def test_unicode_path(self):
|
|
277
|
+
e = b"\xc3\xa9".decode("utf8") # e-acute
|
|
278
|
+
with context() as c:
|
|
279
|
+
assert c.request.make_url(path=e) == "http://localhost/%C3%A9"
|
|
280
|
+
|
|
281
|
+
def test_unicode_path_info(self):
|
|
282
|
+
e = b"\xc3\xa9".decode("utf8") # e-acute
|
|
283
|
+
with context() as c:
|
|
284
|
+
assert c.request.make_url(PATH_INFO=e) == "http://localhost/%C3%A9"
|
|
285
|
+
|
|
286
|
+
def test_unicode_query(self):
|
|
287
|
+
e = b"\xc3\xa9".decode("utf8") # e-acute
|
|
288
|
+
with context() as c:
|
|
289
|
+
assert c.request.make_url(query={"e": e}) == "http://localhost/?e=%C3%A9"
|
|
290
|
+
|
|
291
|
+
def test_it_quotes_paths(self):
|
|
292
|
+
with context() as c:
|
|
293
|
+
assert c.request.make_url(path="/a b") == "http://localhost/a%20b"
|
|
294
|
+
|
|
295
|
+
def test_replace_query_replaces_existing(self):
|
|
296
|
+
with context(QUERY_STRING="a=1") as c:
|
|
297
|
+
assert (
|
|
298
|
+
c.request.make_url(query_replace={"a": "2"}) == "http://localhost/?a=2"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def test_replace_query_adds_new_value(self):
|
|
302
|
+
with context(QUERY_STRING="a=1") as c:
|
|
303
|
+
self.assert_equal_with_query(
|
|
304
|
+
c.request.make_url(query_replace={"b": "2"}),
|
|
305
|
+
"http://localhost/?a=1&b=2",
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def test_add_query_doesnt_replace_existing(self):
|
|
309
|
+
with context(QUERY_STRING="a=1") as c:
|
|
310
|
+
self.assert_equal_with_query(
|
|
311
|
+
c.request.make_url(query_add={"a": "2"}),
|
|
312
|
+
"http://localhost/?a=1&a=2",
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
def test_add_query_works_on_specified_query(self):
|
|
316
|
+
with context(QUERY_STRING="a=1") as c:
|
|
317
|
+
assert (
|
|
318
|
+
c.request.make_url(query="", query_add={"a": "2"})
|
|
319
|
+
== "http://localhost/?a=2"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
def test_add_query_does_not_mutate_request_query(self):
|
|
323
|
+
with context(QUERY_STRING="a=1") as c:
|
|
324
|
+
c.request.make_url(query_add={"a": "2"})
|
|
325
|
+
assert list(c.request.query.allitems()) == [("a", "1")]
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class TestResolveURL(object):
|
|
329
|
+
def test_relative_path(self):
|
|
330
|
+
with context(SCRIPT_NAME="/script", PATH_INFO="/pathinfo") as c:
|
|
331
|
+
assert c.request.resolve_url("foo") == "http://localhost/script/foo"
|
|
332
|
+
|
|
333
|
+
def test_absolute_path_unspecified_relative(self):
|
|
334
|
+
with context(SCRIPT_NAME="/script", PATH_INFO="/pathinfo") as c:
|
|
335
|
+
assert (
|
|
336
|
+
c.request.resolve_url("/foo", relative="app")
|
|
337
|
+
== "http://localhost/script/foo"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
def test_absolute_path_app_relative(self):
|
|
341
|
+
with context(SCRIPT_NAME="/script", PATH_INFO="/pathinfo") as c:
|
|
342
|
+
assert (
|
|
343
|
+
c.request.resolve_url("/foo", relative="app")
|
|
344
|
+
== "http://localhost/script/foo"
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def test_absolute_path_server_relative(self):
|
|
348
|
+
with context(SCRIPT_NAME="/script", PATH_INFO="/pathinfo") as c:
|
|
349
|
+
assert (
|
|
350
|
+
c.request.resolve_url("/foo", relative="server")
|
|
351
|
+
== "http://localhost/foo"
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
def test_ignores_server_port_if_host_header_present(self):
|
|
355
|
+
with context(
|
|
356
|
+
environ={"wsgi.url_scheme": "https", "SERVER_PORT": "81"},
|
|
357
|
+
HTTP_HOST="localhost",
|
|
358
|
+
) as c:
|
|
359
|
+
assert c.request.resolve_url("/foo") == "https://localhost/foo"
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class TestCurrentRequest(object):
|
|
363
|
+
def test_currentrequest_returns_current_request(self):
|
|
364
|
+
from fresco import currentrequest
|
|
365
|
+
|
|
366
|
+
with context() as c:
|
|
367
|
+
assert currentrequest() is c.request
|
|
368
|
+
|
|
369
|
+
def test_currentrequest_returns_None(self):
|
|
370
|
+
from fresco import currentrequest
|
|
371
|
+
|
|
372
|
+
assert currentrequest() is None
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class TestMultipart(object):
|
|
376
|
+
filename = "test.txt"
|
|
377
|
+
filedata = "123456\n"
|
|
378
|
+
boundary = "---------------------------1234"
|
|
379
|
+
|
|
380
|
+
post_data = (
|
|
381
|
+
(
|
|
382
|
+
"--{boundary}\r\n"
|
|
383
|
+
"Content-Disposition: form-data; "
|
|
384
|
+
'name="uploaded_file"; filename="{filename}"\r\n'
|
|
385
|
+
"Content-Type: text/plain\r\n"
|
|
386
|
+
"\r\n"
|
|
387
|
+
"{filedata}\r\n"
|
|
388
|
+
"--{boundary}"
|
|
389
|
+
"--\r\n"
|
|
390
|
+
)
|
|
391
|
+
.format(boundary=boundary, filedata=filedata, filename=filename)
|
|
392
|
+
.encode("latin1")
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
request_args = {
|
|
396
|
+
"wsgi_input": post_data,
|
|
397
|
+
"REQUEST_METHOD": "POST",
|
|
398
|
+
"CONTENT_TYPE": "multipart/form-data; boundary=" + boundary,
|
|
399
|
+
"CONTENT_LENGTH": str(len(post_data)),
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
def test_files_populated(self):
|
|
403
|
+
with FrescoApp().requestcontext(**self.request_args) as c:
|
|
404
|
+
request = c.request
|
|
405
|
+
|
|
406
|
+
assert len(request.files) == 1
|
|
407
|
+
assert "uploaded_file" in request.files
|
|
408
|
+
|
|
409
|
+
def test_file_content_available(self):
|
|
410
|
+
with FrescoApp().requestcontext(**self.request_args) as c:
|
|
411
|
+
request = c.request
|
|
412
|
+
|
|
413
|
+
b = BytesIO()
|
|
414
|
+
request.files["uploaded_file"].save(b)
|
|
415
|
+
assert b.getvalue() == self.filedata.encode("latin1")
|
|
416
|
+
|
|
417
|
+
def test_headers_available(self):
|
|
418
|
+
with FrescoApp().requestcontext(**self.request_args) as c:
|
|
419
|
+
request = c.request
|
|
420
|
+
|
|
421
|
+
assert (
|
|
422
|
+
request.files["uploaded_file"].headers["content-type"] == "text/plain"
|
|
423
|
+
)
|
|
424
|
+
assert request.files["uploaded_file"].filename == self.filename
|
|
425
|
+
|
|
426
|
+
def test_quotes_in_input_names_are_decoded(self):
|
|
427
|
+
"""
|
|
428
|
+
Field names in multipart form data must be decoded as RFC822
|
|
429
|
+
quoted-strings
|
|
430
|
+
"""
|
|
431
|
+
data = (
|
|
432
|
+
b"----BOUNDARY\r\n"
|
|
433
|
+
b'Content-Disposition: form-data; name="\\"qtext\\"";\r\n'
|
|
434
|
+
b"\r\n"
|
|
435
|
+
b"foo\r\n"
|
|
436
|
+
b"----BOUNDARY"
|
|
437
|
+
b"--\r\n"
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
env = {
|
|
441
|
+
"wsgi_input": data,
|
|
442
|
+
"REQUEST_METHOD": "POST",
|
|
443
|
+
"CONTENT_TYPE": "multipart/form-data; boundary=--BOUNDARY",
|
|
444
|
+
"CONTENT_LENGTH": str(len(data)),
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
with FrescoApp().requestcontext(**env) as c:
|
|
448
|
+
assert list(c.request.form.items()) == [('"qtext"', "foo")]
|
|
@@ -0,0 +1,107 @@
|
|
|
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 pytest
|
|
16
|
+
|
|
17
|
+
from fresco import FrescoApp
|
|
18
|
+
from fresco.response import Response
|
|
19
|
+
from fresco.routing import GET
|
|
20
|
+
from fresco.requestcontext import RequestContext
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestRequestContext(object):
|
|
24
|
+
def test_instantiation(self):
|
|
25
|
+
"""
|
|
26
|
+
Can we cleanly instantiate a RequestContext object?
|
|
27
|
+
"""
|
|
28
|
+
RequestContext()
|
|
29
|
+
|
|
30
|
+
def test_app_populates_request_object(self):
|
|
31
|
+
def view():
|
|
32
|
+
from fresco.core import context
|
|
33
|
+
|
|
34
|
+
assert context.request is not None
|
|
35
|
+
return Response([""])
|
|
36
|
+
|
|
37
|
+
app = FrescoApp()
|
|
38
|
+
app.route("/", GET, view)
|
|
39
|
+
|
|
40
|
+
with app.requestcontext("/"):
|
|
41
|
+
app.view()
|
|
42
|
+
|
|
43
|
+
def test_context_returns_correct_request_for_each_app(self):
|
|
44
|
+
from time import sleep
|
|
45
|
+
from threading import Thread, current_thread
|
|
46
|
+
|
|
47
|
+
threadcount = 3
|
|
48
|
+
itercount = 200
|
|
49
|
+
calls = []
|
|
50
|
+
|
|
51
|
+
def view():
|
|
52
|
+
from fresco.core import context
|
|
53
|
+
|
|
54
|
+
request_id = id(context.request)
|
|
55
|
+
|
|
56
|
+
def generate_response():
|
|
57
|
+
for i in range(itercount):
|
|
58
|
+
assert id(context.request) == request_id
|
|
59
|
+
calls.append(current_thread().ident)
|
|
60
|
+
sleep(0.0001)
|
|
61
|
+
yield str(request_id).encode("ascii")
|
|
62
|
+
|
|
63
|
+
return Response(list(generate_response()))
|
|
64
|
+
|
|
65
|
+
app = FrescoApp()
|
|
66
|
+
app.route("/", GET, view)
|
|
67
|
+
|
|
68
|
+
def do_request():
|
|
69
|
+
with app.requestcontext("/"):
|
|
70
|
+
app.view()
|
|
71
|
+
|
|
72
|
+
threads = [Thread(target=do_request) for i in range(threadcount)]
|
|
73
|
+
|
|
74
|
+
for t in threads:
|
|
75
|
+
t.start()
|
|
76
|
+
for t in threads:
|
|
77
|
+
t.join()
|
|
78
|
+
|
|
79
|
+
assert len(calls) == threadcount * itercount
|
|
80
|
+
|
|
81
|
+
# Check that threaded requests were genuinely interleaved
|
|
82
|
+
for i1, i2 in zip(calls[: itercount - 1], calls[1:itercount]):
|
|
83
|
+
if i1 != i2:
|
|
84
|
+
break
|
|
85
|
+
else:
|
|
86
|
+
raise AssertionError("Output does not appear interleaved")
|
|
87
|
+
|
|
88
|
+
def test_context_does_not_inherit_from_parent(self):
|
|
89
|
+
c = RequestContext()
|
|
90
|
+
c.push(foo=1)
|
|
91
|
+
c.push(bar=2)
|
|
92
|
+
with pytest.raises(AttributeError):
|
|
93
|
+
c.foo
|
|
94
|
+
|
|
95
|
+
def test_child_context_overrides_parent(self):
|
|
96
|
+
c = RequestContext()
|
|
97
|
+
c.push(foo=1)
|
|
98
|
+
c.push(foo=2)
|
|
99
|
+
assert c.foo == 2
|
|
100
|
+
|
|
101
|
+
def test_pop_context_removes_keys(self):
|
|
102
|
+
c = RequestContext()
|
|
103
|
+
c.push(foo=1)
|
|
104
|
+
c.push(foo=2)
|
|
105
|
+
assert c.foo == 2
|
|
106
|
+
c.pop()
|
|
107
|
+
assert c.foo == 1
|