def _test_api_gateway_lambda_proxy_integration_any_method( self, fn_name, path): self.create_lambda_function(fn_name) # create API Gateway and connect it to the Lambda proxy backend lambda_uri = aws_stack.lambda_function_arn(fn_name) target_uri = aws_stack.apigateway_invocations_arn(lambda_uri) result = testutil.connect_api_gateway_to_http_with_lambda_proxy( 'test_gateway3', target_uri, methods=['ANY'], path=path, stage_name=self.TEST_STAGE_NAME) # make test request to gateway and check response path = path.replace('{test_param1}', 'foo1') url = gateway_request_url(api_id=result['id'], stage_name=self.TEST_STAGE_NAME, path=path) data = {} for method in ('GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'): body = json.dumps(data) if method in ('POST', 'PUT', 'PATCH') else None result = getattr(requests, method.lower())(url, data=body) if method != 'DELETE': self.assertEqual(result.status_code, 200) parsed_body = json.loads(to_str(result.content)) self.assertEqual(parsed_body.get('httpMethod'), method) else: self.assertEqual(result.status_code, 204)
def test_put_integration_dynamodb_proxy_validation_with_response_template( self): response_templates = { 'application/json': json.dumps({ 'TableName': 'MusicCollection', 'Item': { 'id': '$.Id', 'data': '$.data' } }) } api_id = self.create_api_gateway_and_deploy(response_templates) url = gateway_request_url(api_id=api_id, stage_name='staging', path='/') response = requests.put( url, json.dumps({ 'id': 'id1', 'data': 'foobar123' }), ) self.assertEqual(response.status_code, 200) dynamo_client = aws_stack.connect_to_resource('dynamodb') table = dynamo_client.Table('MusicCollection') result = table.get_item(Key={'id': 'id1'}) self.assertEqual(result['Item']['data'], 'foobar123')
def test_api_gateway_kinesis_integration(self): # create target Kinesis stream stream = aws_stack.create_kinesis_stream(self.TEST_STREAM_KINESIS_API_GW) stream.wait_for() # create API Gateway and connect it to the target stream result = self.connect_api_gateway_to_kinesis('test_gateway1', self.TEST_STREAM_KINESIS_API_GW) # generate test data test_data = {'records': [ {'data': '{"foo": "bar1"}'}, {'data': '{"foo": "bar2"}'}, {'data': '{"foo": "bar3"}'} ]} url = gateway_request_url( api_id=result['id'], stage_name=self.TEST_STAGE_NAME, path=self.API_PATH_DATA_INBOUND) # list Kinesis streams via API Gateway result = requests.get(url) result = json.loads(to_str(result.content)) self.assertIn('StreamNames', result) # post test data to Kinesis via API Gateway result = requests.post(url, data=json.dumps(test_data)) result = json.loads(to_str(result.content)) self.assertEqual(result['FailedRecordCount'], 0) self.assertEqual(len(result['Records']), len(test_data['records'])) # clean up kinesis = aws_stack.connect_to_service('kinesis') kinesis.delete_stream(StreamName=self.TEST_STREAM_KINESIS_API_GW)
def test_api_gateway_s3_get_integration(self): apigw_client = aws_stack.connect_to_service('apigateway') s3_client = aws_stack.connect_to_service('s3') bucket_name = 'test-bucket' object_name = 'test.json' object_content = '{ "success": "true" }' object_content_type = 'application/json' api = apigw_client.create_rest_api(name='test') api_id = api['id'] s3_client.create_bucket(Bucket=bucket_name) s3_client.put_object(Bucket=bucket_name, Key=object_name, Body=object_content, ContentType=object_content_type) self.connect_api_gateway_to_s3(bucket_name, object_name, api_id, 'GET') apigw_client.create_deployment(restApiId=api_id, stageName='test') url = gateway_request_url(api_id, 'test', '/') result = requests.get(url) self.assertEqual(result.status_code, 200) self.assertEqual(result.text, object_content) self.assertEqual(result.headers['content-type'], object_content_type) # clean up apigw_client.delete_rest_api(restApiId=api_id) s3_client.delete_object(Bucket=bucket_name, Key=object_name) s3_client.delete_bucket(Bucket=bucket_name)
def run_api_gateway_http_integration(self, int_type): test_port = get_free_tcp_port() backend_url = 'http://localhost:%s%s' % (test_port, self.API_PATH_HTTP_BACKEND) # start test HTTP backend proxy = self.start_http_backend(test_port) # create API Gateway and connect it to the HTTP_PROXY/HTTP backend result = self.connect_api_gateway_to_http( int_type, 'test_gateway2', backend_url, path=self.API_PATH_HTTP_BACKEND) url = gateway_request_url(api_id=result['id'], stage_name=self.TEST_STAGE_NAME, path=self.API_PATH_HTTP_BACKEND) # make sure CORS headers are present origin = 'localhost' result = requests.options(url, headers={'origin': origin}) self.assertEqual(result.status_code, 200) self.assertTrue( re.match( result.headers['Access-Control-Allow-Origin'].replace( '*', '.*'), origin)) self.assertIn('POST', result.headers['Access-Control-Allow-Methods']) custom_result = json.dumps({'foo': 'bar'}) # make test GET request to gateway result = requests.get(url) self.assertEqual(result.status_code, 200) expected = custom_result if int_type == 'custom' else '{}' self.assertEqual(json.loads(to_str(result.content))['data'], expected) # make test POST request to gateway data = json.dumps({'data': 123}) result = requests.post(url, data=data) self.assertEqual(result.status_code, 200) expected = custom_result if int_type == 'custom' else data self.assertEqual(json.loads(to_str(result.content))['data'], expected) # make test POST request with non-JSON content type data = 'test=123' ctype = 'application/x-www-form-urlencoded' result = requests.post(url, data=data, headers={'content-type': ctype}) self.assertEqual(result.status_code, 200) content = json.loads(to_str(result.content)) headers = CaseInsensitiveDict(content['headers']) expected = custom_result if int_type == 'custom' else data self.assertEqual(content['data'], expected) self.assertEqual(headers['content-type'], ctype) # clean up proxy.stop()
def test_put_integration_dynamodb_proxy_validation_without_response_template(self): api_id = self.create_api_gateway_and_deploy({}) url = gateway_request_url(api_id=api_id, stage_name='staging', path='/') response = requests.put( url, json.dumps({'id': 'id1', 'data': 'foobar123'}), ) self.assertEqual(response.status_code, 404)
def _test_api_gateway_lambda_proxy_integration(self, fn_name, path): self.create_lambda_function(fn_name) # create API Gateway and connect it to the Lambda proxy backend lambda_uri = aws_stack.lambda_function_arn(fn_name) invocation_uri = 'arn:aws:apigateway:%s:lambda:path/2015-03-31/functions/%s/invocations' target_uri = invocation_uri % (aws_stack.get_region(), lambda_uri) result = testutil.connect_api_gateway_to_http_with_lambda_proxy( 'test_gateway2', target_uri, path=path, stage_name=self.TEST_STAGE_NAME) api_id = result['id'] path_map = get_rest_api_paths(api_id) _, resource = get_resource_for_path('/lambda/foo1', path_map) # make test request to gateway and check response path = path.replace('{test_param1}', 'foo1') path = path + '?foo=foo&bar=bar&bar=baz' url = gateway_request_url(api_id=api_id, stage_name=self.TEST_STAGE_NAME, path=path) data = {'return_status_code': 203, 'return_headers': {'foo': 'bar123'}} result = requests.post(url, data=json.dumps(data), headers={'User-Agent': 'python-requests/testing'}) self.assertEqual(result.status_code, 203) self.assertEqual(result.headers.get('foo'), 'bar123') self.assertIn('set-cookie', result.headers) parsed_body = json.loads(to_str(result.content)) self.assertEqual(parsed_body.get('return_status_code'), 203) self.assertDictEqual(parsed_body.get('return_headers'), {'foo': 'bar123'}) self.assertDictEqual(parsed_body.get('queryStringParameters'), {'foo': 'foo', 'bar': ['bar', 'baz']}) request_context = parsed_body.get('requestContext') source_ip = request_context['identity'].pop('sourceIp') self.assertTrue(re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', source_ip)) self.assertEqual(request_context['path'], '/' + self.TEST_STAGE_NAME + '/lambda/foo1') self.assertEqual(request_context.get('stageVariables'), None) self.assertEqual(request_context['accountId'], TEST_AWS_ACCOUNT_ID) self.assertEqual(request_context['resourceId'], resource.get('id')) self.assertEqual(request_context['stage'], self.TEST_STAGE_NAME) self.assertEqual(request_context['identity']['userAgent'], 'python-requests/testing') self.assertEqual(request_context['httpMethod'], 'POST') self.assertEqual(request_context['protocol'], 'HTTP/1.1') self.assertIn('requestTimeEpoch', request_context) self.assertIn('requestTime', request_context) result = requests.delete(url, data=json.dumps(data)) self.assertEqual(result.status_code, 204) # send message with non-ASCII chars body_msg = '🙀 - 参よ' result = requests.post(url, data=json.dumps({'return_raw_body': body_msg})) self.assertEqual(to_str(result.content), body_msg)
def test_multiple_api_keys_validate(self): response_templates = {'application/json': json.dumps({'TableName': 'MusicCollection', 'Item': {'id': '$.Id', 'data': '$.data'}})} api_id = self.create_api_gateway_and_deploy(response_templates, True) url = gateway_request_url(api_id=api_id, stage_name='staging', path='/') client = aws_stack.connect_to_service('apigateway') # Create multiple usage plans usage_plan_ids = [] for i in range(2): payload = { 'name': 'APIKEYTEST-PLAN-{}'.format(i), 'description': 'Description', 'quota': {'limit': 10, 'period': 'DAY', 'offset': 0}, 'throttle': {'rateLimit': 2, 'burstLimit': 1}, 'apiStages': [{'apiId': api_id, 'stage': 'staging'}], 'tags': {'tag_key': 'tag_value'}, } usage_plan_ids.append(client.create_usage_plan(**payload)['id']) api_keys = [] key_type = 'API_KEY' # Create multiple API Keys in each usage plan for usage_plan_id in usage_plan_ids: for i in range(2): api_key = client.create_api_key(name='testMultipleApiKeys{}'.format(i)) payload = {'usagePlanId': usage_plan_id, 'keyId': api_key['id'], 'keyType': key_type} client.create_usage_plan_key(**payload) api_keys.append(api_key['value']) response = requests.put( url, json.dumps({'id': 'id1', 'data': 'foobar123'}), ) # when the api key is not passed as part of the header self.assertEqual(response.status_code, 403) # Check All API Keys work for key in api_keys: response = requests.put( url, json.dumps({'id': 'id1', 'data': 'foobar123'}), headers={'X-API-Key': key} ) # when the api key is passed as part of the header self.assertEqual(response.status_code, 200)
def test_http_invocation_with_apigw_proxy(self): lambda_name = "test_lambda_%s" % short_uid() lambda_resource = "/api/v1/{proxy+}" lambda_path = "/api/v1/hello/world" lambda_request_context_path = "/" + TEST_STAGE_NAME + lambda_path lambda_request_context_resource_path = lambda_resource # create lambda function testutil.create_lambda_function( handler_file=TEST_LAMBDA_PYTHON, libs=TEST_LAMBDA_LIBS, func_name=lambda_name, ) # create API Gateway and connect it to the Lambda proxy backend lambda_uri = aws_stack.lambda_function_arn(lambda_name) invocation_uri = "arn:aws:apigateway:%s:lambda:path/2015-03-31/functions/%s/invocations" target_uri = invocation_uri % (aws_stack.get_region(), lambda_uri) result = testutil.connect_api_gateway_to_http_with_lambda_proxy( "test_gateway2", target_uri, path=lambda_resource, stage_name=TEST_STAGE_NAME, ) api_id = result["id"] url = gateway_request_url(api_id=api_id, stage_name=TEST_STAGE_NAME, path=lambda_path) result = safe_requests.post( url, data=b"{}", headers={"User-Agent": "python-requests/testing"}) content = json.loads(result.content) self.assertEqual(lambda_path, content["path"]) self.assertEqual(lambda_resource, content["resource"]) self.assertEqual(lambda_request_context_path, content["requestContext"]["path"]) self.assertEqual( lambda_request_context_resource_path, content["requestContext"]["resourcePath"], ) # clean up testutil.delete_lambda_function(lambda_name)
def test_api_gateway_sqs_integration_with_event_source(self): # create target SQS stream queue_name = 'queue-%s' % short_uid() queue_url = aws_stack.create_sqs_queue(queue_name)['QueueUrl'] # create API Gateway and connect it to the target queue result = connect_api_gateway_to_sqs('test_gateway4', stage_name=self.TEST_STAGE_NAME, queue_arn=queue_name, path=self.API_PATH_DATA_INBOUND) # create event source for sqs lambda processor self.create_lambda_function(self.TEST_LAMBDA_SQS_HANDLER_NAME) event_source_data = { 'FunctionName': self.TEST_LAMBDA_SQS_HANDLER_NAME, 'EventSourceArn': aws_stack.sqs_queue_arn(queue_name), 'Enabled': True } add_event_source(event_source_data) # generate test data test_data = {'spam': 'eggs & beans'} url = gateway_request_url(api_id=result['id'], stage_name=self.TEST_STAGE_NAME, path=self.API_PATH_DATA_INBOUND) result = requests.post(url, data=json.dumps(test_data)) self.assertEqual(result.status_code, 200) parsed_json = xmltodict.parse(result.content) result = parsed_json['SendMessageResponse']['SendMessageResult'] body_md5 = result['MD5OfMessageBody'] self.assertEqual(body_md5, 'b639f52308afd65866c86f274c59033f') # clean up sqs_client = aws_stack.connect_to_service('sqs') sqs_client.delete_queue(QueueUrl=queue_url) lambda_client = aws_stack.connect_to_service('lambda') lambda_client.delete_function( FunctionName=self.TEST_LAMBDA_SQS_HANDLER_NAME)
def test_api_gateway_sqs_integration(self): # create target SQS stream queue_name = 'queue-%s' % short_uid() aws_stack.create_sqs_queue(queue_name) # create API Gateway and connect it to the target queue result = connect_api_gateway_to_sqs('test_gateway4', stage_name=self.TEST_STAGE_NAME, queue_arn=queue_name, path=self.API_PATH_DATA_INBOUND) # generate test data test_data = {'spam': 'eggs'} url = gateway_request_url( api_id=result['id'], stage_name=self.TEST_STAGE_NAME, path=self.API_PATH_DATA_INBOUND) result = requests.post(url, data=json.dumps(test_data)) self.assertEqual(result.status_code, 200) messages = aws_stack.sqs_receive_message(queue_name)['Messages'] self.assertEqual(len(messages), 1) self.assertEqual(json.loads(base64.b64decode(messages[0]['Body'])), test_data)
def test_api_key_required_for_methods(self): response_templates = {'application/json': json.dumps({'TableName': 'MusicCollection', 'Item': {'id': '$.Id', 'data': '$.data'}})} api_id = self.create_api_gateway_and_deploy(response_templates, True) url = gateway_request_url(api_id=api_id, stage_name='staging', path='/') payload = { 'name': 'TEST-PLAN-2', 'description': 'Description', 'quota': {'limit': 10, 'period': 'DAY', 'offset': 0}, 'throttle': {'rateLimit': 2, 'burstLimit': 1}, 'apiStages': [{'apiId': api_id, 'stage': 'staging'}], 'tags': {'tag_key': 'tag_value'}, } client = aws_stack.connect_to_service('apigateway') usage_plan_id = client.create_usage_plan(**payload)['id'] key_name = 'testApiKey' key_type = 'API_KEY' api_key = client.create_api_key(name=key_name) payload = {'usagePlanId': usage_plan_id, 'keyId': api_key['id'], 'keyType': key_type} client.create_usage_plan_key(**payload) response = requests.put( url, json.dumps({'id': 'id1', 'data': 'foobar123'}), ) # when the api key is not passed as part of the header self.assertEqual(response.status_code, 403) response = requests.put( url, json.dumps({'id': 'id1', 'data': 'foobar123'}), headers={'X-API-Key': api_key['value']} ) # when the api key is passed as part of the header self.assertEqual(response.status_code, 200)
def test_step_function_integrations(self): client = aws_stack.connect_to_service('apigateway') sfn_client = aws_stack.connect_to_service('stepfunctions') lambda_client = aws_stack.connect_to_service('lambda') state_machine_name = 'test' state_machine_def = { 'Comment': 'Hello World example', 'StartAt': 'step1', 'States': { 'step1': { 'Type': 'Task', 'Resource': '__tbd__', 'End': True }, } } fn_name = 'test-stepfunctions-apigw' # create state machine testutil.create_lambda_function(handler_file=TEST_LAMBDA_ECHO_FILE, func_name=fn_name, runtime=LAMBDA_RUNTIME_PYTHON36) resp = lambda_client.list_functions() role_arn = aws_stack.role_arn('sfn_role') definition = clone(state_machine_def) lambda_arn_1 = aws_stack.lambda_function_arn(fn_name) definition['States']['step1']['Resource'] = lambda_arn_1 definition = json.dumps(definition) sm_arn = 'arn:aws:states:%s:%s:stateMachine:%s' \ % (aws_stack.get_region(), TEST_AWS_ACCOUNT_ID, state_machine_name) sfn_client.create_state_machine(name=state_machine_name, definition=definition, roleArn=role_arn) rest_api = client.create_rest_api(name='test', description='test') resources = client.get_resources(restApiId=rest_api['id']) client.put_method(restApiId=rest_api['id'], resourceId=resources['items'][0]['id'], httpMethod='POST', authorizationType='NONE') client.put_integration( restApiId=rest_api['id'], resourceId=resources['items'][0]['id'], httpMethod='POST', integrationHttpMethod='POST', type='AWS', uri='arn:aws:apigateway:%s:states:action/StartExecution' % aws_stack.get_region(), requestTemplates={ 'application/json': """ #set($data = $util.escapeJavaScript($input.json('$'))) {"input": "$data","stateMachineArn": "%s"} """ % sm_arn }, ) client.create_deployment(restApiId=rest_api['id'], stageName='dev') url = gateway_request_url(api_id=rest_api['id'], stage_name='dev', path='/') test_data = {'test': 'test-value'} resp = requests.post(url, data=json.dumps(test_data)) self.assertEqual(resp.status_code, 200) self.assertIn('executionArn', resp.content.decode()) self.assertIn('startDate', resp.content.decode()) client.delete_integration( restApiId=rest_api['id'], resourceId=resources['items'][0]['id'], httpMethod='POST', ) client.put_integration( restApiId=rest_api['id'], resourceId=resources['items'][0]['id'], httpMethod='POST', integrationHttpMethod='POST', type='AWS', uri='arn:aws:apigateway:%s:states:action/StartExecution' % aws_stack.get_region(), ) test_data = { 'input': json.dumps({'test': 'test-value'}), 'name': 'MyExecution', 'stateMachineArn': '{}'.format(sm_arn) } resp = requests.post(url, data=json.dumps(test_data)) self.assertEqual(resp.status_code, 200) self.assertIn('executionArn', resp.content.decode()) self.assertIn('startDate', resp.content.decode()) # Clean up lambda_client.delete_function(FunctionName=fn_name) sfn_client.delete_state_machine(stateMachineArn=sm_arn) client.delete_rest_api(restApiId=rest_api['id'])
def test_apigateway_with_lambda_integration(self): apigw_client = aws_stack.connect_to_service('apigateway') # create Lambda function lambda_name = 'apigw-lambda-%s' % short_uid() self.create_lambda_function(lambda_name) lambda_uri = aws_stack.lambda_function_arn(lambda_name) target_uri = aws_stack.apigateway_invocations_arn(lambda_uri) # create REST API api = apigw_client.create_rest_api(name='test-api', description='') api_id = api['id'] root_res_id = apigw_client.get_resources( restApiId=api_id)['items'][0]['id'] api_resource = apigw_client.create_resource(restApiId=api_id, parentId=root_res_id, pathPart='test') apigw_client.put_method(restApiId=api_id, resourceId=api_resource['id'], httpMethod='GET', authorizationType='NONE') rs = apigw_client.put_integration( restApiId=api_id, resourceId=api_resource['id'], httpMethod='GET', integrationHttpMethod='POST', type='AWS', uri=target_uri, timeoutInMillis=3000, contentHandling='CONVERT_TO_BINARY', requestTemplates={ 'application/json': '{"param1": "$input.params(\'param1\')"}' }) integration_keys = [ 'httpMethod', 'type', 'passthroughBehavior', 'cacheKeyParameters', 'uri', 'cacheNamespace', 'timeoutInMillis', 'contentHandling', 'requestParameters' ] self.assertEqual(rs['ResponseMetadata']['HTTPStatusCode'], 200) for key in integration_keys: self.assertIn(key, rs) self.assertNotIn('responseTemplates', rs) apigw_client.create_deployment(restApiId=api_id, stageName=self.TEST_STAGE_NAME) rs = apigw_client.get_integration(restApiId=api_id, resourceId=api_resource['id'], httpMethod='GET') self.assertEqual(rs['ResponseMetadata']['HTTPStatusCode'], 200) self.assertEqual(rs['type'], 'AWS') self.assertEqual(rs['httpMethod'], 'POST') self.assertEqual(rs['uri'], target_uri) # invoke the gateway endpoint url = gateway_request_url(api_id=api_id, stage_name=self.TEST_STAGE_NAME, path='/test') response = requests.get('%s?param1=foobar' % url) self.assertLess(response.status_code, 400) content = json.loads(to_str(response.content)) self.assertEqual(content.get('httpMethod'), 'GET') self.assertEqual( content.get('requestContext', {}).get('resourceId'), api_resource['id']) self.assertEqual( content.get('requestContext', {}).get('stage'), self.TEST_STAGE_NAME) self.assertEqual(content.get('body'), '{"param1": "foobar"}') # delete integration rs = apigw_client.delete_integration( restApiId=api_id, resourceId=api_resource['id'], httpMethod='GET', ) self.assertEqual(rs['ResponseMetadata']['HTTPStatusCode'], 200) with self.assertRaises(ClientError) as ctx: # This call should not be successful as the integration is deleted apigw_client.get_integration(restApiId=api_id, resourceId=api_resource['id'], httpMethod='GET') self.assertEqual(ctx.exception.response['Error']['Code'], 'BadRequestException') # clean up lambda_client = aws_stack.connect_to_service('lambda') lambda_client.delete_function(FunctionName=lambda_name) apigw_client.delete_rest_api(restApiId=api_id)