diff --git a/moto/__init__.py b/moto/__init__.py index 6a88cb935..be71cc406 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -43,6 +43,12 @@ mock_autoscaling = lazy_load(".autoscaling", "mock_autoscaling") mock_lambda = lazy_load( ".awslambda", "mock_lambda", boto3_name="lambda", backend="lambda_backends" ) +mock_lambda_simple = lazy_load( + ".awslambda_simple", + "mock_lambda_simple", + boto3_name="lambda", + backend="lambda_simple_backends", +) mock_batch = lazy_load(".batch", "mock_batch") mock_batch_simple = lazy_load( ".batch_simple", diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py index b7349323f..3b851e6b5 100644 --- a/moto/awslambda/responses.py +++ b/moto/awslambda/responses.py @@ -125,8 +125,8 @@ class LambdaResponse(BaseResponse): @amz_crc32 @amzn_request_id - def invoke( - self, request: Any, full_url: str, headers: Any + def invoke( # type: ignore + self, request=None, full_url="", headers=None ) -> Tuple[int, Dict[str, str], Union[str, bytes]]: self.setup_class(request, full_url, headers) if request.method == "POST": diff --git a/moto/awslambda_simple/__init__.py b/moto/awslambda_simple/__init__.py new file mode 100644 index 000000000..e9e77ea9d --- /dev/null +++ b/moto/awslambda_simple/__init__.py @@ -0,0 +1,5 @@ +from .models import lambda_simple_backends +from ..core.models import base_decorator + +lambda_simple_backend = lambda_simple_backends["us-east-1"] +mock_lambda_simple = base_decorator(lambda_simple_backends) diff --git a/moto/awslambda_simple/models.py b/moto/awslambda_simple/models.py new file mode 100644 index 000000000..b1446b13e --- /dev/null +++ b/moto/awslambda_simple/models.py @@ -0,0 +1,57 @@ +from ..awslambda.models import ( + lambda_backends, + BaseBackend, + LambdaBackend, +) +from ..core import BackendDict +from typing import Any, Optional, Union + + +class LambdaSimpleBackend(BaseBackend): + """ + Implements a Lambda-Backend that does not use Docker containers, will always succeed. + Annotate your tests with `@mock_lambda_simple`-decorator to use this Lambda-implementation. + """ + + @property + def backend(self) -> LambdaBackend: + return lambda_backends[self.account_id][self.region_name] + + def __getattribute__(self, name: str) -> Any: + """ + Magic part that makes this class behave like a wrapper around the regular lambda_backend + """ + if name in [ + "backend", + "account_id", + "region_name", + "urls", + "_url_module", + "__class__", + "url_bases", + ]: + return object.__getattribute__(self, name) + if name in ["invoke", "invoke_async"]: + + def newfunc(*args: Any, **kwargs: Any) -> Any: + attr = object.__getattribute__(self, name) + return attr(*args, **kwargs) + + return newfunc + else: + return object.__getattribute__(self.backend, name) + + # pylint: disable=unused-argument + def invoke( + self, + function_name: str, + qualifier: str, + body: Any, + headers: Any, + response_headers: Any, + ) -> Optional[Union[str, bytes]]: + + return b"Simple Lambda happy path OK" + + +lambda_simple_backends = BackendDict(LambdaSimpleBackend, "lambda") diff --git a/moto/awslambda_simple/responses.py b/moto/awslambda_simple/responses.py new file mode 100644 index 000000000..022f4bef8 --- /dev/null +++ b/moto/awslambda_simple/responses.py @@ -0,0 +1,8 @@ +from ..awslambda.responses import LambdaResponse +from .models import lambda_simple_backends, LambdaBackend + + +class LambdaSimpleResponse(LambdaResponse): + @property + def backend(self) -> LambdaBackend: + return lambda_simple_backends[self.current_account][self.region] diff --git a/moto/awslambda_simple/urls.py b/moto/awslambda_simple/urls.py new file mode 100644 index 000000000..10571fa64 --- /dev/null +++ b/moto/awslambda_simple/urls.py @@ -0,0 +1,8 @@ +from ..awslambda.urls import url_bases as lambda_url_bases +from ..awslambda.urls import url_paths as lambda_url_paths +from .responses import LambdaSimpleResponse + +url_bases = lambda_url_bases.copy() +url_paths = { + k: LambdaSimpleResponse.method_dispatch(v) for k, v in lambda_url_paths.items() # type: ignore +} diff --git a/moto/core/responses.py b/moto/core/responses.py index 5a5cc2fc4..71dec337f 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -533,6 +533,7 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): action = camelcase_to_underscores(self._get_action()) method_names = method_names_from_class(self.__class__) + if action in method_names: method = getattr(self, action) try: diff --git a/tests/test_awslambda_simple/__init__.py b/tests/test_awslambda_simple/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_awslambda_simple/test_lambda_simple.py b/tests/test_awslambda_simple/test_lambda_simple.py new file mode 100644 index 000000000..3d0dccfd2 --- /dev/null +++ b/tests/test_awslambda_simple/test_lambda_simple.py @@ -0,0 +1,39 @@ +from unittest import SkipTest +import boto3 + +from moto import mock_iam, mock_lambda_simple, settings +from ..test_awslambda.utilities import get_test_zip_file1, get_role_name + +LAMBDA_REGION = "us-west-2" +PYTHON_VERSION = "3.11" +FUNCTION_NAME = "test-function-123" + +if settings.TEST_SERVER_MODE: + raise SkipTest("No point in testing batch_simple in ServerMode") + + +@mock_iam +@mock_lambda_simple +def test_run_function(): + client = boto3.client("lambda", LAMBDA_REGION) + zip_content = get_test_zip_file1() + function_name = FUNCTION_NAME + role = get_role_name() + client.create_function( + FunctionName=function_name, + Runtime=PYTHON_VERSION, + Role=role, + Handler="lambda_function.lambda_handler", + Code={"ZipFile": zip_content}, + Description="test lambda function", + Timeout=3, + MemorySize=128, + Publish=True, + ) + + result = client.invoke( + FunctionName=FUNCTION_NAME, + LogType="Tail", + ) + assert result["StatusCode"] == 200 + assert result["Payload"].read().decode("utf-8") == "Simple Lambda happy path OK"