Skip to content

Commit e2eac21

Browse files
committed
Merge branch 'fix-integ-ws'
PR #1372. * fix-integ-ws: Retry any HTTP requests if we see 5xx errors Retry get_json failures Make ws integ test more robust
2 parents b79470d + d044747 commit e2eac21

File tree

3 files changed

+143
-75
lines changed

3 files changed

+143
-75
lines changed

tests/aws/test_features.py

Lines changed: 104 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ def _wrapped_with_retry(*args, **kwargs):
3636
return _create_wrapped_retry_function
3737

3838

39+
class InternalServerError(Exception):
40+
pass
41+
42+
3943
class SmokeTestApplication(object):
4044

4145
# Number of seconds to wait after redeploy before starting
@@ -87,13 +91,59 @@ def websocket_connect_url(self):
8791
)
8892
)
8993

94+
@retry(max_attempts=10, delay=5)
9095
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):
91102
if not url.startswith('/'):
92103
url = '/' + url
93104
response = requests.get(self.url + url)
94105
response.raise_for_status()
95106
return response.json()
96107

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+
97147
def redeploy_once(self):
98148
# Redeploy the application once. If a redeploy
99149
# has already happened, this function is a noop.
@@ -111,16 +161,12 @@ def redeploy_once(self):
111161
self._wait_for_stablize()
112162
time.sleep(self._POLLING_DELAY)
113163

114-
@retry(max_attempts=10, delay=5)
115164
def _wait_for_stablize(self):
116165
# After a deployment we sometimes need to wait for
117166
# API Gateway to propagate all of its changes.
118167
# We're going to give it num_attempts to give us a
119168
# 200 response before failing.
120-
try:
121-
return self.get_json('/')
122-
except requests.exceptions.HTTPError:
123-
pass
169+
return self.get_json('/')
124170

125171

126172
@pytest.fixture
@@ -284,77 +330,75 @@ def _get_resource_id(apig_client, rest_api_id, path):
284330

285331

286332
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')
289334
response.raise_for_status()
290335
assert response.json() == {'success': True}
291336
with pytest.raises(requests.HTTPError):
292337
# Only POST is supported.
293-
response = requests.get(app_url + '/post')
338+
response = smoke_test_app.get_response('/post')
294339
response.raise_for_status()
295340

296341

297342
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')
300344
response.raise_for_status()
301345
assert response.json() == {'success': True}
302346
with pytest.raises(requests.HTTPError):
303347
# Only PUT is supported.
304-
response = requests.get(app_url + '/put')
348+
response = smoke_test_app.get_response('/put')
305349
response.raise_for_status()
306350

307351

308352
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')
313356
assert response.json() == {'method': 'POST'}
314357

315358

316359
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'}),
320362
headers={'Content-Type': 'application/json'})
321363
response.raise_for_status()
322364
assert response.json() == {'json_body': {'hello': 'world'}}
323365

324366

325367
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')
327369
assert response.status_code == 400
328370
assert response.json()['Code'] == 'BadRequestError'
329371
assert response.json()['Message'] == 'BadRequestError: Bad request.'
330372

331373

332374
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')
334376
assert response.status_code == 404
335377
assert response.json()['Code'] == 'NotFoundError'
336378

337379

338380
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.
339383
response = requests.get(smoke_test_app.url + '/arbitrary-error')
340384
assert response.status_code == 500
341385
assert response.json()['Code'] == 'InternalServerError'
342386
assert 'internal server error' in response.json()['Message']
343387

344388

345389
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')
347391
response.raise_for_status()
348392
assert response.json()['method'] == 'GET'
349393

350-
response = requests.post(smoke_test_app.url + '/multimethod')
394+
response = smoke_test_app.post_response('/multimethod')
351395
response.raise_for_status()
352396
assert response.json()['method'] == 'POST'
353397

354398

355399
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'})
358402
response.raise_for_status()
359403
assert response.json() == {'parsed': {'foo': ['bar']}}
360404

@@ -363,34 +407,31 @@ def test_can_round_trip_binary(smoke_test_app):
363407
# xde xed xbe xef will fail unicode decoding because xbe is an invalid
364408
# start byte in utf-8.
365409
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)
372415
response.raise_for_status()
373416
assert response.content == bin_data
374417

375418

376419
def test_can_round_trip_binary_custom_content_type(smoke_test_app):
377420
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+
)
384427
assert response.content == bin_data
385428

386429

387430
def test_can_return_default_binary_data_to_a_browser(smoke_test_app):
388431
base64encoded_response = b'3q2+7w=='
389432
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})
394435
response.raise_for_status()
395436
assert response.content == base64encoded_response
396437

@@ -403,12 +444,12 @@ def _assert_contains_access_control_allow_methods(headers, methods):
403444

404445

405446
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')
407448
response.raise_for_status()
408449
assert response.headers['Access-Control-Allow-Origin'] == '*'
409450

410451
# Should also have injected an OPTIONs request.
411-
response = requests.options(smoke_test_app.url + '/cors')
452+
response = smoke_test_app.options_response('/cors')
412453
response.raise_for_status()
413454
headers = response.headers
414455
assert headers['Access-Control-Allow-Origin'] == '*'
@@ -420,14 +461,14 @@ def test_can_support_cors(smoke_test_app):
420461

421462

422463
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')
424465
response.raise_for_status()
425466
expected_allow_origin = 'https://foo.example.com'
426467
assert response.headers[
427468
'Access-Control-Allow-Origin'] == expected_allow_origin
428469

429470
# 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')
431472
response.raise_for_status()
432473
headers = response.headers
433474
assert headers['Access-Control-Allow-Origin'] == expected_allow_origin
@@ -451,8 +492,7 @@ def test_multifile_support(smoke_test_app):
451492

452493

453494
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')
456496
response.raise_for_status()
457497
# Custom header
458498
assert response.headers['Content-Type'] == 'text/plain'
@@ -463,28 +503,30 @@ def test_custom_response(smoke_test_app):
463503

464504

465505
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')
468507
# Request should fail because we're not providing
469508
# an API key.
470509
assert response.status_code == 403
471510

472511

473512
def test_can_handle_charset(smoke_test_app):
474-
url = smoke_test_app.url + '/json-only'
475513
# 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+
)
478518
assert response.status_code == 200
479519

480520

481521
def test_can_use_builtin_custom_auth(smoke_test_app):
482-
url = smoke_test_app.url + '/builtin-auth'
522+
url = '/builtin-auth'
483523
# First time without an Auth header, we should fail.
484-
response = requests.get(url)
524+
response = smoke_test_app.get_response(url)
485525
assert response.status_code == 401
486526
# 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+
)
488530
assert response.status_code == 200
489531
context = response.json()['context']
490532
assert 'authorizer' in context
@@ -494,24 +536,23 @@ def test_can_use_builtin_custom_auth(smoke_test_app):
494536

495537

496538
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')
499540
# GETs are allowed
500541
assert response.status_code == 200
501542
# However, POSTs require auth.
502543
# This has the same auth config as /builtin-auth,
503544
# 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'})
506548
assert response.status_code == 200
507549
context = response.json()['context']
508550
assert 'authorizer' in context
509551
assert context['authorizer']['foo'] == 'bar'
510552

511553

512554
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')
515556
response.raise_for_status()
516557
assert response.json() == {'repr-raw-body': ''}
517558

@@ -556,18 +597,16 @@ def test_redeploy_new_function(smoke_test_app):
556597
@pytest.mark.on_redeploy
557598
def test_redeploy_change_route_info(smoke_test_app):
558599
smoke_test_app.redeploy_once()
559-
url = smoke_test_app.url + '/multimethod'
560600
# POST is no longer allowed:
561-
assert requests.post(url).status_code == 403
601+
assert smoke_test_app.post_response('/multimethod').status_code == 403
562602
# 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
564604

565605

566606
@pytest.mark.on_redeploy
567607
def test_redeploy_view_deleted(smoke_test_app):
568608
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')
571610
# Request should fail because it's not in the redeployed
572611
# app.py
573612
assert response.status_code == 403

0 commit comments

Comments
 (0)