示例#1
0
    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 = Service(list_of_routes, lambda_runner)

        service.create()

        self.assertEquals(service._dict_of_routes, {
            '/:GET': api_gateway_route_1,
            '/:POST': api_gateway_route_2
        })
示例#2
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 = Service(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()
示例#3
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.stderr = Mock()
        self.service = Service(self.list_of_routes,
                               self.lambda_runner,
                               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)

    def test_request_handler_returns_process_stdout_when_making_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

        lambda_logs = "logs"
        lambda_response = "response"
        self.service._get_lambda_output = Mock()
        self.service._get_lambda_output.return_value = lambda_response, lambda_logs

        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.service._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_runtime_error_raised_when_app_not_created(self):
        with self.assertRaises(RuntimeError):
            self.service.run()

    def test_run_starts_service_multithreaded(self):
        self.service._app = Mock()
        app_run_mock = Mock()
        self.service._app.run = app_run_mock

        self.lambda_runner.is_debugging.return_value = False  # multithreaded
        self.service.run()

        app_run_mock.assert_called_once_with(threaded=True,
                                             host='127.0.0.1',
                                             port=3000)

    def test_run_starts_service_singlethreaded(self):
        self.service._app = Mock()
        app_run_mock = Mock()
        self.service._app.run = app_run_mock

        self.lambda_runner.is_debugging.return_value = True  # single threaded
        self.service.run()

        app_run_mock.assert_called_once_with(threaded=False,
                                             host='127.0.0.1',
                                             port=3000)

    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 = Service(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.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 = Service([],
                                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.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.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.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.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.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.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)

    @parameterized.expand([
        param("with both logs and response",
              b'this\nis\nlog\ndata\n{"a": "b"}', b'this\nis\nlog\ndata',
              b'{"a": "b"}'),
        param("with response as string", b"logs\nresponse", b"logs",
              b"response"),
        param("with response only", b'{"a": "b"}', None, b'{"a": "b"}'),
        param("with response only as string", b'this is the response line',
              None, b'this is the response line'),
        param("with whitespaces", b'log\ndata\n{"a": "b"}  \n\n\n',
              b"log\ndata", b'{"a": "b"}'),
        param("with empty data", b'', None, b''),
        param("with just new lines", b'\n\n', None, b''),
        param(
            "with no data but with whitespaces",
            b'\n   \n   \n',
            b'\n   ',
            b''  # Log data with whitespaces will be in the output unchanged
        )
    ])
    def test_get_lambda_output_extracts_response(self, test_case_name,
                                                 stdout_data, expected_logs,
                                                 expected_response):
        stdout = Mock()
        stdout.getvalue.return_value = stdout_data

        response, logs = self.service._get_lambda_output(stdout)
        self.assertEquals(logs, expected_logs)
        self.assertEquals(response, expected_response)