1# -*- coding: utf-8 -*- 2 3from datetime import datetime 4import functools 5import random 6 7import pytest 8 9import falcon 10from falcon import testing 11from falcon import util 12from falcon.util import compat, json, misc, structures, uri 13 14 15def _arbitrary_uris(count, length): 16 return ( 17 u''.join( 18 [random.choice(uri._ALL_ALLOWED) 19 for _ in range(length)] 20 ) for __ in range(count) 21 ) 22 23 24class TestFalconUtils(object): 25 26 def setup_method(self, method): 27 # NOTE(cabrera): for DRYness - used in uri.[de|en]code tests 28 # below. 29 self.uris = _arbitrary_uris(count=100, length=32) 30 31 def test_deprecated_decorator(self): 32 msg = 'Please stop using this thing. It is going away.' 33 34 @util.deprecated(msg) 35 def old_thing(): 36 pass 37 38 with pytest.warns(UserWarning) as rec: 39 old_thing() 40 41 warn = rec.pop() 42 assert msg in str(warn.message) 43 44 def test_http_now(self): 45 expected = datetime.utcnow() 46 actual = falcon.http_date_to_dt(falcon.http_now()) 47 48 delta = actual - expected 49 delta_sec = abs(delta.days * 86400 + delta.seconds) 50 51 assert delta_sec <= 1 52 53 def test_dt_to_http(self): 54 assert falcon.dt_to_http(datetime(2013, 4, 4)) == 'Thu, 04 Apr 2013 00:00:00 GMT' 55 56 assert falcon.dt_to_http( 57 datetime(2013, 4, 4, 10, 28, 54) 58 ) == 'Thu, 04 Apr 2013 10:28:54 GMT' 59 60 def test_http_date_to_dt(self): 61 assert falcon.http_date_to_dt('Thu, 04 Apr 2013 00:00:00 GMT') == datetime(2013, 4, 4) 62 63 assert falcon.http_date_to_dt( 64 'Thu, 04 Apr 2013 10:28:54 GMT' 65 ) == datetime(2013, 4, 4, 10, 28, 54) 66 67 with pytest.raises(ValueError): 68 falcon.http_date_to_dt('Thu, 04-Apr-2013 10:28:54 GMT') 69 70 assert falcon.http_date_to_dt( 71 'Thu, 04-Apr-2013 10:28:54 GMT', obs_date=True 72 ) == datetime(2013, 4, 4, 10, 28, 54) 73 74 with pytest.raises(ValueError): 75 falcon.http_date_to_dt('Sun Nov 6 08:49:37 1994') 76 77 with pytest.raises(ValueError): 78 falcon.http_date_to_dt('Nov 6 08:49:37 1994', obs_date=True) 79 80 assert falcon.http_date_to_dt( 81 'Sun Nov 6 08:49:37 1994', obs_date=True 82 ) == datetime(1994, 11, 6, 8, 49, 37) 83 84 assert falcon.http_date_to_dt( 85 'Sunday, 06-Nov-94 08:49:37 GMT', obs_date=True 86 ) == datetime(1994, 11, 6, 8, 49, 37) 87 88 def test_pack_query_params_none(self): 89 assert falcon.to_query_str({}) == '' 90 91 def test_pack_query_params_one(self): 92 assert falcon.to_query_str({'limit': 10}) == '?limit=10' 93 94 assert falcon.to_query_str( 95 {'things': [1, 2, 3]}) == '?things=1,2,3' 96 97 assert falcon.to_query_str({'things': ['a']}) == '?things=a' 98 99 assert falcon.to_query_str( 100 {'things': ['a', 'b']}) == '?things=a,b' 101 102 expected = ('?things=a&things=b&things=&things=None' 103 '&things=true&things=false&things=0') 104 105 actual = falcon.to_query_str( 106 {'things': ['a', 'b', '', None, True, False, 0]}, 107 comma_delimited_lists=False 108 ) 109 110 assert actual == expected 111 112 def test_pack_query_params_several(self): 113 garbage_in = { 114 'limit': 17, 115 'echo': True, 116 'doit': False, 117 'x': 'val', 118 'y': 0.2 119 } 120 121 query_str = falcon.to_query_str(garbage_in) 122 fields = query_str[1:].split('&') 123 124 garbage_out = {} 125 for field in fields: 126 k, v = field.split('=') 127 garbage_out[k] = v 128 129 expected = { 130 'echo': 'true', 131 'limit': '17', 132 'x': 'val', 133 'y': '0.2', 134 'doit': 'false'} 135 136 assert expected == garbage_out 137 138 def test_uri_encode(self): 139 url = 'http://example.com/v1/fizbit/messages?limit=3&echo=true' 140 assert uri.encode(url) == url 141 142 url = 'http://example.com/v1/fiz bit/messages' 143 expected = 'http://example.com/v1/fiz%20bit/messages' 144 assert uri.encode(url) == expected 145 146 url = u'http://example.com/v1/fizbit/messages?limit=3&e\u00e7ho=true' 147 expected = ('http://example.com/v1/fizbit/messages' 148 '?limit=3&e%C3%A7ho=true') 149 assert uri.encode(url) == expected 150 151 def test_uri_encode_double(self): 152 url = 'http://example.com/v1/fiz bit/messages' 153 expected = 'http://example.com/v1/fiz%20bit/messages' 154 assert uri.encode(uri.encode(url)) == expected 155 156 url = u'http://example.com/v1/fizbit/messages?limit=3&e\u00e7ho=true' 157 expected = ('http://example.com/v1/fizbit/messages' 158 '?limit=3&e%C3%A7ho=true') 159 assert uri.encode(uri.encode(url)) == expected 160 161 url = 'http://example.com/v1/fiz%bit/mess%ages/%' 162 expected = 'http://example.com/v1/fiz%25bit/mess%25ages/%25' 163 assert uri.encode(uri.encode(url)) == expected 164 165 url = 'http://example.com/%%' 166 expected = 'http://example.com/%25%25' 167 assert uri.encode(uri.encode(url)) == expected 168 169 # NOTE(kgriffs): Specific example cited in GH issue 170 url = 'http://something?redirect_uri=http%3A%2F%2Fsite' 171 assert uri.encode(url) == url 172 173 hex_digits = 'abcdefABCDEF0123456789' 174 for c1 in hex_digits: 175 for c2 in hex_digits: 176 url = 'http://example.com/%' + c1 + c2 177 encoded = uri.encode(uri.encode(url)) 178 assert encoded == url 179 180 def test_uri_encode_value(self): 181 assert uri.encode_value('abcd') == 'abcd' 182 assert uri.encode_value(u'abcd') == u'abcd' 183 assert uri.encode_value(u'ab cd') == u'ab%20cd' 184 assert uri.encode_value(u'\u00e7') == '%C3%A7' 185 assert uri.encode_value(u'\u00e7\u20ac') == '%C3%A7%E2%82%AC' 186 assert uri.encode_value('ab/cd') == 'ab%2Fcd' 187 assert uri.encode_value('ab+cd=42,9') == 'ab%2Bcd%3D42%2C9' 188 189 def test_uri_decode(self): 190 assert uri.decode('abcd') == 'abcd' 191 assert uri.decode(u'abcd') == u'abcd' 192 assert uri.decode(u'ab%20cd') == u'ab cd' 193 194 assert uri.decode('This thing is %C3%A7') == u'This thing is \u00e7' 195 196 assert uri.decode('This thing is %C3%A7%E2%82%AC') == u'This thing is \u00e7\u20ac' 197 198 assert uri.decode('ab%2Fcd') == 'ab/cd' 199 200 assert uri.decode( 201 'http://example.com?x=ab%2Bcd%3D42%2C9' 202 ) == 'http://example.com?x=ab+cd=42,9' 203 204 def test_uri_decode_unquote_plus(self): 205 assert uri.decode('/disk/lost+found/fd0') == '/disk/lost found/fd0' 206 assert uri.decode('/disk/lost+found/fd0', unquote_plus=True) == ( 207 '/disk/lost found/fd0') 208 assert uri.decode('/disk/lost+found/fd0', unquote_plus=False) == ( 209 '/disk/lost+found/fd0') 210 211 assert uri.decode('http://example.com?x=ab%2Bcd%3D42%2C9') == ( 212 'http://example.com?x=ab+cd=42,9') 213 assert uri.decode('http://example.com?x=ab%2Bcd%3D42%2C9', unquote_plus=True) == ( 214 'http://example.com?x=ab+cd=42,9') 215 assert uri.decode('http://example.com?x=ab%2Bcd%3D42%2C9', unquote_plus=False) == ( 216 'http://example.com?x=ab+cd=42,9') 217 218 def test_prop_uri_encode_models_stdlib_quote(self): 219 equiv_quote = functools.partial( 220 compat.quote, safe=uri._ALL_ALLOWED 221 ) 222 for case in self.uris: 223 expect = equiv_quote(case) 224 actual = uri.encode(case) 225 assert expect == actual 226 227 def test_prop_uri_encode_value_models_stdlib_quote_safe_tilde(self): 228 equiv_quote = functools.partial( 229 compat.quote, safe='~' 230 ) 231 for case in self.uris: 232 expect = equiv_quote(case) 233 actual = uri.encode_value(case) 234 assert expect == actual 235 236 def test_prop_uri_decode_models_stdlib_unquote_plus(self): 237 stdlib_unquote = compat.unquote_plus 238 for case in self.uris: 239 case = uri.encode_value(case) 240 241 expect = stdlib_unquote(case) 242 actual = uri.decode(case) 243 assert expect == actual 244 245 def test_unquote_string(self): 246 assert uri.unquote_string('v') == 'v' 247 assert uri.unquote_string('not-quoted') == 'not-quoted' 248 assert uri.unquote_string('partial-quoted"') == 'partial-quoted"' 249 assert uri.unquote_string('"partial-quoted') == '"partial-quoted' 250 assert uri.unquote_string('"partial-quoted"') == 'partial-quoted' 251 252 def test_parse_query_string(self): 253 query_strinq = ( 254 'a=http%3A%2F%2Ffalconframework.org%3Ftest%3D1' 255 '&b=%7B%22test1%22%3A%20%22data1%22%' 256 '2C%20%22test2%22%3A%20%22data2%22%7D' 257 '&c=1,2,3' 258 '&d=test' 259 '&e=a,,%26%3D%2C' 260 '&f=a&f=a%3Db' 261 '&%C3%A9=a%3Db' 262 ) 263 decoded_url = 'http://falconframework.org?test=1' 264 decoded_json = '{"test1": "data1", "test2": "data2"}' 265 266 result = uri.parse_query_string(query_strinq) 267 assert result['a'] == decoded_url 268 assert result['b'] == decoded_json 269 assert result['c'] == ['1', '2', '3'] 270 assert result['d'] == 'test' 271 assert result['e'] == ['a', '&=,'] 272 assert result['f'] == ['a', 'a=b'] 273 assert result[u'é'] == 'a=b' 274 275 result = uri.parse_query_string(query_strinq, True) 276 assert result['a'] == decoded_url 277 assert result['b'] == decoded_json 278 assert result['c'] == ['1', '2', '3'] 279 assert result['d'] == 'test' 280 assert result['e'] == ['a', '', '&=,'] 281 assert result['f'] == ['a', 'a=b'] 282 assert result[u'é'] == 'a=b' 283 284 def test_parse_host(self): 285 assert uri.parse_host('::1') == ('::1', None) 286 assert uri.parse_host('2001:ODB8:AC10:FE01::') == ('2001:ODB8:AC10:FE01::', None) 287 assert uri.parse_host( 288 '2001:ODB8:AC10:FE01::', default_port=80 289 ) == ('2001:ODB8:AC10:FE01::', 80) 290 291 ipv6_addr = '2001:4801:1221:101:1c10::f5:116' 292 293 assert uri.parse_host(ipv6_addr) == (ipv6_addr, None) 294 assert uri.parse_host('[' + ipv6_addr + ']') == (ipv6_addr, None) 295 assert uri.parse_host('[' + ipv6_addr + ']:28080') == (ipv6_addr, 28080) 296 assert uri.parse_host('[' + ipv6_addr + ']:8080') == (ipv6_addr, 8080) 297 assert uri.parse_host('[' + ipv6_addr + ']:123') == (ipv6_addr, 123) 298 assert uri.parse_host('[' + ipv6_addr + ']:42') == (ipv6_addr, 42) 299 300 assert uri.parse_host('173.203.44.122') == ('173.203.44.122', None) 301 assert uri.parse_host('173.203.44.122', default_port=80) == ('173.203.44.122', 80) 302 assert uri.parse_host('173.203.44.122:27070') == ('173.203.44.122', 27070) 303 assert uri.parse_host('173.203.44.122:123') == ('173.203.44.122', 123) 304 assert uri.parse_host('173.203.44.122:42') == ('173.203.44.122', 42) 305 306 assert uri.parse_host('example.com') == ('example.com', None) 307 assert uri.parse_host('example.com', default_port=443) == ('example.com', 443) 308 assert uri.parse_host('falcon.example.com') == ('falcon.example.com', None) 309 assert uri.parse_host('falcon.example.com:9876') == ('falcon.example.com', 9876) 310 assert uri.parse_host('falcon.example.com:42') == ('falcon.example.com', 42) 311 312 def test_get_http_status(self): 313 assert falcon.get_http_status(404) == falcon.HTTP_404 314 assert falcon.get_http_status(404.3) == falcon.HTTP_404 315 assert falcon.get_http_status('404.3') == falcon.HTTP_404 316 assert falcon.get_http_status(404.9) == falcon.HTTP_404 317 assert falcon.get_http_status('404') == falcon.HTTP_404 318 assert falcon.get_http_status(123) == '123 Unknown' 319 with pytest.raises(ValueError): 320 falcon.get_http_status('not_a_number') 321 with pytest.raises(ValueError): 322 falcon.get_http_status(0) 323 with pytest.raises(ValueError): 324 falcon.get_http_status(0) 325 with pytest.raises(ValueError): 326 falcon.get_http_status(99) 327 with pytest.raises(ValueError): 328 falcon.get_http_status(-404.3) 329 with pytest.raises(ValueError): 330 falcon.get_http_status('-404') 331 with pytest.raises(ValueError): 332 falcon.get_http_status('-404.3') 333 assert falcon.get_http_status(123, 'Go Away') == '123 Go Away' 334 335 def test_etag_dumps_to_header_format(self): 336 etag = structures.ETag('67ab43') 337 338 assert etag.dumps() == '"67ab43"' 339 340 etag.is_weak = True 341 assert etag.dumps() == 'W/"67ab43"' 342 343 assert structures.ETag('67a b43').dumps() == '"67a b43"' 344 345 def test_etag_strong_vs_weak_comparison(self): 346 strong_67ab43_one = structures.ETag.loads('"67ab43"') 347 strong_67ab43_too = structures.ETag.loads('"67ab43"') 348 strong_67aB43 = structures.ETag.loads('"67aB43"') 349 weak_67ab43_one = structures.ETag.loads('W/"67ab43"') 350 weak_67ab43_two = structures.ETag.loads('W/"67ab43"') 351 weak_67aB43 = structures.ETag.loads('W/"67aB43"') 352 353 assert strong_67aB43 == strong_67aB43 354 assert weak_67aB43 == weak_67aB43 355 assert strong_67aB43 == weak_67aB43 356 assert weak_67aB43 == strong_67aB43 357 assert strong_67ab43_one == strong_67ab43_too 358 assert weak_67ab43_one == weak_67ab43_two 359 360 assert strong_67aB43 != strong_67ab43_one 361 assert strong_67ab43_one != strong_67aB43 362 363 assert strong_67aB43.strong_compare(strong_67aB43) 364 assert strong_67ab43_one.strong_compare(strong_67ab43_too) 365 assert not strong_67aB43.strong_compare(strong_67ab43_one) 366 assert not strong_67ab43_one.strong_compare(strong_67aB43) 367 368 assert not strong_67ab43_one.strong_compare(weak_67ab43_one) 369 assert not weak_67ab43_one.strong_compare(strong_67ab43_one) 370 371 assert not weak_67aB43.strong_compare(weak_67aB43) 372 assert not weak_67ab43_one.strong_compare(weak_67ab43_two) 373 374 assert not weak_67ab43_one.strong_compare(weak_67aB43) 375 assert not weak_67aB43.strong_compare(weak_67ab43_one) 376 377 378@pytest.mark.parametrize( 379 'protocol,method', 380 zip( 381 ['https'] * len(falcon.HTTP_METHODS) + ['http'] * len(falcon.HTTP_METHODS), 382 falcon.HTTP_METHODS * 2 383 ) 384) 385def test_simulate_request_protocol(protocol, method): 386 sink_called = [False] 387 388 def sink(req, resp): 389 sink_called[0] = True 390 assert req.protocol == protocol 391 392 app = falcon.API() 393 app.add_sink(sink, '/test') 394 395 client = testing.TestClient(app) 396 397 try: 398 simulate = client.getattr('simulate_' + method.lower()) 399 simulate('/test', protocol=protocol) 400 assert sink_called[0] 401 except AttributeError: 402 # NOTE(kgriffs): simulate_* helpers do not exist for all methods 403 pass 404 405 406@pytest.mark.parametrize('simulate', [ 407 testing.simulate_get, 408 testing.simulate_head, 409 testing.simulate_post, 410 testing.simulate_put, 411 testing.simulate_options, 412 testing.simulate_patch, 413 testing.simulate_delete, 414]) 415def test_simulate_free_functions(simulate): 416 sink_called = [False] 417 418 def sink(req, resp): 419 sink_called[0] = True 420 421 app = falcon.API() 422 app.add_sink(sink, '/test') 423 424 simulate(app, '/test') 425 assert sink_called[0] 426 427 428class TestFalconTestingUtils(object): 429 """Verify some branches not covered elsewhere.""" 430 431 def test_path_escape_chars_in_create_environ(self): 432 env = testing.create_environ('/hello%20world%21') 433 assert env['PATH_INFO'] == '/hello world!' 434 435 def test_no_prefix_allowed_for_query_strings_in_create_environ(self): 436 with pytest.raises(ValueError): 437 testing.create_environ(query_string='?foo=bar') 438 439 @pytest.mark.skipif(compat.PY3, reason='Test does not apply to Py3K') 440 def test_unicode_path_in_create_environ(self): 441 env = testing.create_environ(u'/fancy/unícode') 442 assert env['PATH_INFO'] == '/fancy/un\xc3\xadcode' 443 444 env = testing.create_environ(u'/simple') 445 assert env['PATH_INFO'] == '/simple' 446 447 def test_plus_in_path_in_create_environ(self): 448 env = testing.create_environ('/mnt/grub2/lost+found/inode001') 449 assert env['PATH_INFO'] == '/mnt/grub2/lost+found/inode001' 450 451 def test_none_header_value_in_create_environ(self): 452 env = testing.create_environ('/', headers={'X-Foo': None}) 453 assert env['HTTP_X_FOO'] == '' 454 455 def test_decode_empty_result(self): 456 app = falcon.API() 457 client = testing.TestClient(app) 458 response = client.simulate_request(path='/') 459 assert response.text == '' 460 461 def test_httpnow_alias_for_backwards_compat(self): 462 assert testing.httpnow is util.http_now 463 464 def test_default_headers(self): 465 app = falcon.API() 466 resource = testing.SimpleTestResource() 467 app.add_route('/', resource) 468 469 headers = { 470 'Authorization': 'Bearer 123', 471 } 472 473 client = testing.TestClient(app, headers=headers) 474 475 client.simulate_get() 476 assert resource.captured_req.auth == headers['Authorization'] 477 478 client.simulate_get(headers=None) 479 assert resource.captured_req.auth == headers['Authorization'] 480 481 def test_default_headers_with_override(self): 482 app = falcon.API() 483 resource = testing.SimpleTestResource() 484 app.add_route('/', resource) 485 486 override_before = 'something-something' 487 override_after = 'something-something'[::-1] 488 489 headers = { 490 'Authorization': 'Bearer XYZ', 491 'Accept': 'application/vnd.siren+json', 492 'X-Override-Me': override_before, 493 } 494 495 client = testing.TestClient(app, headers=headers) 496 client.simulate_get(headers={'X-Override-Me': override_after}) 497 498 assert resource.captured_req.auth == headers['Authorization'] 499 assert resource.captured_req.accept == headers['Accept'] 500 assert resource.captured_req.get_header('X-Override-Me') == override_after 501 502 def test_status(self): 503 app = falcon.API() 504 resource = testing.SimpleTestResource(status=falcon.HTTP_702) 505 app.add_route('/', resource) 506 client = testing.TestClient(app) 507 508 result = client.simulate_get() 509 assert result.status == falcon.HTTP_702 510 511 def test_wsgi_iterable_not_closeable(self): 512 result = testing.Result([], falcon.HTTP_200, []) 513 assert not result.content 514 assert result.json is None 515 516 def test_path_must_start_with_slash(self): 517 app = falcon.API() 518 app.add_route('/', testing.SimpleTestResource()) 519 client = testing.TestClient(app) 520 with pytest.raises(ValueError): 521 client.simulate_get('foo') 522 523 def test_cached_text_in_result(self): 524 app = falcon.API() 525 app.add_route('/', testing.SimpleTestResource(body='test')) 526 client = testing.TestClient(app) 527 528 result = client.simulate_get() 529 assert result.text == result.text 530 531 def test_simple_resource_body_json_xor(self): 532 with pytest.raises(ValueError): 533 testing.SimpleTestResource(body='', json={}) 534 535 def test_query_string(self): 536 class SomeResource(object): 537 def on_get(self, req, resp): 538 doc = {} 539 540 doc['oid'] = req.get_param_as_int('oid') 541 doc['detailed'] = req.get_param_as_bool('detailed') 542 doc['things'] = req.get_param_as_list('things', int) 543 doc['query_string'] = req.query_string 544 545 resp.body = json.dumps(doc) 546 547 app = falcon.API() 548 app.req_options.auto_parse_qs_csv = True 549 app.add_route('/', SomeResource()) 550 client = testing.TestClient(app) 551 552 result = client.simulate_get(query_string='oid=42&detailed=no&things=1') 553 assert result.json['oid'] == 42 554 assert not result.json['detailed'] 555 assert result.json['things'] == [1] 556 557 params = {'oid': 42, 'detailed': False} 558 result = client.simulate_get(params=params) 559 assert result.json['oid'] == params['oid'] 560 assert not result.json['detailed'] 561 assert result.json['things'] is None 562 563 params = {'oid': 1978, 'detailed': 'yes', 'things': [1, 2, 3]} 564 result = client.simulate_get(params=params) 565 assert result.json['oid'] == params['oid'] 566 assert result.json['detailed'] 567 assert result.json['things'] == params['things'] 568 569 expected_qs = 'things=1,2,3' 570 result = client.simulate_get(params={'things': [1, 2, 3]}) 571 assert result.json['query_string'] == expected_qs 572 573 expected_qs = 'things=1&things=2&things=3' 574 result = client.simulate_get(params={'things': [1, 2, 3]}, 575 params_csv=False) 576 assert result.json['query_string'] == expected_qs 577 578 def test_query_string_no_question(self): 579 app = falcon.API() 580 app.add_route('/', testing.SimpleTestResource()) 581 client = testing.TestClient(app) 582 with pytest.raises(ValueError): 583 client.simulate_get(query_string='?x=1') 584 585 def test_query_string_in_path(self): 586 app = falcon.API() 587 resource = testing.SimpleTestResource() 588 app.add_route('/thing', resource) 589 client = testing.TestClient(app) 590 591 with pytest.raises(ValueError): 592 client.simulate_get(path='/thing?x=1', query_string='things=1,2,3') 593 with pytest.raises(ValueError): 594 client.simulate_get(path='/thing?x=1', params={'oid': 1978}) 595 with pytest.raises(ValueError): 596 client.simulate_get(path='/thing?x=1', query_string='things=1,2,3', 597 params={'oid': 1978}) 598 599 client.simulate_get(path='/thing?detailed=no&oid=1337') 600 assert resource.captured_req.path == '/thing' 601 assert resource.captured_req.query_string == 'detailed=no&oid=1337' 602 603 @pytest.mark.parametrize('document', [ 604 # NOTE(vytas): using an exact binary fraction here to avoid special 605 # code branch for approximate equality as it is not the focus here 606 16.0625, 607 123456789, 608 True, 609 '', 610 u'I am a \u1d0a\ua731\u1d0f\u0274 string.', 611 [1, 3, 3, 7], 612 {u'message': u'\xa1Hello Unicode! \U0001F638'}, 613 { 614 'count': 4, 615 'items': [ 616 {'number': 'one'}, 617 {'number': 'two'}, 618 {'number': 'three'}, 619 {'number': 'four'}, 620 ], 621 'next': None, 622 }, 623 ]) 624 def test_simulate_json_body(self, document): 625 app = falcon.API() 626 resource = testing.SimpleTestResource() 627 app.add_route('/', resource) 628 629 json_types = ('application/json', 'application/json; charset=UTF-8') 630 client = testing.TestClient(app) 631 client.simulate_post('/', json=document) 632 captured_body = resource.captured_req.bounded_stream.read().decode('utf-8') 633 assert json.loads(captured_body) == document 634 assert resource.captured_req.content_type in json_types 635 636 headers = { 637 'Content-Type': 'x-falcon/peregrine', 638 'X-Falcon-Type': 'peregrine', 639 } 640 body = 'If provided, `json` parameter overrides `body`.' 641 client.simulate_post('/', headers=headers, body=body, json=document) 642 assert resource.captured_req.media == document 643 assert resource.captured_req.content_type in json_types 644 assert resource.captured_req.get_header('X-Falcon-Type') == 'peregrine' 645 646 @pytest.mark.parametrize('remote_addr', [ 647 None, 648 '127.0.0.1', 649 '8.8.8.8', 650 '104.24.101.85', 651 '2606:4700:30::6818:6455', 652 ]) 653 def test_simulate_remote_addr(self, remote_addr): 654 class ShowMyIPResource(object): 655 def on_get(self, req, resp): 656 resp.body = req.remote_addr 657 resp.content_type = falcon.MEDIA_TEXT 658 659 app = falcon.API() 660 app.add_route('/', ShowMyIPResource()) 661 662 client = testing.TestClient(app) 663 resp = client.simulate_get('/', remote_addr=remote_addr) 664 assert resp.status_code == 200 665 666 if remote_addr is None: 667 assert resp.text == '127.0.0.1' 668 else: 669 assert resp.text == remote_addr 670 671 def test_simulate_hostname(self): 672 app = falcon.API() 673 resource = testing.SimpleTestResource() 674 app.add_route('/', resource) 675 676 client = testing.TestClient(app) 677 client.simulate_get('/', protocol='https', 678 host='falcon.readthedocs.io') 679 assert resource.captured_req.uri == 'https://falcon.readthedocs.io/' 680 681 @pytest.mark.parametrize('extras,expected_headers', [ 682 ( 683 {}, 684 (('user-agent', 'curl/7.24.0 (x86_64-apple-darwin12.0)'),), 685 ), 686 ( 687 {'HTTP_USER_AGENT': 'URL/Emacs', 'HTTP_X_FALCON': 'peregrine'}, 688 (('user-agent', 'URL/Emacs'), ('x-falcon', 'peregrine')), 689 ), 690 ]) 691 def test_simulate_with_environ_extras(self, extras, expected_headers): 692 app = falcon.API() 693 resource = testing.SimpleTestResource() 694 app.add_route('/', resource) 695 696 client = testing.TestClient(app) 697 client.simulate_get('/', extras=extras) 698 699 for header, value in expected_headers: 700 assert resource.captured_req.get_header(header) == value 701 702 def test_override_method_with_extras(self): 703 app = falcon.API() 704 app.add_route('/', testing.SimpleTestResource(body='test')) 705 client = testing.TestClient(app) 706 707 with pytest.raises(ValueError): 708 client.simulate_get('/', extras={'REQUEST_METHOD': 'PATCH'}) 709 710 resp = client.simulate_get('/', extras={'REQUEST_METHOD': 'GET'}) 711 assert resp.status_code == 200 712 assert resp.text == 'test' 713 714 715class TestNoApiClass(testing.TestCase): 716 def test_something(self): 717 self.assertTrue(isinstance(self.app, falcon.API)) 718 719 720class TestSetupApi(testing.TestCase): 721 def setUp(self): 722 super(TestSetupApi, self).setUp() 723 self.api = falcon.API() 724 725 def test_something(self): 726 self.assertTrue(isinstance(self.api, falcon.API)) 727 728 729def test_get_argnames(): 730 def foo(a, b, c): 731 pass 732 733 class Bar(object): 734 def __call__(self, a, b): 735 pass 736 737 assert misc.get_argnames(foo) == ['a', 'b', 'c'] 738 assert misc.get_argnames(Bar()) == ['a', 'b'] 739 740 # NOTE(kgriffs): This difference will go away once we drop Python 2.7 741 # support, so we just use this regression test to ensure the status quo. 742 expected = ['b', 'c'] if compat.PY3 else ['a', 'b', 'c'] 743 assert misc.get_argnames(functools.partial(foo, 42)) == expected 744 745 746class TestContextType(object): 747 748 class CustomContextType(structures.Context): 749 def __init__(self): 750 pass 751 752 @pytest.mark.parametrize('context_type', [ 753 CustomContextType, 754 structures.Context, 755 ]) 756 def test_attributes(self, context_type): 757 ctx = context_type() 758 759 ctx.foo = 'bar' 760 ctx.details = None 761 ctx._cache = {} 762 763 assert ctx.foo == 'bar' 764 assert ctx.details is None 765 assert ctx._cache == {} 766 767 with pytest.raises(AttributeError): 768 ctx.cache_strategy 769 770 @pytest.mark.parametrize('context_type', [ 771 CustomContextType, 772 structures.Context, 773 ]) 774 def test_items_from_attributes(self, context_type): 775 ctx = context_type() 776 777 ctx.foo = 'bar' 778 ctx.details = None 779 ctx._cache = {} 780 781 assert ctx['foo'] == 'bar' 782 assert ctx['details'] is None 783 assert ctx['_cache'] == {} 784 785 with pytest.raises(KeyError): 786 ctx['cache_strategy'] 787 788 assert 'foo' in ctx 789 assert '_cache' in ctx 790 assert 'cache_strategy' not in ctx 791 792 @pytest.mark.parametrize('context_type', [ 793 CustomContextType, 794 structures.Context, 795 ]) 796 def test_attributes_from_items(self, context_type): 797 ctx = context_type() 798 799 ctx['foo'] = 'bar' 800 ctx['details'] = None 801 ctx['_cache'] = {} 802 ctx['cache_strategy'] = 'lru' 803 804 assert ctx['cache_strategy'] == 'lru' 805 del ctx['cache_strategy'] 806 807 assert ctx['foo'] == 'bar' 808 assert ctx['details'] is None 809 assert ctx['_cache'] == {} 810 811 with pytest.raises(KeyError): 812 ctx['cache_strategy'] 813 814 @pytest.mark.parametrize('context_type,type_name', [ 815 (CustomContextType, 'CustomContextType'), 816 (structures.Context, 'Context'), 817 ]) 818 def test_dict_interface(self, context_type, type_name): 819 ctx = context_type() 820 821 ctx['foo'] = 'bar' 822 ctx['details'] = None 823 ctx[1] = 'one' 824 ctx[2] = 'two' 825 826 assert ctx == {'foo': 'bar', 'details': None, 1: 'one', 2: 'two'} 827 assert ctx != {'bar': 'foo', 'details': None, 1: 'one', 2: 'two'} 828 assert ctx != {} 829 830 copy = ctx.copy() 831 assert isinstance(copy, context_type) 832 assert copy == ctx 833 assert copy == {'foo': 'bar', 'details': None, 1: 'one', 2: 'two'} 834 copy.pop('foo') 835 assert copy != ctx 836 837 assert set(key for key in ctx) == {'foo', 'details', 1, 2} 838 839 assert ctx.get('foo') == 'bar' 840 assert ctx.get('bar') is None 841 assert ctx.get('bar', frozenset('hello')) == frozenset('hello') 842 false = ctx.get('bar', False) 843 assert isinstance(false, bool) 844 assert not false 845 846 assert len(ctx) == 4 847 assert ctx.pop(3) is None 848 assert ctx.pop(3, 'not found') == 'not found' 849 assert ctx.pop('foo') == 'bar' 850 assert ctx.pop(1) == 'one' 851 assert ctx.pop(2) == 'two' 852 assert len(ctx) == 1 853 854 assert repr(ctx) == type_name + "({'details': None})" 855 assert str(ctx) == type_name + "({'details': None})" 856 assert '{}'.format(ctx) == type_name + "({'details': None})" 857 858 with pytest.raises(TypeError): 859 {ctx: ctx} 860 861 ctx.clear() 862 assert ctx == {} 863 assert len(ctx) == 0 864 865 ctx['key'] = 'value' 866 assert ctx.popitem() == ('key', 'value') 867 868 ctx.setdefault('numbers', []).append(1) 869 ctx.setdefault('numbers', []).append(2) 870 ctx.setdefault('numbers', []).append(3) 871 assert ctx['numbers'] == [1, 2, 3] 872 873 @pytest.mark.parametrize('context_type', [ 874 CustomContextType, 875 structures.Context, 876 ]) 877 def test_keys_and_values(self, context_type): 878 ctx = context_type() 879 ctx.update((number, number ** 2) for number in range(1, 5)) 880 881 assert set(ctx.keys()) == {1, 2, 3, 4} 882 assert set(ctx.values()) == {1, 4, 9, 16} 883 assert set(ctx.items()) == {(1, 1), (2, 4), (3, 9), (4, 16)} 884 885 @pytest.mark.skipif(compat.PY3, reason='python2-specific dict methods') 886 def test_python2_dict_methods(self): 887 ctx = structures.Context() 888 ctx.update((number, number ** 2) for number in range(1, 5)) 889 890 assert set(ctx.keys()) == set(ctx.iterkeys()) == set(ctx.viewkeys()) 891 assert set(ctx.values()) == set(ctx.itervalues()) == set(ctx.viewvalues()) 892 assert set(ctx.items()) == set(ctx.iteritems()) == set(ctx.viewitems()) 893 894 assert ctx.has_key(2) # noqa 895