@@ -36,6 +36,10 @@ def _wrapped_with_retry(*args, **kwargs):
36
36
return _create_wrapped_retry_function
37
37
38
38
39
+ class InternalServerError (Exception ):
40
+ pass
41
+
42
+
39
43
class SmokeTestApplication (object ):
40
44
41
45
# Number of seconds to wait after redeploy before starting
@@ -87,13 +91,59 @@ def websocket_connect_url(self):
87
91
)
88
92
)
89
93
94
+ @retry (max_attempts = 10 , delay = 5 )
90
95
def get_json (self , url ):
96
+ try :
97
+ return self ._get_json (url )
98
+ except requests .exceptions .HTTPError :
99
+ pass
100
+
101
+ def _get_json (self , url ):
91
102
if not url .startswith ('/' ):
92
103
url = '/' + url
93
104
response = requests .get (self .url + url )
94
105
response .raise_for_status ()
95
106
return response .json ()
96
107
108
+ @retry (max_attempts = 10 , delay = 5 )
109
+ def get_response (self , url , headers = None ):
110
+ try :
111
+ return self ._send_request ('GET' , url , headers = headers )
112
+ except InternalServerError :
113
+ pass
114
+
115
+ def _send_request (self , http_method , url , headers = None , data = None ):
116
+ kwargs = {}
117
+ if headers is not None :
118
+ kwargs ['headers' ] = headers
119
+ if data is not None :
120
+ kwargs ['data' ] = data
121
+ response = requests .request (http_method , self .url + url , ** kwargs )
122
+ if response .status_code >= 500 :
123
+ raise InternalServerError ()
124
+ return response
125
+
126
+ @retry (max_attempts = 10 , delay = 5 )
127
+ def post_response (self , url , headers = None , data = None ):
128
+ try :
129
+ return self ._send_request ('POST' , url , headers = headers , data = data )
130
+ except InternalServerError :
131
+ pass
132
+
133
+ @retry (max_attempts = 10 , delay = 5 )
134
+ def put_response (self , url ):
135
+ try :
136
+ return self ._send_request ('PUT' , url )
137
+ except InternalServerError :
138
+ pass
139
+
140
+ @retry (max_attempts = 10 , delay = 5 )
141
+ def options_response (self , url ):
142
+ try :
143
+ return self ._send_request ('OPTIONS' , url )
144
+ except InternalServerError :
145
+ pass
146
+
97
147
def redeploy_once (self ):
98
148
# Redeploy the application once. If a redeploy
99
149
# has already happened, this function is a noop.
@@ -111,16 +161,12 @@ def redeploy_once(self):
111
161
self ._wait_for_stablize ()
112
162
time .sleep (self ._POLLING_DELAY )
113
163
114
- @retry (max_attempts = 10 , delay = 5 )
115
164
def _wait_for_stablize (self ):
116
165
# After a deployment we sometimes need to wait for
117
166
# API Gateway to propagate all of its changes.
118
167
# We're going to give it num_attempts to give us a
119
168
# 200 response before failing.
120
- try :
121
- return self .get_json ('/' )
122
- except requests .exceptions .HTTPError :
123
- pass
169
+ return self .get_json ('/' )
124
170
125
171
126
172
@pytest .fixture
@@ -284,77 +330,75 @@ def _get_resource_id(apig_client, rest_api_id, path):
284
330
285
331
286
332
def test_supports_post (smoke_test_app ):
287
- app_url = smoke_test_app .url
288
- response = requests .post (app_url + '/post' )
333
+ response = smoke_test_app .post_response ('/post' )
289
334
response .raise_for_status ()
290
335
assert response .json () == {'success' : True }
291
336
with pytest .raises (requests .HTTPError ):
292
337
# Only POST is supported.
293
- response = requests . get ( app_url + '/post' )
338
+ response = smoke_test_app . get_response ( '/post' )
294
339
response .raise_for_status ()
295
340
296
341
297
342
def test_supports_put (smoke_test_app ):
298
- app_url = smoke_test_app .url
299
- response = requests .put (app_url + '/put' )
343
+ response = smoke_test_app .put_response ('/put' )
300
344
response .raise_for_status ()
301
345
assert response .json () == {'success' : True }
302
346
with pytest .raises (requests .HTTPError ):
303
347
# Only PUT is supported.
304
- response = requests . get ( app_url + '/put' )
348
+ response = smoke_test_app . get_response ( '/put' )
305
349
response .raise_for_status ()
306
350
307
351
308
352
def test_supports_shared_routes (smoke_test_app ):
309
- app_url = smoke_test_app .url
310
- response = requests .get (app_url + '/shared' )
311
- assert response .json () == {'method' : 'GET' }
312
- response = requests .post (app_url + '/shared' )
353
+ response = smoke_test_app .get_json ('/shared' )
354
+ assert response == {'method' : 'GET' }
355
+ response = smoke_test_app .post_response ('/shared' )
313
356
assert response .json () == {'method' : 'POST' }
314
357
315
358
316
359
def test_can_read_json_body_on_post (smoke_test_app ):
317
- app_url = smoke_test_app .url
318
- response = requests .post (
319
- app_url + '/jsonpost' , data = json .dumps ({'hello' : 'world' }),
360
+ response = smoke_test_app .post_response (
361
+ '/jsonpost' , data = json .dumps ({'hello' : 'world' }),
320
362
headers = {'Content-Type' : 'application/json' })
321
363
response .raise_for_status ()
322
364
assert response .json () == {'json_body' : {'hello' : 'world' }}
323
365
324
366
325
367
def test_can_raise_bad_request (smoke_test_app ):
326
- response = requests . get ( smoke_test_app .url + '/badrequest' )
368
+ response = smoke_test_app .get_response ( '/badrequest' )
327
369
assert response .status_code == 400
328
370
assert response .json ()['Code' ] == 'BadRequestError'
329
371
assert response .json ()['Message' ] == 'BadRequestError: Bad request.'
330
372
331
373
332
374
def test_can_raise_not_found (smoke_test_app ):
333
- response = requests . get ( smoke_test_app .url + '/notfound' )
375
+ response = smoke_test_app .get_response ( '/notfound' )
334
376
assert response .status_code == 404
335
377
assert response .json ()['Code' ] == 'NotFoundError'
336
378
337
379
338
380
def test_unexpected_error_raises_500_in_prod_mode (smoke_test_app ):
381
+ # Can't use smoke_test_app.get_response() because we're explicitly
382
+ # testing for a 500.
339
383
response = requests .get (smoke_test_app .url + '/arbitrary-error' )
340
384
assert response .status_code == 500
341
385
assert response .json ()['Code' ] == 'InternalServerError'
342
386
assert 'internal server error' in response .json ()['Message' ]
343
387
344
388
345
389
def test_can_route_multiple_methods_in_one_view (smoke_test_app ):
346
- response = requests . get ( smoke_test_app .url + '/multimethod' )
390
+ response = smoke_test_app .get_response ( '/multimethod' )
347
391
response .raise_for_status ()
348
392
assert response .json ()['method' ] == 'GET'
349
393
350
- response = requests . post ( smoke_test_app .url + '/multimethod' )
394
+ response = smoke_test_app .post_response ( '/multimethod' )
351
395
response .raise_for_status ()
352
396
assert response .json ()['method' ] == 'POST'
353
397
354
398
355
399
def test_form_encoded_content_type (smoke_test_app ):
356
- response = requests . post ( smoke_test_app .url + '/formencoded' ,
357
- data = {'foo' : 'bar' })
400
+ response = smoke_test_app .post_response ( '/formencoded' ,
401
+ data = {'foo' : 'bar' })
358
402
response .raise_for_status ()
359
403
assert response .json () == {'parsed' : {'foo' : ['bar' ]}}
360
404
@@ -363,34 +407,31 @@ def test_can_round_trip_binary(smoke_test_app):
363
407
# xde xed xbe xef will fail unicode decoding because xbe is an invalid
364
408
# start byte in utf-8.
365
409
bin_data = b'\xDE \xAD \xBE \xEF '
366
- response = requests .post (smoke_test_app .url + '/binary' ,
367
- headers = {
368
- 'Content-Type' : 'application/octet-stream' ,
369
- 'Accept' : 'application/octet-stream' ,
370
- },
371
- data = bin_data )
410
+ response = smoke_test_app .post_response (
411
+ '/binary' ,
412
+ headers = {'Content-Type' : 'application/octet-stream' ,
413
+ 'Accept' : 'application/octet-stream' },
414
+ data = bin_data )
372
415
response .raise_for_status ()
373
416
assert response .content == bin_data
374
417
375
418
376
419
def test_can_round_trip_binary_custom_content_type (smoke_test_app ):
377
420
bin_data = b'\xDE \xAD \xBE \xEF '
378
- response = requests . post ( smoke_test_app .url + '/custom-binary' ,
379
- headers = {
380
- 'Content-Type' : 'application/binary' ,
381
- 'Accept' : 'application/binary' ,
382
- },
383
- data = bin_data )
421
+ response = smoke_test_app .post_response (
422
+ '/custom-binary' ,
423
+ headers = { 'Content-Type' : 'application/binary' ,
424
+ 'Accept' : 'application/binary' } ,
425
+ data = bin_data
426
+ )
384
427
assert response .content == bin_data
385
428
386
429
387
430
def test_can_return_default_binary_data_to_a_browser (smoke_test_app ):
388
431
base64encoded_response = b'3q2+7w=='
389
432
accept = 'text/html,application/xhtml+xml;q=0.9,image/webp,*/*;q=0.8'
390
- response = requests .get (smoke_test_app .url + '/get-binary' ,
391
- headers = {
392
- 'Accept' : accept ,
393
- })
433
+ response = smoke_test_app .get_response (
434
+ '/get-binary' , headers = {'Accept' : accept })
394
435
response .raise_for_status ()
395
436
assert response .content == base64encoded_response
396
437
@@ -403,12 +444,12 @@ def _assert_contains_access_control_allow_methods(headers, methods):
403
444
404
445
405
446
def test_can_support_cors (smoke_test_app ):
406
- response = requests . get ( smoke_test_app .url + '/cors' )
447
+ response = smoke_test_app .get_response ( '/cors' )
407
448
response .raise_for_status ()
408
449
assert response .headers ['Access-Control-Allow-Origin' ] == '*'
409
450
410
451
# Should also have injected an OPTIONs request.
411
- response = requests . options ( smoke_test_app .url + '/cors' )
452
+ response = smoke_test_app .options_response ( '/cors' )
412
453
response .raise_for_status ()
413
454
headers = response .headers
414
455
assert headers ['Access-Control-Allow-Origin' ] == '*'
@@ -420,14 +461,14 @@ def test_can_support_cors(smoke_test_app):
420
461
421
462
422
463
def test_can_support_custom_cors (smoke_test_app ):
423
- response = requests . get ( smoke_test_app .url + '/custom_cors' )
464
+ response = smoke_test_app .get_response ( '/custom_cors' )
424
465
response .raise_for_status ()
425
466
expected_allow_origin = 'https://foo.example.com'
426
467
assert response .headers [
427
468
'Access-Control-Allow-Origin' ] == expected_allow_origin
428
469
429
470
# Should also have injected an OPTIONs request.
430
- response = requests . options ( smoke_test_app .url + '/custom_cors' )
471
+ response = smoke_test_app .options_response ( '/custom_cors' )
431
472
response .raise_for_status ()
432
473
headers = response .headers
433
474
assert headers ['Access-Control-Allow-Origin' ] == expected_allow_origin
@@ -451,8 +492,7 @@ def test_multifile_support(smoke_test_app):
451
492
452
493
453
494
def test_custom_response (smoke_test_app ):
454
- url = smoke_test_app .url + '/custom-response'
455
- response = requests .get (url )
495
+ response = smoke_test_app .get_response ('/custom-response' )
456
496
response .raise_for_status ()
457
497
# Custom header
458
498
assert response .headers ['Content-Type' ] == 'text/plain'
@@ -463,28 +503,30 @@ def test_custom_response(smoke_test_app):
463
503
464
504
465
505
def test_api_key_required_fails_with_no_key (smoke_test_app ):
466
- url = smoke_test_app .url + '/api-key-required'
467
- response = requests .get (url )
506
+ response = smoke_test_app .get_response ('/api-key-required' )
468
507
# Request should fail because we're not providing
469
508
# an API key.
470
509
assert response .status_code == 403
471
510
472
511
473
512
def test_can_handle_charset (smoke_test_app ):
474
- url = smoke_test_app .url + '/json-only'
475
513
# Should pass content type validation even with charset specified.
476
- response = requests .get (
477
- url , headers = {'Content-Type' : 'application/json; charset=utf-8' })
514
+ response = smoke_test_app .get_response (
515
+ '/json-only' ,
516
+ headers = {'Content-Type' : 'application/json; charset=utf-8' }
517
+ )
478
518
assert response .status_code == 200
479
519
480
520
481
521
def test_can_use_builtin_custom_auth (smoke_test_app ):
482
- url = smoke_test_app . url + '/builtin-auth'
522
+ url = '/builtin-auth'
483
523
# First time without an Auth header, we should fail.
484
- response = requests . get (url )
524
+ response = smoke_test_app . get_response (url )
485
525
assert response .status_code == 401
486
526
# Now with the proper auth header, things should work.
487
- response = requests .get (url , headers = {'Authorization' : 'yes' })
527
+ response = smoke_test_app .get_response (
528
+ url , headers = {'Authorization' : 'yes' }
529
+ )
488
530
assert response .status_code == 200
489
531
context = response .json ()['context' ]
490
532
assert 'authorizer' in context
@@ -494,24 +536,23 @@ def test_can_use_builtin_custom_auth(smoke_test_app):
494
536
495
537
496
538
def test_can_use_shared_auth (smoke_test_app ):
497
- url = smoke_test_app .url + '/fake-profile'
498
- response = requests .get (url )
539
+ response = smoke_test_app .get_response ('/fake-profile' )
499
540
# GETs are allowed
500
541
assert response .status_code == 200
501
542
# However, POSTs require auth.
502
543
# This has the same auth config as /builtin-auth,
503
544
# so we're testing the auth handler can be shared.
504
- assert requests .post (url ).status_code == 401
505
- response = requests .post (url , headers = {'Authorization' : 'yes' })
545
+ assert smoke_test_app .post_response ('/fake-profile' ).status_code == 401
546
+ response = smoke_test_app .post_response ('/fake-profile' ,
547
+ headers = {'Authorization' : 'yes' })
506
548
assert response .status_code == 200
507
549
context = response .json ()['context' ]
508
550
assert 'authorizer' in context
509
551
assert context ['authorizer' ]['foo' ] == 'bar'
510
552
511
553
512
554
def test_empty_raw_body (smoke_test_app ):
513
- url = smoke_test_app .url + '/repr-raw-body'
514
- response = requests .post (url )
555
+ response = smoke_test_app .post_response ('/repr-raw-body' )
515
556
response .raise_for_status ()
516
557
assert response .json () == {'repr-raw-body' : '' }
517
558
@@ -556,18 +597,16 @@ def test_redeploy_new_function(smoke_test_app):
556
597
@pytest .mark .on_redeploy
557
598
def test_redeploy_change_route_info (smoke_test_app ):
558
599
smoke_test_app .redeploy_once ()
559
- url = smoke_test_app .url + '/multimethod'
560
600
# POST is no longer allowed:
561
- assert requests . post ( url ).status_code == 403
601
+ assert smoke_test_app . post_response ( '/multimethod' ).status_code == 403
562
602
# But PUT is now allowed in the redeployed app.py
563
- assert requests . put ( url ).status_code == 200
603
+ assert smoke_test_app . put_response ( '/multimethod' ).status_code == 200
564
604
565
605
566
606
@pytest .mark .on_redeploy
567
607
def test_redeploy_view_deleted (smoke_test_app ):
568
608
smoke_test_app .redeploy_once ()
569
- url = smoke_test_app .url + '/path/foo'
570
- response = requests .get (url )
609
+ response = smoke_test_app .get_response ('/path/foo' )
571
610
# Request should fail because it's not in the redeployed
572
611
# app.py
573
612
assert response .status_code == 403
0 commit comments