def test_dispatch_missing_method_returns_internal_failure(): table: DispatchTable = dict() sqs_service = load_service("sqs") skeleton = Skeleton(sqs_service, table) context = RequestContext() context.account = "test" context.region = "us-west-1" context.service = sqs_service context.request = { "method": "POST", "path": "/", "body": "Action=DeleteQueue&Version=2012-11-05&QueueUrl=http%3A%2F%2Flocalhost%3A4566%2F000000000000%2Ftf-acc-test-queue", "headers": _get_sqs_request_headers(), } result = skeleton.invoke(context) # Use the parser from botocore to parse the serialized response response_parser = create_parser(sqs_service.protocol) parsed_response = response_parser.parse( result, sqs_service.operation_model("SendMessage").output_shape) assert "Error" in parsed_response assert parsed_response["Error"] == { "Code": "InternalFailure", "Message": "API action 'DeleteQueue' for service 'sqs' not yet implemented", }
def call_moto(context: RequestContext, include_response_metadata=False) -> ServiceResponse: """ Call moto with the given request context and receive a parsed ServiceResponse. :param context: the request context :param include_response_metadata: whether to include botocore's "ResponseMetadata" attribute :return: a serialized AWS ServiceResponse (same as boto3 would return) """ status, headers, content = dispatch_to_moto(context) operation_model = context.operation response_dict = { # this is what botocore.endpoint.convert_to_response_dict normally does "headers": dict(headers.items()), # boto doesn't like werkzeug headers "status_code": status, "body": to_bytes(content), "context": { "operation_name": operation_model.name, }, } parser = create_parser(context.service.protocol) response = parser.parse(response_dict, operation_model.output_shape) if status >= 301: error = response["Error"] raise CommonServiceException( code=error.get("Code", "UnknownError"), status_code=status, message=error.get("Message", ""), ) if not include_response_metadata: response.pop("ResponseMetadata", None) return response
def _botocore_error_serializer_integration_test( service: str, action: str, exception: ServiceException, code: str, status_code: int, message: Optional[str], is_sender_fault: bool = False, ): """ Performs an integration test for the error serialization using botocore as parser. It executes the following steps: - Load the given service (f.e. "sqs") - Serialize the _error_ response with the appropriate serializer from the AWS Serivce Framework - Parse the serialized error response using the botocore parser - Checks the the metadata is correct (status code, requestID,...) - Checks if the parsed error response content is correct :param service: to load the correct service specification, serializer, and parser :param action: to load the correct service specification, serializer, and parser :param exception: which should be serialized and tested against :param code: expected "code" of the exception (i.e. the AWS specific exception ID, f.e. "CloudFrontOriginAccessIdentityAlreadyExists") :param status_code: expected HTTP response status code :param message: expected error message :return: None """ # Load the appropriate service service = load_service(service) # Use our serializer to serialize the response response_serializer = create_serializer(service) serialized_response = response_serializer.serialize_error_to_response( exception, service.operation_model(action)) # Use the parser from botocore to parse the serialized response response_parser: ResponseParser = create_parser(service.protocol) parsed_response = response_parser.parse( serialized_response.to_readonly_response_dict(), service.operation_model(action).output_shape, ) # Check if the result is equal to the initial response params assert "Error" in parsed_response assert "Code" in parsed_response["Error"] assert "Message" in parsed_response["Error"] assert parsed_response["Error"]["Code"] == code assert parsed_response["Error"]["Message"] == message assert "ResponseMetadata" in parsed_response assert "RequestId" in parsed_response["ResponseMetadata"] assert len(parsed_response["ResponseMetadata"]["RequestId"]) == 52 assert "HTTPStatusCode" in parsed_response["ResponseMetadata"] assert parsed_response["ResponseMetadata"]["HTTPStatusCode"] == status_code type = parsed_response["Error"].get("Type") if is_sender_fault: assert type == "Sender" else: assert type is None
def test_dispatch_common_service_exception(): def delete_queue(_context: RequestContext, _request: ServiceRequest): raise CommonServiceException("NonExistentQueue", "No such queue") table: DispatchTable = dict() table["DeleteQueue"] = delete_queue sqs_service = load_service("sqs") skeleton = Skeleton(sqs_service, table) context = RequestContext() context.account = "test" context.region = "us-west-1" context.service = sqs_service context.request = { "method": "POST", "path": "/", "body": "Action=DeleteQueue&Version=2012-11-05&QueueUrl=http%3A%2F%2Flocalhost%3A4566%2F000000000000%2Ftf-acc-test-queue", "headers": _get_sqs_request_headers(), } result = skeleton.invoke(context) # Use the parser from botocore to parse the serialized response response_parser = create_parser(sqs_service.protocol) parsed_response = response_parser.parse( result, sqs_service.operation_model("SendMessage").output_shape) assert "Error" in parsed_response assert parsed_response["Error"] == { "Code": "NonExistentQueue", "Message": "No such queue", }
def call_moto(context: RequestContext) -> ServiceResponse: """ Call moto with the given request context and receive a parsed ServiceResponse. :param context: the request context :return: a serialized AWS ServiceResponse (same as boto3 would return) """ status, headers, content = dispatch_to_moto(context) operation_model = context.operation response_dict = { # this is what botocore.endpoint.convert_to_response_dict normally does "headers": headers, "status_code": status, "body": to_bytes(content), "context": { "operation_name": operation_model.name, }, } parser = create_parser(context.service.protocol) response = parser.parse(response_dict, operation_model.output_shape) if status >= 301: error = response["Error"] raise CommonServiceException( code=error.get("Code", "UnknownError"), status_code=status, message=error.get("Message", ""), ) return response
def _botocore_serializer_integration_test( service: str, action: str, response: dict, status_code=200, expected_response_content: dict = None, ): """ Performs an integration test for the serializer using botocore as parser. It executes the following steps: - Load the given service (f.e. "sqs") - Serialize the response with the appropriate serializer from the AWS Serivce Framework - Parse the serialized response using the botocore parser - Checks if the metadata is correct (status code, requestID,...) - Checks if the parsed response content is equal to the input to the serializer :param service: to load the correct service specification, serializer, and parser :param action: to load the correct service specification, serializer, and parser :param response: which should be serialized and tested against :param status_code: Optional - expected status code of the response - defaults to 200 :param expected_response_content: Optional - if the input data ("response") differs from the actually expected data (because f.e. it contains None values) :return: None """ # Load the appropriate service service = load_service(service) # Use our serializer to serialize the response response_serializer = create_serializer(service) # The serializer changes the incoming dict, therefore copy it before passing it to the serializer response_to_parse = copy.deepcopy(response) serialized_response = response_serializer.serialize_to_response( response_to_parse, service.operation_model(action)) # Use the parser from botocore to parse the serialized response response_parser = create_parser(service.protocol) parsed_response = response_parser.parse( serialized_response.to_readonly_response_dict(), service.operation_model(action).output_shape, ) return_response = copy.deepcopy(parsed_response) # Check if the result is equal to the initial response params assert "ResponseMetadata" in parsed_response assert "HTTPStatusCode" in parsed_response["ResponseMetadata"] assert parsed_response["ResponseMetadata"]["HTTPStatusCode"] == status_code assert "RequestId" in parsed_response["ResponseMetadata"] assert len(parsed_response["ResponseMetadata"]["RequestId"]) == 52 del parsed_response["ResponseMetadata"] if expected_response_content is None: expected_response_content = response if expected_response_content is not _skip_assert: assert parsed_response == expected_response_content return return_response
def get_response(operation_model, http_response): protocol = operation_model.metadata["protocol"] response_dict = {"headers": http_response.headers, "status_code": http_response.status_code} # TODO: Unfortunately, we have to have error logic here. # If it looks like an error, in the streaming response case we # need to actually grab the contents. if response_dict["status_code"] >= 300: response_dict["body"] = http_response.content elif operation_model.has_streaming_output: response_dict["body"] = StreamingBody(http_response.raw, response_dict["headers"].get("content-length")) else: response_dict["body"] = http_response.content parser = parsers.create_parser(protocol) return http_response, parser.parse(response_dict, operation_model.output_shape)
def get_response(operation_model, http_response): protocol = operation_model.metadata['protocol'] response_dict = { 'headers': http_response.headers, 'status_code': http_response.status_code, } # TODO: Unfortunately, we have to have error logic here. # If it looks like an error, in the streaming response case we # need to actually grab the contents. if response_dict['status_code'] >= 300: response_dict['body'] = http_response.content elif operation_model.has_streaming_output: response_dict['body'] = StreamingBody( http_response.raw, response_dict['headers'].get('content-length')) else: response_dict['body'] = http_response.content parser = parsers.create_parser(protocol) return http_response, parser.parse(response_dict, operation_model.output_shape)
def dispatch_to_backend( context: RequestContext, http_request_dispatcher: Callable[[RequestContext], HttpBackendResponse], include_response_metadata=False, ) -> ServiceResponse: """ Dispatch the given request to a backend by using the `request_forwarder` function to fetch an HTTP response, converting it to a ServiceResponse. :param context: the request context :param http_request_dispatcher: dispatcher that performs the request and returns an HTTP response :param include_response_metadata: whether to include boto3 response metadata in the response :return: """ status, headers, content = http_request_dispatcher(context) operation_model = context.operation response_dict = { # this is what botocore.endpoint.convert_to_response_dict normally does "headers": dict(headers.items()), # boto doesn't like werkzeug headers "status_code": status, "body": to_bytes(content), "context": { "operation_name": operation_model.name, }, } parser = create_parser(context.service.protocol) response = parser.parse(response_dict, operation_model.output_shape) if status >= 301: error = response["Error"] raise CommonServiceException( code=error.get("Code", "UnknownError"), status_code=status, message=error.get("Message", ""), sender_fault=("Type" in error), ) if not include_response_metadata: response.pop("ResponseMetadata", None) return response
def test_skeleton_e2e_sqs_send_message(): sqs_service = load_service("sqs") skeleton = Skeleton(sqs_service, TestSqsApi()) context = RequestContext() context.account = "test" context.region = "us-west-1" context.service = sqs_service context.request = HttpRequest( **{ "method": "POST", "path": "/", "body": "Action=SendMessage&Version=2012-11-05&QueueUrl=http%3A%2F%2Flocalhost%3A4566%2F000000000000%2Ftf-acc-test-queue&MessageBody=%7B%22foo%22%3A+%22bared%22%7D&DelaySeconds=2", "headers": _get_sqs_request_headers(), }) result = skeleton.invoke(context) # Use the parser from botocore to parse the serialized response response_parser = create_parser(sqs_service.protocol) parsed_response = response_parser.parse( result.to_readonly_response_dict(), sqs_service.operation_model("SendMessage").output_shape) # Test the ResponseMetadata and delete it afterwards assert "ResponseMetadata" in parsed_response assert "RequestId" in parsed_response["ResponseMetadata"] assert len(parsed_response["ResponseMetadata"]["RequestId"]) == 52 assert "HTTPStatusCode" in parsed_response["ResponseMetadata"] assert parsed_response["ResponseMetadata"]["HTTPStatusCode"] == 200 del parsed_response["ResponseMetadata"] # Compare the (remaining) actual payload assert parsed_response == { "MD5OfMessageBody": "String", "MD5OfMessageAttributes": "String", "MD5OfMessageSystemAttributes": "String", "MessageId": "String", "SequenceNumber": "String", }
def test_skeleton_e2e_sqs_send_message_not_implemented(): sqs_service = load_service("sqs") skeleton = Skeleton(sqs_service, TestSqsApiNotImplemented()) context = RequestContext() context.account = "test" context.region = "us-west-1" context.service = sqs_service context.request = HttpRequest( **{ "method": "POST", "path": "/", "body": "Action=SendMessage&Version=2012-11-05&QueueUrl=http%3A%2F%2Flocalhost%3A4566%2F000000000000%2Ftf-acc-test-queue&MessageBody=%7B%22foo%22%3A+%22bared%22%7D&DelaySeconds=2", "headers": _get_sqs_request_headers(), }) result = skeleton.invoke(context) # Use the parser from botocore to parse the serialized response response_parser = create_parser(sqs_service.protocol) parsed_response = response_parser.parse( result.to_readonly_response_dict(), sqs_service.operation_model("SendMessage").output_shape) # Test the ResponseMetadata assert "ResponseMetadata" in parsed_response assert "RequestId" in parsed_response["ResponseMetadata"] assert len(parsed_response["ResponseMetadata"]["RequestId"]) == 52 assert "HTTPStatusCode" in parsed_response["ResponseMetadata"] assert parsed_response["ResponseMetadata"]["HTTPStatusCode"] == 501 # Compare the (remaining) actual eror payload assert "Error" in parsed_response assert parsed_response["Error"] == { "Code": "InternalFailure", "Message": "API action 'SendMessage' for service 'sqs' not yet implemented", }
def handle_method(fragment): if fragment["Type"] != "AWS::ApiGateway::Method": response_string = "Macro only supports \"AWS::ApiGateway::Method\", user supplied {}" raise InvalidTypeException(response_string.format(fragment["Type"])) service_name = fragment["Properties"]["Integration"].pop("Service").lower() action = fragment["Properties"]["Integration"].pop("Action") response_maps = fragment["Properties"]["Integration"].pop("ResponseMaps") try: fragment.pop("Fn::Transform") except: pass loader = Loader() service_description = loader.load_service_model(service_name=service_name, type_name='service-2') service_model = ServiceModel(service_description) protocol = service_model.protocol op_model = service_model.operation_model(action["Name"]) request_parameters = action.get("Parameters", {}) params = dict(ChainMap(*request_parameters)) print("params: {}".format(params)) serializer = create_serializer(protocol) response_parser = create_parser(protocol) print(service_model.protocol) request = serializer.serialize_to_request(params, op_model) request_object = AWSRequest( method=request['method'], url=get_endpoint(service_model.service_name), data=request['body'], headers=request['headers']) X = request_object.prepare() print("Raw request: {}".format(request)) print("Prepared request: {}".format(X)) integration = fragment["Properties"]["Integration"] new_integration = integration_template() # Copy the existing values to the new template for entry in integration.keys(): new_integration[entry] = integration[entry] # Add headers to cfn template if X.headers is not None and callable(getattr(X.headers, "keys", None)): for header in X.headers.keys(): if header.lower() != 'Content-Length'.lower(): new_integration["RequestParameters"].update({"integration.request.header.{}".format(header): "'{}'".format(X.headers[header])}) # Add Query Strings to cfn template if 'query_string' in request and callable(getattr(request['query_string'], "keys", None)): for query in request['query_string'].keys(): new_integration["RequestParameters"].update({"integration.request.querystring.{}".format(query): "'{}'".format(request['query_string'][query])}) # Set the body if isinstance(X.body, str): new_integration["RequestTemplates"]["application/json"] = X.body else: new_integration["RequestTemplates"]["application/json"] = str(X.body, "utf-8") if X.body else '' new_integration["Uri"] = ":".join([ "arn", "aws", "apigateway", REGION, service_model.endpoint_prefix, "path/" + request["url_path"] ]) new_integration["IntegrationHttpMethod"] = X.method fragment["Properties"]["Integration"] = new_integration print(fragment) return fragment