Example #1
0
    def test_composite_handler_stops_handler_chain(self):
        def inner1(_chain: HandlerChain, request: RequestContext,
                   response: Response):
            _chain.stop()

        inner2 = mock.MagicMock()
        outer1 = mock.MagicMock()
        outer2 = mock.MagicMock()
        response1 = mock.MagicMock()

        chain = HandlerChain()

        composite = CompositeHandler()
        composite.handlers.append(inner1)
        composite.handlers.append(inner2)

        chain.request_handlers.append(outer1)
        chain.request_handlers.append(composite)
        chain.request_handlers.append(outer2)
        chain.response_handlers.append(response1)

        chain.handle(RequestContext(), Response())
        outer1.assert_called_once()
        outer2.assert_not_called()
        inner2.assert_not_called()
        response1.assert_called_once()
Example #2
0
    def test_composite_handler_exception_calls_outer_exception_handlers(self):
        def inner1(_chain: HandlerChain, request: RequestContext,
                   response: Response):
            raise ValueError()

        inner2 = mock.MagicMock()
        outer1 = mock.MagicMock()
        outer2 = mock.MagicMock()
        exception_handler = mock.MagicMock()
        response1 = mock.MagicMock()

        chain = HandlerChain()

        composite = CompositeHandler()
        composite.handlers.append(inner1)
        composite.handlers.append(inner2)

        chain.request_handlers.append(outer1)
        chain.request_handlers.append(composite)
        chain.request_handlers.append(outer2)
        chain.exception_handlers.append(exception_handler)
        chain.response_handlers.append(response1)

        chain.handle(RequestContext(), Response())
        outer1.assert_called_once()
        outer2.assert_not_called()
        inner2.assert_not_called()
        exception_handler.assert_called_once()
        response1.assert_called_once()
Example #3
0
    def test_without_context_without_expand(self):
        def fn(*args):
            assert len(args) == 1
            assert type(args[0]) == dict

        dispatcher = ServiceRequestDispatcher(fn,
                                              "SomeAction",
                                              pass_context=False,
                                              expand_parameters=False)
        dispatcher(RequestContext(), ServiceRequest())
Example #4
0
    def test_default_dispatcher(self):
        class SomeAction(ServiceRequest):
            ArgOne: str
            ArgTwo: int

        def fn(context, arg_one, arg_two):
            assert type(context) == RequestContext
            assert arg_one == "foo"
            assert arg_two == 69

        dispatcher = ServiceRequestDispatcher(fn, "SomeAction")
        dispatcher(RequestContext(), SomeAction(ArgOne="foo", ArgTwo=69))
Example #5
0
 def create_request_context(self, request: HttpRequest) -> RequestContext:
     context = RequestContext()
     context.service = self.service
     context.request = request
     context.region = get_region(request)
     context.account_id = get_account_id(request)
     return context
Example #6
0
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",
    }
Example #7
0
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",
    }
Example #8
0
    def invoke(self, context: RequestContext) -> HttpResponse:
        if context.operation and context.service_request:
            # if the parsed request is already set in the context, re-use them
            operation, instance = context.operation, context.service_request
        else:
            # otherwise parse the incoming HTTPRequest
            operation, instance = self.parser.parse(context.request)
            context.operation = operation

        serializer = self.serializer

        try:
            # Find the operation's handler in the dispatch table
            if operation.name not in self.dispatch_table:
                LOG.warning(
                    "missing entry in dispatch table for %s.%s",
                    self.service.service_name,
                    operation.name,
                )
                raise NotImplementedError
            handler = self.dispatch_table[operation.name]

            # Call the appropriate handler
            result = handler(context, instance) or dict()
            # Serialize result dict to an HTTPResponse and return it
            return serializer.serialize_to_response(result, operation)
        except ServiceException as e:
            return serializer.serialize_error_to_response(e, operation)
        except NotImplementedError:
            action_name = operation.name
            service_name = operation.service_model.service_name
            message = (
                f"API action '{action_name}' for service '{service_name}' "
                f"not yet implemented")
            LOG.info(message)
            error = CommonServiceException("InternalFailure",
                                           message,
                                           status_code=501)

            # record event
            analytics.log.event("services_notimplemented",
                                payload={
                                    "s": service_name,
                                    "a": action_name
                                })

            return serializer.serialize_error_to_response(error, operation)
Example #9
0
def create_aws_request_context(
    service_name: str,
    action: str,
    parameters: Mapping[str, Any] = None,
    region: str = None,
    endpoint_url: Optional[str] = None,
) -> RequestContext:
    """
    This is a stripped-down version of what the botocore client does to perform an HTTP request from a client call. A
    client call looks something like this: boto3.client("sqs").create_queue(QueueName="myqueue"), which will be
    serialized into an HTTP request. This method does the same, without performing the actual request, and with a
    more low-level interface. An equivalent call would be

         create_aws_request_context("sqs", "CreateQueue", {"QueueName": "myqueue"})

    :param service_name: the AWS service
    :param action: the action to invoke
    :param parameters: the invocation parameters
    :param region: the region name (default is us-east-1)
    :param endpoint_url: the endpoint to call (defaults to localstack)
    :return: a RequestContext object that describes this request
    """
    if parameters is None:
        parameters = {}
    if region is None:
        region = config.AWS_REGION_US_EAST_1

    service = load_service(service_name)
    operation = service.operation_model(action)

    # we re-use botocore internals here to serialize the HTTP request, but don't send it
    client = aws_stack.connect_to_service(service_name,
                                          endpoint_url=endpoint_url,
                                          region_name=region)
    request_context = {
        "client_region": region,
        "has_streaming_input": operation.has_streaming_input,
        "auth_type": operation.auth_type,
    }
    request_dict = client._convert_to_request_dict(parameters,
                                                   operation,
                                                   context=request_context)
    aws_request = client._endpoint.create_request(request_dict, operation)

    context = RequestContext()
    context.service = service
    context.operation = operation
    context.region = region
    context.request = create_http_request(aws_request)

    return context
Example #10
0
    def invoke(self, context: RequestContext) -> HttpResponse:
        if context.operation and context.service_request:
            # if the parsed request is already set in the context, re-use them
            operation, instance = context.operation, context.service_request
        else:
            # otherwise, parse the incoming HTTPRequest
            operation, instance = self.parser.parse(context.request)
            context.operation = operation

        try:
            # Find the operation's handler in the dispatch table
            if operation.name not in self.dispatch_table:
                LOG.warning(
                    "missing entry in dispatch table for %s.%s",
                    self.service.service_name,
                    operation.name,
                )
                raise NotImplementedError

            return self.dispatch_request(context, instance)
        except ServiceException as e:
            return self.on_service_exception(context, e)
        except NotImplementedError:
            return self.on_not_implemented_error(context)
Example #11
0
    def test_composite_handler_continues_handler_chain(self):
        inner1 = mock.MagicMock()
        inner2 = mock.MagicMock()
        outer1 = mock.MagicMock()
        outer2 = mock.MagicMock()
        response1 = mock.MagicMock()

        chain = HandlerChain()

        composite = CompositeHandler()
        composite.handlers.append(inner1)
        composite.handlers.append(inner2)

        chain.request_handlers.append(outer1)
        chain.request_handlers.append(composite)
        chain.request_handlers.append(outer2)
        chain.response_handlers.append(response1)

        chain.handle(RequestContext(), Response())
        outer1.assert_called_once()
        outer2.assert_called_once()
        inner1.assert_called_once()
        inner2.assert_called_once()
        response1.assert_called_once()
Example #12
0
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",
    }
Example #13
0
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",
    }
Example #14
0
    def test_dispatch_without_args(self):
        def fn(context):
            assert type(context) == RequestContext

        dispatcher = ServiceRequestDispatcher(fn, "SomeAction")
        dispatcher(RequestContext(), ServiceRequest())