def test_lambda_output_list_not_dict(self):
        lambda_output = '[]'

        with self.assertRaises(TypeError):
            LocalApigwService._parse_lambda_output(lambda_output,
                                                   binary_types=[],
                                                   flask_request=Mock())
    def test_status_code_negative_int_str(self):
        lambda_output = '{"statusCode": "-1", "headers": {}, "body": "{\\"message\\":\\"Hello from Lambda\\"}", ' \
                            '"isBase64Encoded": false}'

        with self.assertRaises(TypeError):
            LocalApigwService._parse_lambda_output(lambda_output,
                                                   binary_types=[],
                                                   flask_request=Mock())
    def test_status_code_int_str(self):
        lambda_output = '{"statusCode": "200", "headers": {}, "body": "{\\"message\\":\\"Hello from Lambda\\"}", ' \
                        '"isBase64Encoded": false}'

        (status_code, _, _) = LocalApigwService._parse_lambda_output(lambda_output,
                                                                     binary_types=[],
                                                                     flask_request=Mock())
        self.assertEquals(status_code, 200)
    def test_query_string_params_with_empty_params(self):
        request_mock = Mock()
        query_param_args_mock = Mock()
        query_param_args_mock.lists.return_value = {}.items()
        request_mock.args = query_param_args_mock

        actual_query_string = LocalApigwService._query_string_params(request_mock)
        self.assertEquals(actual_query_string, {})
    def test_custom_content_type_header_is_not_modified(self):
        lambda_output = '{"statusCode": 200, "headers":{"Content-Type": "text/xml"}, "body": "{}", ' \
                        '"isBase64Encoded": false}'

        (_, headers, _) = LocalApigwService._parse_lambda_output(lambda_output, binary_types=[], flask_request=Mock())

        self.assertIn("Content-Type", headers)
        self.assertEquals(headers["Content-Type"], "text/xml")
    def test_default_content_type_header_added_with_empty_headers(self):
        lambda_output = '{"statusCode": 200, "headers":{}, "body": "{\\"message\\":\\"Hello from Lambda\\"}", ' \
                        '"isBase64Encoded": false}'

        (_, headers, _) = LocalApigwService._parse_lambda_output(lambda_output, binary_types=[], flask_request=Mock())

        self.assertIn("Content-Type", headers)
        self.assertEquals(headers["Content-Type"], "application/json")
    def test_query_string_params_with_param_value_being_non_empty_list(self):
        request_mock = Mock()
        query_param_args_mock = Mock()
        query_param_args_mock.lists.return_value = {"param": ["a", "b"]}.items()
        request_mock.args = query_param_args_mock

        actual_query_string = LocalApigwService._query_string_params(request_mock)
        self.assertEquals(actual_query_string, {"param": "b"})
    def test_create_creates_dict_of_routes(self):
        function_name_1 = Mock()
        function_name_2 = Mock()
        api_gateway_route_1 = Route(['GET'], function_name_1, '/')
        api_gateway_route_2 = Route(['POST'], function_name_2, '/')

        list_of_routes = [api_gateway_route_1, api_gateway_route_2]

        lambda_runner = Mock()

        service = LocalApigwService(list_of_routes, lambda_runner)

        service.create()

        self.assertEquals(service._dict_of_routes, {'/:GET': api_gateway_route_1,
                                                    '/:POST': api_gateway_route_2
                                                    })
    def test_parse_returns_correct_tuple(self):
        lambda_output = '{"statusCode": 200, "headers": {}, "body": "{\\"message\\":\\"Hello from Lambda\\"}", ' \
                        '"isBase64Encoded": false}'

        (status_code, headers, body) = LocalApigwService._parse_lambda_output(lambda_output,
                                                                              binary_types=[],
                                                                              flask_request=Mock())

        self.assertEquals(status_code, 200)
        self.assertEquals(headers, {"Content-Type": "application/json"})
        self.assertEquals(body, '{"message":"Hello from Lambda"}')
    def test_properties_are_null(self):
        lambda_output = '{"statusCode": 0, "headers": null, "body": null, ' \
                        '"isBase64Encoded": null}'

        (status_code, headers, body) = LocalApigwService._parse_lambda_output(lambda_output,
                                                                              binary_types=[],
                                                                              flask_request=Mock())

        self.assertEquals(status_code, 200)
        self.assertEquals(headers, {"Content-Type": "application/json"})
        self.assertEquals(body, "no data")
    def test_construct_event_with_binary_data(self, should_base64_encode_patch):
        should_base64_encode_patch.return_value = True

        binary_body = b"011000100110100101101110011000010111001001111001"  # binary in binary
        base64_body = base64.b64encode(binary_body).decode('utf-8')

        self.request_mock.get_data.return_value = binary_body
        self.expected_dict["body"] = base64_body
        self.expected_dict["isBase64Encoded"] = True

        actual_event_str = LocalApigwService._construct_event(self.request_mock, 3000, binary_types=[])
        self.assertEquals(json.loads(actual_event_str), self.expected_dict)
    def setUp(self):
        self.function_name = Mock()
        self.api_gateway_route = Route(['GET'], self.function_name, '/')
        self.list_of_routes = [self.api_gateway_route]

        self.lambda_runner = Mock()
        self.lambda_runner.is_debugging.return_value = False

        self.stderr = Mock()
        self.service = LocalApigwService(self.list_of_routes,
                                         self.lambda_runner,
                                         port=3000,
                                         host='127.0.0.1',
                                         stderr=self.stderr)
Example #13
0
    def start(self):
        """
        Creates and starts the local API Gateway service. This method will block until the service is stopped
        manually using an interrupt. After the service is started, callers can make HTTP requests to the endpoint
        to invoke the Lambda function and receive a response.

        NOTE: This is a blocking call that will not return until the thread is interrupted with SIGINT/SIGTERM
        """

        routing_list = self._make_routing_list(self.api_provider)

        if not routing_list:
            raise NoApisDefined("No APIs available in SAM template")

        static_dir_path = self._make_static_dir_path(self.cwd, self.static_dir)

        # We care about passing only stderr to the Service and not stdout because stdout from Docker container
        # contains the response to the API which is sent out as HTTP response. Only stderr needs to be printed
        # to the console or a log file. stderr from Docker container contains runtime logs and output of print
        # statements from the Lambda function
        service = LocalApigwService(routing_list=routing_list,
                                    lambda_runner=self.lambda_runner,
                                    static_dir=static_dir_path,
                                    port=self.port,
                                    host=self.host,
                                    stderr=self.stderr_stream)

        service.create()

        # Print out the list of routes that will be mounted
        self._print_routes(self.api_provider, self.host, self.port)
        LOG.info("You can now browse to the above endpoints to invoke your functions. "
                 "You do not need to restart/reload SAM CLI while working on your functions, "
                 "changes will be reflected instantly/automatically. You only need to restart "
                 "SAM CLI if you update your AWS SAM template")

        service.run()
    def test_parse_returns_decodes_base64_to_binary(self, should_decode_body_patch):
        should_decode_body_patch.return_value = True

        binary_body = b"011000100110100101101110011000010111001001111001"  # binary in binary
        base64_body = base64.b64encode(binary_body).decode('utf-8')
        lambda_output = {"statusCode": 200,
                         "headers": {"Content-Type": "application/octet-stream"},
                         "body": base64_body,
                         "isBase64Encoded": False}

        (status_code, headers, body) = LocalApigwService._parse_lambda_output(json.dumps(lambda_output),
                                                                              binary_types=['*/*'],
                                                                              flask_request=Mock())

        self.assertEquals(status_code, 200)
        self.assertEquals(headers, {"Content-Type": "application/octet-stream"})
        self.assertEquals(body, binary_body)
Example #15
0
    def test_lambda_output_list_not_dict(self):
        lambda_output = "[]"

        with self.assertRaises(TypeError):
            LocalApigwService._parse_lambda_output(lambda_output, binary_types=[], flask_request=Mock())
 def test_should_base64_encode_returns_false(self, test_case_name, binary_types, mimetype):
     self.assertFalse(LocalApigwService._should_base64_encode(binary_types, mimetype))
Example #17
0
class TestApiGatewayService(TestCase):

    def setUp(self):
        self.function_name = Mock()
        self.api_gateway_route = Route(['GET'], self.function_name, '/')
        self.list_of_routes = [self.api_gateway_route]

        self.lambda_runner = Mock()
        self.lambda_runner.is_debugging.return_value = False

        self.stderr = Mock()
        self.service = LocalApigwService(self.list_of_routes,
                                         self.lambda_runner,
                                         port=3000,
                                         host='127.0.0.1',
                                         stderr=self.stderr)

    def test_request_must_invoke_lambda(self):
        make_response_mock = Mock()

        self.service.service_response = make_response_mock
        self.service._get_current_route = Mock()
        self.service._construct_event = Mock()

        parse_output_mock = Mock()
        parse_output_mock.return_value = ("status_code", "headers", "body")
        self.service._parse_lambda_output = parse_output_mock

        service_response_mock = Mock()
        service_response_mock.return_value = make_response_mock
        self.service.service_response = service_response_mock

        result = self.service._request_handler()

        self.assertEquals(result, make_response_mock)
        self.lambda_runner.invoke.assert_called_with(ANY,
                                                     ANY,
                                                     stdout=ANY,
                                                     stderr=self.stderr)

    @patch('samcli.local.apigw.local_apigw_service.LambdaOutputParser')
    def test_request_handler_returns_process_stdout_when_making_response(self, lambda_output_parser_mock):

        make_response_mock = Mock()

        self.service.service_response = make_response_mock
        self.service._get_current_route = Mock()
        self.service._construct_event = Mock()

        parse_output_mock = Mock()
        parse_output_mock.return_value = ("status_code", "headers", "body")
        self.service._parse_lambda_output = parse_output_mock

        lambda_logs = "logs"
        lambda_response = "response"
        is_customer_error = False
        lambda_output_parser_mock.get_lambda_output.return_value = lambda_response, lambda_logs, is_customer_error
        service_response_mock = Mock()
        service_response_mock.return_value = make_response_mock
        self.service.service_response = service_response_mock

        result = self.service._request_handler()

        self.assertEquals(result, make_response_mock)
        lambda_output_parser_mock.get_lambda_output.assert_called_with(ANY)

        # Make sure the parse method is called only on the returned response and not on the raw data from stdout
        parse_output_mock.assert_called_with(lambda_response, ANY, ANY)
        # Make sure the logs are written to stderr
        self.stderr.write.assert_called_with(lambda_logs)

    def test_request_handler_returns_make_response(self):
        make_response_mock = Mock()

        self.service.service_response = make_response_mock
        self.service._get_current_route = Mock()
        self.service._construct_event = Mock()

        parse_output_mock = Mock()
        parse_output_mock.return_value = ("status_code", "headers", "body")
        self.service._parse_lambda_output = parse_output_mock

        service_response_mock = Mock()
        service_response_mock.return_value = make_response_mock
        self.service.service_response = service_response_mock

        result = self.service._request_handler()

        self.assertEquals(result, make_response_mock)

    def test_create_creates_dict_of_routes(self):
        function_name_1 = Mock()
        function_name_2 = Mock()
        api_gateway_route_1 = Route(['GET'], function_name_1, '/')
        api_gateway_route_2 = Route(['POST'], function_name_2, '/')

        list_of_routes = [api_gateway_route_1, api_gateway_route_2]

        lambda_runner = Mock()

        service = LocalApigwService(list_of_routes, lambda_runner)

        service.create()

        self.assertEquals(service._dict_of_routes, {'/:GET': api_gateway_route_1,
                                                    '/:POST': api_gateway_route_2
                                                    })

    @patch('samcli.local.apigw.local_apigw_service.Flask')
    def test_create_creates_flask_app_with_url_rules(self, flask):
        app_mock = Mock()
        flask.return_value = app_mock

        self.service._construct_error_handling = Mock()

        self.service.create()

        app_mock.add_url_rule.assert_called_once_with('/',
                                                      endpoint='/',
                                                      view_func=self.service._request_handler,
                                                      methods=['GET'],
                                                      provide_automatic_options=False)

    def test_initalize_creates_default_values(self):
        self.assertEquals(self.service.port, 3000)
        self.assertEquals(self.service.host, '127.0.0.1')
        self.assertEquals(self.service.routing_list, self.list_of_routes)
        self.assertIsNone(self.service.static_dir)
        self.assertEquals(self.service.lambda_runner, self.lambda_runner)

    def test_initalize_with_values(self):
        lambda_runner = Mock()
        local_service = LocalApigwService([], lambda_runner, static_dir='dir/static', port=5000, host='129.0.0.0')
        self.assertEquals(local_service.port, 5000)
        self.assertEquals(local_service.host, '129.0.0.0')
        self.assertEquals(local_service.routing_list, [])
        self.assertEquals(local_service.static_dir, 'dir/static')
        self.assertEquals(local_service.lambda_runner, lambda_runner)

    @patch('samcli.local.apigw.local_apigw_service.ServiceErrorResponses')
    def test_request_handles_error_when_invoke_cant_find_function(self, service_error_responses_patch):

        not_found_response_mock = Mock()
        self.service._construct_event = Mock()
        self.service._get_current_route = Mock()
        service_error_responses_patch.lambda_not_found_response.return_value = not_found_response_mock

        self.lambda_runner.invoke.side_effect = FunctionNotFound()

        response = self.service._request_handler()

        self.assertEquals(response, not_found_response_mock)

    def test_request_throws_when_invoke_fails(self):
        self.lambda_runner.invoke.side_effect = Exception()

        self.service._construct_event = Mock()
        self.service._get_current_route = Mock()

        with self.assertRaises(Exception):
            self.service._request_handler()

    @patch('samcli.local.apigw.local_apigw_service.ServiceErrorResponses')
    def test_request_handler_errors_when_parse_lambda_output_raises_keyerror(self, service_error_responses_patch):
        parse_output_mock = Mock()
        parse_output_mock.side_effect = KeyError()
        self.service._parse_lambda_output = parse_output_mock

        failure_response_mock = Mock()

        service_error_responses_patch.lambda_failure_response.return_value = failure_response_mock

        self.service._construct_event = Mock()
        self.service._get_current_route = Mock()

        result = self.service._request_handler()

        self.assertEquals(result, failure_response_mock)

    @patch('samcli.local.apigw.local_apigw_service.ServiceErrorResponses')
    def test_request_handler_errors_when_get_current_route_fails(self, service_error_responses_patch):
        get_current_route = Mock()
        get_current_route.side_effect = KeyError()
        self.service._get_current_route = get_current_route

        with self.assertRaises(KeyError):
            self.service._request_handler()

    @patch('samcli.local.apigw.local_apigw_service.ServiceErrorResponses')
    def test_request_handler_errors_when_unable_to_read_binary_data(self, service_error_responses_patch):
        _construct_event = Mock()
        _construct_event.side_effect = UnicodeDecodeError("utf8", b"obj", 1, 2, "reason")
        self.service._get_current_route = Mock()
        self.service._construct_event = _construct_event

        failure_mock = Mock()
        service_error_responses_patch.lambda_failure_response.return_value = failure_mock

        result = self.service._request_handler()
        self.assertEquals(result, failure_mock)

    @patch('samcli.local.apigw.local_apigw_service.request')
    def test_get_current_route(self, request_patch):

        request_mock = Mock()
        request_mock.endpoint = "path"
        request_mock.method = "method"

        request_patch.return_value = request_mock

        route_key_method_mock = Mock()
        route_key_method_mock.return_value = "method:path"
        self.service._route_key = route_key_method_mock
        self.service._dict_of_routes = {"method:path": "function"}

        self.assertEquals(self.service._get_current_route(request_mock), "function")

    @patch('samcli.local.apigw.local_apigw_service.request')
    def test_get_current_route_keyerror(self, request_patch):
        """
        When the a HTTP request for given method+path combination is allowed by Flask but not in the list of routes,
        something is messed up. Flask should be configured only from the list of routes.
        """

        request_mock = Mock()
        request_mock.endpoint = "path"
        request_mock.method = "method"

        request_patch.return_value = request_mock

        route_key_method_mock = Mock()
        route_key_method_mock.return_value = "method:path"
        self.service._route_key = route_key_method_mock
        self.service._dict_of_routes = {"a": "b"}

        with self.assertRaises(KeyError):
            self.service._get_current_route(request_mock)
Example #18
0
 def test_should_base64_encode_returns_false(self, test_case_name,
                                             binary_types, mimetype):
     self.assertFalse(
         LocalApigwService._should_base64_encode(binary_types, mimetype))
Example #19
0
    def test_construct_event_no_data(self):
        self.request_mock.get_data.return_value = None
        self.expected_dict["body"] = None

        actual_event_str = LocalApigwService._construct_event(self.request_mock, 3000, binary_types=[])
        self.assertEquals(json.loads(actual_event_str), self.expected_dict)
Example #20
0
 def test_construct_event_with_data(self):
     actual_event_str = LocalApigwService._construct_event(self.request_mock, 3000, binary_types=[])
     self.assertEquals(json.loads(actual_event_str), self.expected_dict)
Example #21
0
    def test_lambda_output_not_json_serializable(self):
        lambda_output = 'some str'

        with self.assertRaises(ValueError):
            LocalApigwService._parse_lambda_output(lambda_output, binary_types=[], flask_request=Mock())