import os
import posixpath
from unittest import TestCase
from unittest.mock import patch, PropertyMock, Mock, call

from parameterized import parameterized

from samcli.lib.utils.architecture import X86_64, ARM64

from samcli.commands.local.cli_common.user_exceptions import InvalidLayerVersionArn
from samcli.lib.providers.provider import Function, LayerVersion, Stack, FunctionBuildInfo
from samcli.lib.providers.sam_function_provider import SamFunctionProvider, RefreshableSamFunctionProvider
from samcli.lib.providers.exceptions import InvalidLayerReference
from samcli.lib.utils.packagetype import IMAGE, ZIP


def make_root_stack(template, parameter_overrides=None):
    return Stack("", "", "template.yaml", parameter_overrides, template)


STACK_PATH = posixpath.join("this_is_a", "stack_path")
STACK = Mock(stack_path=STACK_PATH, location="./template.yaml")


class TestSamFunctionProviderEndToEnd(TestCase):
    """
    Test all public methods with an input template and its child templates
    """

    TEMPLATE = {
        "Resources": {
            "SamFunctions": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "FunctionName": "SamFunc1",
                    "CodeUri": "/usr/foo/bar",
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
            },
            "SamFuncWithInlineCode": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "FunctionName": "SamFuncWithInlineCode",
                    "InlineCode": "testcode",
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
            },
            "SamFunc2": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    # CodeUri is unsupported S3 location
                    "CodeUri": "s3://bucket/key",
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
            },
            "SamFunc3": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    # CodeUri is unsupported S3 location
                    "CodeUri": {"Bucket": "bucket", "Key": "key"},
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
            },
            "SamFuncWithFunctionNameOverride": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "FunctionName": "SamFuncWithFunctionNameOverride-x",
                    "CodeUri": "/usr/foo/bar",
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
            },
            "SamFuncWithImage1": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "PackageType": IMAGE,
                },
                "Metadata": {"DockerTag": "tag", "DockerContext": "./image", "Dockerfile": "Dockerfile"},
            },
            "SamFuncWithImage2": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "ImageUri": "image:tag",
                    "PackageType": IMAGE,
                },
                "Metadata": {"DockerTag": "tag", "DockerContext": "./image", "Dockerfile": "Dockerfile"},
            },
            "SamFuncWithImage3": {
                # ImageUri is unsupported ECR location
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "ImageUri": "123456789012.dkr.ecr.us-east-1.amazonaws.com/myrepo:myimage",
                    "PackageType": IMAGE,
                },
            },
            "SamFuncWithImage4": {
                # ImageUri is unsupported ECR location, but metadata is still provided, build
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "ImageUri": "123456789012.dkr.ecr.us-east-1.amazonaws.com/myrepo:myimage",
                    "PackageType": IMAGE,
                },
                "Metadata": {"DockerTag": "tag", "DockerContext": "./image", "Dockerfile": "Dockerfile"},
            },
            "SamFuncWithRuntimeManagementConfig": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "CodeUri": "/usr/foo/bar",
                    "Runtime": "python3.9",
                    "Handler": "index.handler",
                    "RuntimeManagementConfig": {
                        "UpdateRuntimeOn": "Manual",
                        "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:python3.9::0af1966588ced06e3143ae720245c9b7aeaae213c6921c12c742a166679cc505",
                    },
                },
            },
            "LambdaFunc1": {
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "Code": {"S3Bucket": "bucket", "S3Key": "key"},
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
            },
            "LambdaFuncWithImage1": {
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "PackageType": IMAGE,
                },
                "Metadata": {"DockerTag": "tag", "DockerContext": "./image", "Dockerfile": "Dockerfile"},
            },
            "LambdaFuncWithImage2": {
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "Code": {"ImageUri": "image:tag"},
                    "PackageType": IMAGE,
                },
                "Metadata": {"DockerTag": "tag", "DockerContext": "./image", "Dockerfile": "Dockerfile"},
            },
            "LambdaFuncWithImage3": {
                # ImageUri is unsupported ECR location
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "Code": {"ImageUri": "123456789012.dkr.ecr.us-east-1.amazonaws.com/myrepo"},
                    "PackageType": IMAGE,
                },
            },
            "LambdaFuncWithImage4": {
                # ImageUri is unsupported ECR location, but metadata is still provided, build
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "Code": {"ImageUri": "123456789012.dkr.ecr.us-east-1.amazonaws.com/myrepo"},
                    "PackageType": IMAGE,
                },
                "Metadata": {"DockerTag": "tag", "DockerContext": "./image", "Dockerfile": "Dockerfile"},
            },
            "LambdaFuncWithInlineCode": {
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "Code": {"ZipFile": "testcode"},
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
            },
            "LambdaFuncWithLocalPath": {
                "Type": "AWS::Lambda::Function",
                "Properties": {"Code": "./some/path/to/code", "Runtime": "nodejs4.3", "Handler": "index.handler"},
            },
            "LambdaFuncWithFunctionNameOverride": {
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "FunctionName": "LambdaFuncWithFunctionNameOverride-x",
                    "Code": "./some/path/to/code",
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
            },
            "LambdaFuncWithCodeSignConfig": {
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "FunctionName": "LambdaFuncWithCodeSignConfig",
                    "Code": "./some/path/to/code",
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                    "CodeSigningConfigArn": "codeSignConfigArn",
                },
            },
            "LambdaFuncWithCustomId": {
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "Code": "/usr/foo/bar",
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
                "Metadata": {"SamResourceId": "LambdaFunctionCustomId-x"},
            },
            "LambdaCDKFunc": {
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "Code": {"Bucket": "bucket", "Key": "key"},
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
                "Metadata": {
                    "aws:cdk:path": "Stack/LambdaCFKFunction-x/Resource",
                    "aws:asset:path": "/usr/foo/bar",
                    "aws:asset:property": "Code",
                },
            },
            "OtherResource": {
                "Type": "AWS::Serverless::Api",
                "Properties": {"StageName": "prod", "DefinitionUri": "s3://bucket/key"},
            },
            "ChildStack": {
                "Type": "AWS::Serverless::Application",
                "Properties": {"Location": "./child.yaml"},
            },
        }
    }

    CHILD_TEMPLATE = {
        "Resources": {
            "SamFunctionsInChild": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "FunctionName": "SamFunctionsInChildName",
                    "CodeUri": "./foo/bar",
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
            },
            "SamFunctionsInChildAbsPath": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "FunctionName": "SamFunctionsInChildAbsPathName",
                    "CodeUri": "/foo/bar",
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
            },
            "SamImageFunctionsInChild": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "PackageType": "Image",
                },
                "Metadata": {"DockerTag": "tag", "DockerContext": "./image", "Dockerfile": "Dockerfile"},
            },
            "LambdaCDKFuncInChild": {
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "Code": {"Bucket": "bucket", "Key": "key"},
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                },
                "Metadata": {
                    "aws:cdk:path": "Stack/LambdaCDKFuncInChild-x/Resource",
                    "aws:asset:path": "/usr/foo/bar",
                    "aws:asset:property": "Code",
                },
            },
        }
    }

    def setUp(self):
        self.parameter_overrides = {}
        root_stack = Stack("", "", "template.yaml", self.parameter_overrides, self.TEMPLATE)
        child_stack = Stack("", "ChildStack", "./child/template.yaml", None, self.CHILD_TEMPLATE)
        with patch("samcli.lib.providers.sam_stack_provider.get_template_data") as get_template_data_mock:
            get_template_data_mock.side_effect = lambda t: {
                "template.yaml": self.TEMPLATE,
                "./child/template.yaml": self.CHILD_TEMPLATE,
            }
            self.provider = SamFunctionProvider([root_stack, child_stack])

    @parameterized.expand(
        [
            (
                "SamFunc1",
                Function(
                    function_id="SamFunctions",
                    name="SamFunctions",
                    functionname="SamFunc1",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri="/usr/foo/bar",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "SamFunctions"},
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                "SamFuncWithInlineCode",
                Function(
                    function_id="SamFuncWithInlineCode",
                    name="SamFuncWithInlineCode",
                    functionname="SamFuncWithInlineCode",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri=None,
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "SamFuncWithInlineCode"},
                    inlinecode="testcode",
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.InlineCode,
                ),
            ),
            (
                "SamFunctions",
                Function(
                    function_id="SamFunctions",
                    name="SamFunctions",
                    functionname="SamFunc1",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri="/usr/foo/bar",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "SamFunctions"},
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            ("SamFunc2", None),  # codeuri is a s3 location, ignored
            ("SamFunc3", None),  # codeuri is a s3 location, ignored
            (
                "SamFuncWithImage1",
                Function(
                    function_id="SamFuncWithImage1",
                    name="SamFuncWithImage1",
                    functionname="SamFuncWithImage1",
                    runtime=None,
                    handler=None,
                    codeuri=".",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=IMAGE,
                    metadata={
                        "DockerTag": "tag",
                        "DockerContext": os.path.join("image"),
                        "Dockerfile": "Dockerfile",
                        "SamResourceId": "SamFuncWithImage1",
                    },
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableImage,
                ),
            ),
            (
                "SamFuncWithImage2",
                Function(
                    function_id="SamFuncWithImage2",
                    name="SamFuncWithImage2",
                    functionname="SamFuncWithImage2",
                    runtime=None,
                    handler=None,
                    codeuri=".",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    inlinecode=None,
                    imageuri="image:tag",
                    imageconfig=None,
                    packagetype=IMAGE,
                    metadata={
                        "DockerTag": "tag",
                        "DockerContext": os.path.join("image"),
                        "Dockerfile": "Dockerfile",
                        "SamResourceId": "SamFuncWithImage2",
                    },
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableImage,
                ),
            ),
            ("SamFuncWithImage3", None),  # imageuri is ecr location, ignored
            (
                "SamFuncWithImage4",  # despite imageuri is ecr location, the necessary metadata is still provided, build
                Function(
                    function_id="SamFuncWithImage4",
                    name="SamFuncWithImage4",
                    functionname="SamFuncWithImage4",
                    runtime=None,
                    handler=None,
                    codeuri=".",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    inlinecode=None,
                    imageuri="123456789012.dkr.ecr.us-east-1.amazonaws.com/myrepo:myimage",
                    imageconfig=None,
                    packagetype=IMAGE,
                    metadata={
                        "DockerTag": "tag",
                        "DockerContext": os.path.join("image"),
                        "Dockerfile": "Dockerfile",
                        "SamResourceId": "SamFuncWithImage4",
                    },
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableImage,
                ),
            ),
            (
                "SamFuncWithFunctionNameOverride-x",
                Function(
                    function_id="SamFuncWithFunctionNameOverride",
                    name="SamFuncWithFunctionNameOverride",
                    functionname="SamFuncWithFunctionNameOverride-x",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri="/usr/foo/bar",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "SamFuncWithFunctionNameOverride"},
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                "SamFuncWithRuntimeManagementConfig",
                Function(
                    function_id="SamFuncWithRuntimeManagementConfig",
                    name="SamFuncWithRuntimeManagementConfig",
                    functionname="SamFuncWithRuntimeManagementConfig",
                    runtime="python3.9",
                    handler="index.handler",
                    codeuri="/usr/foo/bar",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "SamFuncWithRuntimeManagementConfig"},
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    runtime_management_config={
                        "UpdateRuntimeOn": "Manual",
                        "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:python3.9::0af1966588ced06e3143ae720245c9b7aeaae213c6921c12c742a166679cc505",
                    },
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            ("LambdaFunc1", None),  # codeuri is a s3 location, ignored
            (
                "LambdaFuncWithImage1",
                Function(
                    function_id="LambdaFuncWithImage1",
                    name="LambdaFuncWithImage1",
                    functionname="LambdaFuncWithImage1",
                    runtime=None,
                    handler=None,
                    codeuri=".",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={
                        "DockerTag": "tag",
                        "DockerContext": os.path.join("image"),
                        "Dockerfile": "Dockerfile",
                        "SamResourceId": "LambdaFuncWithImage1",
                    },
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=IMAGE,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableImage,
                ),
            ),
            (
                "LambdaFuncWithImage2",
                Function(
                    function_id="LambdaFuncWithImage2",
                    name="LambdaFuncWithImage2",
                    functionname="LambdaFuncWithImage2",
                    runtime=None,
                    handler=None,
                    codeuri=".",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={
                        "DockerTag": "tag",
                        "DockerContext": os.path.join("image"),
                        "Dockerfile": "Dockerfile",
                        "SamResourceId": "LambdaFuncWithImage2",
                    },
                    inlinecode=None,
                    imageuri="image:tag",
                    imageconfig=None,
                    packagetype=IMAGE,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableImage,
                ),
            ),
            ("LambdaFuncWithImage3", None),  # imageuri is a ecr location, ignored
            (
                "LambdaFuncWithImage4",  # despite imageuri is ecr location, the necessary metadata is still provided, build
                Function(
                    function_id="LambdaFuncWithImage4",
                    name="LambdaFuncWithImage4",
                    functionname="LambdaFuncWithImage4",
                    runtime=None,
                    handler=None,
                    codeuri=".",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={
                        "DockerTag": "tag",
                        "DockerContext": os.path.join("image"),
                        "Dockerfile": "Dockerfile",
                        "SamResourceId": "LambdaFuncWithImage4",
                    },
                    inlinecode=None,
                    imageuri="123456789012.dkr.ecr.us-east-1.amazonaws.com/myrepo",
                    imageconfig=None,
                    packagetype=IMAGE,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableImage,
                ),
            ),
            (
                "LambdaFuncWithInlineCode",
                Function(
                    function_id="LambdaFuncWithInlineCode",
                    name="LambdaFuncWithInlineCode",
                    functionname="LambdaFuncWithInlineCode",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri=None,
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "LambdaFuncWithInlineCode"},
                    inlinecode="testcode",
                    codesign_config_arn=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.InlineCode,
                ),
            ),
            (
                "LambdaFuncWithLocalPath",
                Function(
                    function_id="LambdaFuncWithLocalPath",
                    name="LambdaFuncWithLocalPath",
                    functionname="LambdaFuncWithLocalPath",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri=os.path.join("some", "path", "to", "code"),
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "LambdaFuncWithLocalPath"},
                    inlinecode=None,
                    codesign_config_arn=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                "LambdaFuncWithFunctionNameOverride-x",
                Function(
                    function_id="LambdaFuncWithFunctionNameOverride",
                    name="LambdaFuncWithFunctionNameOverride",
                    functionname="LambdaFuncWithFunctionNameOverride-x",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri=os.path.join("some", "path", "to", "code"),
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "LambdaFuncWithFunctionNameOverride"},
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                "LambdaFuncWithCodeSignConfig",
                Function(
                    function_id="LambdaFuncWithCodeSignConfig",
                    name="LambdaFuncWithCodeSignConfig",
                    functionname="LambdaFuncWithCodeSignConfig",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri=os.path.join("some", "path", "to", "code"),
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "LambdaFuncWithCodeSignConfig"},
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn="codeSignConfigArn",
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                posixpath.join("ChildStack", "SamFunctionsInChild"),
                Function(
                    function_id="SamFunctionsInChild",
                    name="SamFunctionsInChild",
                    functionname="SamFunctionsInChildName",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri=os.path.join("child", "foo", "bar"),
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "SamFunctionsInChild"},
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="ChildStack",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                posixpath.join("ChildStack", "SamFunctionsInChildAbsPath"),
                Function(
                    function_id="SamFunctionsInChildAbsPath",
                    name="SamFunctionsInChildAbsPath",
                    functionname="SamFunctionsInChildAbsPathName",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri="/foo/bar",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "SamFunctionsInChildAbsPath"},
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="ChildStack",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                posixpath.join("ChildStack", "SamImageFunctionsInChild"),
                Function(
                    function_id="SamImageFunctionsInChild",
                    name="SamImageFunctionsInChild",
                    functionname="SamImageFunctionsInChild",
                    runtime=None,
                    handler=None,
                    codeuri="child",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={
                        "DockerTag": "tag",
                        "DockerContext": os.path.join("child", "image"),  # the path should starts with child
                        "Dockerfile": "Dockerfile",
                        "SamResourceId": "SamImageFunctionsInChild",
                    },
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=IMAGE,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="ChildStack",
                    function_build_info=FunctionBuildInfo.BuildableImage,
                ),
            ),
            (
                "LambdaFunctionCustomId-x",
                Function(
                    function_id="LambdaFunctionCustomId-x",
                    name="LambdaFuncWithCustomId",
                    functionname="LambdaFuncWithCustomId",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri="/usr/foo/bar",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "LambdaFunctionCustomId-x"},
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                "LambdaFuncWithCustomId",
                Function(
                    function_id="LambdaFunctionCustomId-x",
                    name="LambdaFuncWithCustomId",
                    functionname="LambdaFuncWithCustomId",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri="/usr/foo/bar",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={"SamResourceId": "LambdaFunctionCustomId-x"},
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                "LambdaCFKFunction-x",
                Function(
                    function_id="LambdaCFKFunction-x",
                    name="LambdaCDKFunc",
                    functionname="LambdaCDKFunc",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri="/usr/foo/bar",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={
                        "aws:cdk:path": "Stack/LambdaCFKFunction-x/Resource",
                        "aws:asset:path": "/usr/foo/bar",
                        "aws:asset:property": "Code",
                        "SamNormalized": True,
                        "SamResourceId": "LambdaCFKFunction-x",
                    },
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                "LambdaCDKFunc",
                Function(
                    function_id="LambdaCFKFunction-x",
                    name="LambdaCDKFunc",
                    functionname="LambdaCDKFunc",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri="/usr/foo/bar",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={
                        "aws:cdk:path": "Stack/LambdaCFKFunction-x/Resource",
                        "aws:asset:path": "/usr/foo/bar",
                        "aws:asset:property": "Code",
                        "SamNormalized": True,
                        "SamResourceId": "LambdaCFKFunction-x",
                    },
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                "LambdaCDKFuncInChild-x",
                Function(
                    function_id="LambdaCDKFuncInChild-x",
                    name="LambdaCDKFuncInChild",
                    functionname="LambdaCDKFuncInChild",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri="/usr/foo/bar",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={
                        "aws:cdk:path": "Stack/LambdaCDKFuncInChild-x/Resource",
                        "aws:asset:path": "/usr/foo/bar",
                        "aws:asset:property": "Code",
                        "SamNormalized": True,
                        "SamResourceId": "LambdaCDKFuncInChild-x",
                    },
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="ChildStack",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                "LambdaCDKFuncInChild",
                Function(
                    function_id="LambdaCDKFuncInChild-x",
                    name="LambdaCDKFuncInChild",
                    functionname="LambdaCDKFuncInChild",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri="/usr/foo/bar",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={
                        "aws:cdk:path": "Stack/LambdaCDKFuncInChild-x/Resource",
                        "aws:asset:path": "/usr/foo/bar",
                        "aws:asset:property": "Code",
                        "SamNormalized": True,
                        "SamResourceId": "LambdaCDKFuncInChild-x",
                    },
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="ChildStack",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                posixpath.join("ChildStack", "LambdaCDKFuncInChild-x"),
                Function(
                    function_id="LambdaCDKFuncInChild-x",
                    name="LambdaCDKFuncInChild",
                    functionname="LambdaCDKFuncInChild",
                    runtime="nodejs4.3",
                    handler="index.handler",
                    codeuri="/usr/foo/bar",
                    memory=None,
                    timeout=None,
                    environment=None,
                    rolearn=None,
                    layers=[],
                    events=None,
                    metadata={
                        "aws:cdk:path": "Stack/LambdaCDKFuncInChild-x/Resource",
                        "aws:asset:path": "/usr/foo/bar",
                        "aws:asset:property": "Code",
                        "SamNormalized": True,
                        "SamResourceId": "LambdaCDKFuncInChild-x",
                    },
                    inlinecode=None,
                    imageuri=None,
                    imageconfig=None,
                    packagetype=ZIP,
                    codesign_config_arn=None,
                    architectures=None,
                    function_url_config=None,
                    stack_path="ChildStack",
                    function_build_info=FunctionBuildInfo.BuildableZip,
                ),
            ),
            (
                # resource_Iac_id is used to build full_path, so logical id will not be used in full_path if
                # resource_iac_id exists
                posixpath.join("ChildStack", "LambdaCDKFuncInChild"),
                None,
            ),
        ]
    )
    def test_get_must_return_each_function(self, name, expected_output):
        actual = self.provider.get(name)
        self.assertEqual(actual, expected_output)

    def test_get_all_must_return_all_functions(self):
        result = {f.full_path for f in self.provider.get_all()}
        expected = {
            "SamFunctions",
            "SamFuncWithImage1",
            "SamFuncWithImage2",
            "SamFuncWithImage4",
            "SamFuncWithInlineCode",
            "SamFuncWithFunctionNameOverride",
            "SamFuncWithRuntimeManagementConfig",
            "LambdaFuncWithImage1",
            "LambdaFuncWithImage2",
            "LambdaFuncWithImage4",
            "LambdaFuncWithInlineCode",
            "LambdaFuncWithLocalPath",
            "LambdaFuncWithFunctionNameOverride",
            "LambdaFuncWithCodeSignConfig",
            "LambdaFunctionCustomId-x",
            "LambdaCFKFunction-x",
            posixpath.join("ChildStack", "SamFunctionsInChild"),
            posixpath.join("ChildStack", "SamFunctionsInChildAbsPath"),
            posixpath.join("ChildStack", "SamImageFunctionsInChild"),
            posixpath.join("ChildStack", "LambdaCDKFuncInChild-x"),
        }

        self.assertEqual(expected, result)

    def test_update_function_provider(self):
        updated_template = {
            "Resources": {
                "SamFunctions": {
                    "Type": "AWS::Serverless::Function",
                    "Properties": {
                        "FunctionName": "SamFunc1",
                        "CodeUri": "/usr/foo/bar",
                        "Runtime": "nodejs4.3",
                        "Handler": "index.handler",
                    },
                },
                "SamFuncWithInlineCode": {
                    "Type": "AWS::Serverless::Function",
                    "Properties": {
                        "FunctionName": "SamFuncWithInlineCode",
                        "InlineCode": "testcode",
                        "Runtime": "nodejs4.3",
                        "Handler": "index.handler",
                    },
                },
            }
        }
        updated_stack = Stack("", "", "template.yaml", self.parameter_overrides, updated_template)
        self.provider.update([updated_stack])
        functions = list(self.provider.get_all())
        self.assertEqual(len(functions), 2)


class TestSamFunctionProvider_init(TestCase):
    def setUp(self):
        self.parameter_overrides = {}

    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_must_extract_functions(self, get_template_mock, extract_mock):
        extract_result = {"foo": "bar"}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        provider = SamFunctionProvider([stack])

        extract_mock.assert_called_with([stack], False, False, False)
        get_template_mock.assert_called_with(template, self.parameter_overrides)
        self.assertEqual(provider.functions, extract_result)

    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_must_default_to_empty_resources(self, get_template_mock, extract_mock):
        extract_result = {"foo": "bar"}
        extract_mock.return_value = extract_result

        template = {"a": "b"}  # Template does *not* have 'Resources' key
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        provider = SamFunctionProvider([stack])

        extract_mock.assert_called_with([stack], False, False, False)  # Empty Resources value must be passed
        self.assertEqual(provider.functions, extract_result)

    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_search_layer_flag(self, get_template_mock, extract_mock):
        extract_result = {"foo": "bar"}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        provider = SamFunctionProvider([stack], locate_layer_nested=True)

        extract_mock.assert_called_with([stack], False, False, True)
        get_template_mock.assert_called_with(template, self.parameter_overrides)
        self.assertEqual(provider.functions, extract_result)


class TestSamFunctionProvider_extract_functions(TestCase):
    @patch("samcli.lib.providers.sam_function_provider.Stack.resources", new_callable=PropertyMock)
    @patch.object(SamFunctionProvider, "_convert_sam_function_resource")
    def test_must_work_for_sam_function(self, convert_mock, resources_mock):
        convertion_result = Mock()
        convertion_result.full_path = "A/B/C/Func1"
        convert_mock.return_value = convertion_result

        resources_mock.return_value = {"Func1": {"Type": "AWS::Serverless::Function", "Properties": {"a": "b"}}}
        expected = {"A/B/C/Func1": convertion_result}

        stack = make_root_stack(None)
        result = SamFunctionProvider._extract_functions([stack])
        self.assertEqual(expected, result)
        convert_mock.assert_called_with(stack, "Func1", {"a": "b"}, [], False)

    @patch("samcli.lib.providers.sam_function_provider.Stack.resources", new_callable=PropertyMock)
    @patch.object(SamFunctionProvider, "_convert_sam_function_resource")
    def test_must_work_with_no_properties(self, convert_mock, resources_mock):
        convertion_result = Mock()
        convertion_result.full_path = "A/B/C/Func1"
        convert_mock.return_value = convertion_result

        resources_mock.return_value = {
            "Func1": {
                "Type": "AWS::Serverless::Function"
                # No Properties
            }
        }

        expected = {"A/B/C/Func1": convertion_result}

        stack = make_root_stack(None)
        result = SamFunctionProvider._extract_functions([stack])
        self.assertEqual(expected, result)
        convert_mock.assert_called_with(
            stack,
            "Func1",
            {},
            [],
            False,
        )

    @patch("samcli.lib.providers.sam_function_provider.Stack.resources", new_callable=PropertyMock)
    @patch.object(SamFunctionProvider, "_convert_lambda_function_resource")
    def test_must_work_for_lambda_function(self, convert_mock, resources_mock):
        convertion_result = Mock()
        convertion_result.full_path = "A/B/C/Func1"
        convert_mock.return_value = convertion_result

        resources_mock.return_value = {"Func1": {"Type": "AWS::Lambda::Function", "Properties": {"a": "b"}}}

        expected = {"A/B/C/Func1": convertion_result}

        stack = make_root_stack(None)
        result = SamFunctionProvider._extract_functions([stack])
        self.assertEqual(expected, result)
        convert_mock.assert_called_with(stack, "Func1", {"a": "b"}, [], False)

    @patch("samcli.lib.providers.sam_function_provider.Stack.resources", new_callable=PropertyMock)
    def test_must_skip_unknown_resource(self, resources_mock):
        resources_mock.return_value = {"Func1": {"Type": "AWS::SomeOther::Function", "Properties": {"a": "b"}}}

        expected = {}

        result = SamFunctionProvider._extract_functions([make_root_stack(None)])
        self.assertEqual(expected, result)

    @patch.object(SamFunctionProvider, "_convert_lambda_function_resource")
    def test_must_work_for_multiple_functions_with_name_but_in_different_stacks(
        self,
        convert_mock,
    ):
        function_root = Mock()
        function_root.name = "Func1"
        function_root.full_path = "Func1"
        function_child = Mock()
        function_child.name = "Func1"
        function_child.full_path = "C/Func1"

        stack_root = Mock()
        stack_root.resources = {
            "Func1": {"Type": "AWS::Lambda::Function", "Properties": {"a": "b"}},
            "C": {"Type": "AWS::Serverless::Application", "Properties": {"Location": "./child.yaml"}},
        }
        stack_child = Mock()
        stack_child.resources = {
            "Func1": {"Type": "AWS::Lambda::Function", "Properties": {"a": "b"}},
        }

        convert_mock.side_effect = [function_root, function_child]

        expected = {"Func1": function_root, "C/Func1": function_child}

        result = SamFunctionProvider._extract_functions([stack_root, stack_child])
        self.assertEqual(expected, result)
        convert_mock.assert_has_calls(
            [
                call(stack_root, "Func1", {"a": "b"}, [], False),
                call(stack_child, "Func1", {"a": "b"}, [], False),
            ]
        )

    @patch("samcli.lib.providers.sam_function_provider.Stack.resources", new_callable=PropertyMock)
    @patch.object(SamFunctionProvider, "_parse_layer_info")
    @patch.object(SamFunctionProvider, "_convert_lambda_function_resource")
    def test_must_work_for_lambda_function_search_layer(self, convert_mock, parse_layer_mock, resources_mock):
        convertion_result = Mock()
        convertion_result.full_path = "A/B/C/Func1"
        convert_mock.return_value = convertion_result
        parse_layer_mock.return_value = []

        resources_mock.return_value = {
            "Func1": {"Type": "AWS::Lambda::Function", "Properties": {"a": "b"}, "Metadata": {"SamResourceId": "id"}}
        }

        expected = {"A/B/C/Func1": convertion_result}

        stack = make_root_stack(None)
        result = SamFunctionProvider._extract_functions([stack], locate_layer_nested=True)
        self.assertEqual(expected, result)
        convert_mock.assert_called_with(stack, "Func1", {"a": "b", "Metadata": {"SamResourceId": "id"}}, [], False)
        parse_layer_mock.assert_called_with(
            stack,
            [],
            False,
            ignore_code_extraction_warnings=False,
            locate_layer_nested=True,
            stacks=[stack],
            function_id="id",
        )

    @patch("samcli.lib.providers.sam_function_provider.Stack.resources", new_callable=PropertyMock)
    @patch.object(SamFunctionProvider, "_parse_layer_info")
    @patch.object(SamFunctionProvider, "_convert_sam_function_resource")
    def test_must_work_for_serverless_function_search_layer(self, convert_mock, parse_layer_mock, resources_mock):
        convertion_result = Mock()
        convertion_result.full_path = "A/B/C/Func1"
        convert_mock.return_value = convertion_result
        parse_layer_mock.return_value = []

        resources_mock.return_value = {
            "Func1": {
                "Type": "AWS::Serverless::Function",
                "Properties": {"a": "b"},
                "Metadata": {"SamResourceId": "id"},
            }
        }

        expected = {"A/B/C/Func1": convertion_result}

        stack = make_root_stack(None)
        result = SamFunctionProvider._extract_functions([stack], locate_layer_nested=True)
        self.assertEqual(expected, result)
        convert_mock.assert_called_with(stack, "Func1", {"a": "b", "Metadata": {"SamResourceId": "id"}}, [], False)
        parse_layer_mock.assert_called_with(
            stack,
            [],
            False,
            ignore_code_extraction_warnings=False,
            locate_layer_nested=True,
            stacks=[stack],
            function_id="id",
        )

    @patch("samcli.lib.providers.sam_function_provider.Stack.resources", new_callable=PropertyMock)
    @patch.object(SamFunctionProvider, "_parse_layer_info")
    @patch.object(SamFunctionProvider, "_convert_lambda_function_resource")
    def test_must_work_for_lambda_function_no_search_layer(self, convert_mock, parse_layer_mock, resources_mock):
        convertion_result = Mock()
        convertion_result.full_path = "A/B/C/Func1"
        convert_mock.return_value = convertion_result
        parse_layer_mock.return_value = []

        resources_mock.return_value = {
            "Func1": {"Type": "AWS::Lambda::Function", "Properties": {"a": "b"}, "Metadata": {"SamResourceId": "id"}}
        }

        expected = {"A/B/C/Func1": convertion_result}

        stack = make_root_stack(None)
        result = SamFunctionProvider._extract_functions([stack])
        self.assertEqual(expected, result)
        convert_mock.assert_called_with(stack, "Func1", {"a": "b", "Metadata": {"SamResourceId": "id"}}, [], False)
        parse_layer_mock.assert_called_with(
            stack,
            [],
            False,
            ignore_code_extraction_warnings=False,
            locate_layer_nested=False,
            stacks=None,
            function_id=None,
        )

    @patch("samcli.lib.providers.sam_function_provider.Stack.resources", new_callable=PropertyMock)
    @patch.object(SamFunctionProvider, "_parse_layer_info")
    @patch.object(SamFunctionProvider, "_convert_sam_function_resource")
    def test_must_work_for_serverless_function_no_search_layer(self, convert_mock, parse_layer_mock, resources_mock):
        convertion_result = Mock()
        convertion_result.full_path = "A/B/C/Func1"
        convert_mock.return_value = convertion_result
        parse_layer_mock.return_value = []

        resources_mock.return_value = {
            "Func1": {
                "Type": "AWS::Serverless::Function",
                "Properties": {"a": "b"},
                "Metadata": {"SamResourceId": "id"},
            }
        }

        expected = {"A/B/C/Func1": convertion_result}

        stack = make_root_stack(None)
        result = SamFunctionProvider._extract_functions([stack])
        self.assertEqual(expected, result)
        convert_mock.assert_called_with(stack, "Func1", {"a": "b", "Metadata": {"SamResourceId": "id"}}, [], False)
        parse_layer_mock.assert_called_with(
            stack,
            [],
            False,
            ignore_code_extraction_warnings=False,
            locate_layer_nested=False,
            stacks=None,
            function_id=None,
        )


class TestSamFunctionProvider_get_function_id(TestCase):
    def test_get_default_logical_id_no_property(self):
        resource_properties = {
            "CodeUri": "/usr/local",
            "Runtime": "myruntime",
            "MemorySize": "mymemorysize",
            "Timeout": "30",
            "Handler": "myhandler",
            "Environment": "myenvironment",
            "Role": "myrole",
            "Layers": ["Layer1", "Layer2"],
            "Architectures": [X86_64],
            "Metadata": {"aws:asset:path": "new path", "aws:asset:property": "Code"},
        }
        logical_id = "DefaultLogicalId"

        result = SamFunctionProvider._get_function_id(resource_properties=resource_properties, logical_id=logical_id)
        expected = logical_id
        self.assertEqual(expected, result)

    def test_get_default_logical_id_property_empty_str(self):
        resource_properties = {
            "CodeUri": "/usr/local",
            "Runtime": "myruntime",
            "MemorySize": "mymemorysize",
            "Timeout": "30",
            "Handler": "myhandler",
            "Environment": "myenvironment",
            "Role": "myrole",
            "Layers": ["Layer1", "Layer2"],
            "Architectures": [X86_64],
            "Metadata": {
                "aws:asset:path": "new path",
                "aws:asset:property": "Code",
                "aws:cdk:path": "",
                "SamResourceId": "",
            },
        }
        logical_id = "DefaultLogicalId"

        result = SamFunctionProvider._get_function_id(resource_properties=resource_properties, logical_id=logical_id)
        expected = logical_id
        self.assertEqual(expected, result)

    def test_get_function_id(self):
        resource_properties = {
            "CodeUri": "/usr/local",
            "Runtime": "myruntime",
            "MemorySize": "mymemorysize",
            "Timeout": "30",
            "Handler": "myhandler",
            "Environment": "myenvironment",
            "Role": "myrole",
            "Layers": ["Layer1", "Layer2"],
            "Architectures": [X86_64],
            "Metadata": {
                "aws:asset:path": "new path",
                "aws:asset:property": "Code",
                "aws:cdk:path": "stack/functionId/Resource",
                "SamResourceId": "functionId",
            },
        }
        logical_id = "DefaultLogicalId"

        result = SamFunctionProvider._get_function_id(resource_properties=resource_properties, logical_id=logical_id)
        expected = "functionId"
        self.assertEqual(expected, result)


class TestSamFunctionProvider_convert_sam_function_resource(TestCase):
    def test_must_convert_zip(self):
        name = "myname"
        properties = {
            "CodeUri": "/usr/local",
            "Runtime": "myruntime",
            "MemorySize": "mymemorysize",
            "Timeout": "30",
            "Handler": "myhandler",
            "Environment": "myenvironment",
            "Role": "myrole",
            "Layers": ["Layer1", "Layer2"],
            "Architectures": [X86_64],
        }

        expected = Function(
            function_id="myname",
            name="myname",
            functionname="myname",
            runtime="myruntime",
            memory="mymemorysize",
            timeout="30",
            handler="myhandler",
            codeuri="/usr/local",
            environment="myenvironment",
            rolearn="myrole",
            layers=["Layer1", "Layer2"],
            events=None,
            metadata=None,
            inlinecode=None,
            imageuri=None,
            imageconfig=None,
            packagetype=ZIP,
            codesign_config_arn=None,
            architectures=[X86_64],
            function_url_config=None,
            stack_path=STACK_PATH,
            function_build_info=FunctionBuildInfo.BuildableZip,
        )

        result = SamFunctionProvider._convert_sam_function_resource(STACK, name, properties, ["Layer1", "Layer2"])

        self.assertEqual(expected, result)

    def test_must_convert_image(self):
        name = "myname"
        properties = {
            "ImageUri": "helloworld:v1",
            "Runtime": "myruntime",
            "MemorySize": "mymemorysize",
            "Timeout": "30",
            "Handler": "myhandler",
            "Environment": "myenvironment",
            "Role": "myrole",
            "ImageConfig": {"WorkingDirectory": "/var/task", "Command": "/bin/bash", "EntryPoint": "echo Hello!"},
            "PackageType": IMAGE,
        }

        expected = Function(
            function_id="myname",
            name="myname",
            functionname="myname",
            runtime="myruntime",
            memory="mymemorysize",
            timeout="30",
            handler="myhandler",
            codeuri=".",
            environment="myenvironment",
            rolearn="myrole",
            layers=[],
            events=None,
            metadata=None,
            inlinecode=None,
            imageuri="helloworld:v1",
            imageconfig={"WorkingDirectory": "/var/task", "Command": "/bin/bash", "EntryPoint": "echo Hello!"},
            packagetype=IMAGE,
            codesign_config_arn=None,
            architectures=None,
            function_url_config=None,
            stack_path=STACK_PATH,
            function_build_info=FunctionBuildInfo.NonBuildableImage,
        )

        result = SamFunctionProvider._convert_sam_function_resource(STACK, name, properties, [])

        self.assertEqual(expected, result)

    def test_must_skip_non_existent_properties(self):
        name = "myname"
        properties = {"CodeUri": "/usr/local"}

        expected = Function(
            function_id="myname",
            name="myname",
            functionname="myname",
            runtime=None,
            memory=None,
            timeout=None,
            handler=None,
            codeuri="/usr/local",
            environment=None,
            rolearn=None,
            layers=[],
            events=None,
            metadata=None,
            inlinecode=None,
            imageuri=None,
            imageconfig=None,
            packagetype=ZIP,
            codesign_config_arn=None,
            architectures=None,
            function_url_config=None,
            stack_path=STACK_PATH,
            function_build_info=FunctionBuildInfo.BuildableZip,
        )

        result = SamFunctionProvider._convert_sam_function_resource(STACK, name, properties, [])

        self.assertEqual(expected, result)

    def test_must_default_missing_code_uri(self):
        name = "myname"
        properties = {"Runtime": "myruntime"}

        result = SamFunctionProvider._convert_sam_function_resource(STACK, name, properties, [])
        self.assertEqual(result.codeuri, ".")  # Default value

    def test_must_use_inlinecode(self):
        name = "myname"
        properties = {
            "InlineCode": "testcode",
            "Runtime": "myruntime",
            "MemorySize": "mymemorysize",
            "Timeout": "30",
            "Handler": "index.handler",
            "Architectures": [X86_64],
        }

        expected = Function(
            function_id="myname",
            name="myname",
            functionname="myname",
            runtime="myruntime",
            memory="mymemorysize",
            timeout="30",
            handler="index.handler",
            codeuri=None,
            environment=None,
            rolearn=None,
            layers=[],
            events=None,
            metadata=None,
            inlinecode="testcode",
            imageuri=None,
            imageconfig=None,
            packagetype=ZIP,
            codesign_config_arn=None,
            architectures=[X86_64],
            function_url_config=None,
            stack_path=STACK_PATH,
            function_build_info=FunctionBuildInfo.InlineCode,
        )

        result = SamFunctionProvider._convert_sam_function_resource(STACK, name, properties, [])

        self.assertEqual(expected, result)

    def test_must_prioritize_inlinecode(self):
        name = "myname"
        properties = {
            "CodeUri": "/usr/local",
            "InlineCode": "testcode",
            "Runtime": "myruntime",
            "MemorySize": "mymemorysize",
            "Timeout": "30",
            "Handler": "index.handler",
            "Architectures": [ARM64],
        }

        expected = Function(
            function_id="myname",
            name="myname",
            functionname="myname",
            runtime="myruntime",
            memory="mymemorysize",
            timeout="30",
            handler="index.handler",
            codeuri=None,
            environment=None,
            rolearn=None,
            layers=[],
            events=None,
            metadata=None,
            inlinecode="testcode",
            imageuri=None,
            imageconfig=None,
            packagetype=ZIP,
            codesign_config_arn=None,
            architectures=[ARM64],
            function_url_config=None,
            stack_path=STACK_PATH,
            function_build_info=FunctionBuildInfo.InlineCode,
        )

        result = SamFunctionProvider._convert_sam_function_resource(STACK, name, properties, [])

        self.assertEqual(expected, result)

    def test_must_handle_code_dict(self):
        name = "myname"
        properties = {
            "CodeUri": {
                # CodeUri is some dictionary
                "a": "b"
            }
        }

        result = SamFunctionProvider._convert_sam_function_resource(STACK, name, properties, [])
        self.assertEqual(result.codeuri, ".")  # Default value


class TestSamFunctionProvider_convert_lambda_function_resource(TestCase):
    def test_must_convert(self):
        name = "myname"
        properties = {
            "Code": {"Bucket": "bucket"},
            "Runtime": "myruntime",
            "MemorySize": "mymemorysize",
            "Timeout": "30",
            "Handler": "myhandler",
            "Environment": "myenvironment",
            "Role": "myrole",
            "Layers": ["Layer1", "Layer2"],
        }

        expected = Function(
            function_id="myname",
            name="myname",
            functionname="myname",
            runtime="myruntime",
            memory="mymemorysize",
            timeout="30",
            handler="myhandler",
            codeuri=".",
            environment="myenvironment",
            rolearn="myrole",
            layers=["Layer1", "Layer2"],
            events=None,
            metadata=None,
            inlinecode=None,
            imageuri=None,
            imageconfig=None,
            packagetype=ZIP,
            codesign_config_arn=None,
            architectures=None,
            function_url_config=None,
            stack_path=STACK_PATH,
            function_build_info=FunctionBuildInfo.BuildableZip,
        )

        result = SamFunctionProvider._convert_lambda_function_resource(STACK, name, properties, ["Layer1", "Layer2"])

        self.assertEqual(expected, result)

    def test_must_use_inlinecode(self):
        name = "myname"
        properties = {
            "Code": {"ZipFile": "testcode"},
            "Runtime": "myruntime",
            "MemorySize": "mymemorysize",
            "Timeout": "30",
            "Handler": "myhandler",
            "Environment": "myenvironment",
            "Architectures": [ARM64],
        }

        expected = Function(
            function_id="myname",
            name="myname",
            functionname="myname",
            runtime="myruntime",
            memory="mymemorysize",
            timeout="30",
            handler="myhandler",
            codeuri=None,
            environment="myenvironment",
            rolearn=None,
            layers=[],
            events=None,
            metadata=None,
            inlinecode="testcode",
            imageuri=None,
            imageconfig=None,
            packagetype=ZIP,
            codesign_config_arn=None,
            architectures=[ARM64],
            function_url_config=None,
            stack_path=STACK_PATH,
            function_build_info=FunctionBuildInfo.InlineCode,
        )

        result = SamFunctionProvider._convert_lambda_function_resource(STACK, name, properties, [])

        self.assertEqual(expected, result)

    def test_must_skip_non_existent_properties(self):
        name = "myname"
        properties = {"Code": {"Bucket": "bucket"}}

        expected = Function(
            function_id="myname",
            name="myname",
            functionname="myname",
            runtime=None,
            memory=None,
            timeout=None,
            handler=None,
            codeuri=".",
            environment=None,
            rolearn=None,
            layers=[],
            events=None,
            metadata=None,
            inlinecode=None,
            imageuri=None,
            imageconfig=None,
            packagetype=ZIP,
            codesign_config_arn=None,
            architectures=None,
            function_url_config=None,
            stack_path=STACK_PATH,
            function_build_info=FunctionBuildInfo.BuildableZip,
        )

        result = SamFunctionProvider._convert_lambda_function_resource(STACK, name, properties, [])

        self.assertEqual(expected, result)


class TestSamFunctionProvider_parse_layer_info(TestCase):
    @parameterized.expand(
        [
            ({"Function": {"Type": "AWS::Serverless::Function", "Properties": {}}}, {"Ref": "Function"}),
            ({}, {"Ref": "LayerDoesNotExist"}),
        ]
    )
    def test_raise_on_invalid_layer_resource(self, resources, layer_reference):
        with self.assertRaises(InvalidLayerReference):
            SamFunctionProvider._parse_layer_info(STACK, [layer_reference], resources)

    @parameterized.expand(
        [
            (
                {"Function": {"Type": "AWS::Serverless::Function", "Properties": {}}},
                "arn:aws:lambda:::awslayer:AmazonLinux1703",
            )
        ]
    )
    def test_raise_on_AmazonLinux1703_layer_provided(self, resources, layer_reference):
        with self.assertRaises(InvalidLayerVersionArn):
            SamFunctionProvider._parse_layer_info(STACK, [layer_reference], resources)

    def test_must_ignore_opt_in_AmazonLinux1803_layer(self):
        resources = {}

        list_of_layers = [
            "arn:aws:lambda:region:account-id:layer:layer-name:1",
            "arn:aws:lambda:::awslayer:AmazonLinux1803",
        ]
        actual = SamFunctionProvider._parse_layer_info(
            Mock(stack_path=STACK_PATH, location="template.yaml", resources=resources), list_of_layers
        )

        for actual_layer, expected_layer in zip(
            actual, [LayerVersion("arn:aws:lambda:region:account-id:layer:layer-name:1", None, stack_path=STACK_PATH)]
        ):
            self.assertEqual(actual_layer, expected_layer)

    def test_layers_created_from_template_resources(self):
        resources = {
            "Layer": {"Type": "AWS::Lambda::LayerVersion", "Properties": {"Content": "/somepath"}},
            "ServerlessLayer": {"Type": "AWS::Serverless::LayerVersion", "Properties": {"ContentUri": "/somepath"}},
        }

        list_of_layers = [
            {"Ref": "Layer"},
            {"Ref": "ServerlessLayer"},
            "arn:aws:lambda:region:account-id:layer:layer-name:1",
            {"NonRef": "Something"},
        ]
        actual = SamFunctionProvider._parse_layer_info(
            Mock(stack_path=STACK_PATH, location="template.yaml", resources=resources), list_of_layers
        )

        for actual_layer, expected_layer in zip(
            actual,
            [
                LayerVersion("Layer", "/somepath", stack_path=STACK_PATH),
                LayerVersion("ServerlessLayer", "/somepath", stack_path=STACK_PATH),
                LayerVersion("arn:aws:lambda:region:account-id:layer:layer-name:1", None, stack_path=STACK_PATH),
            ],
        ):
            self.assertEqual(actual_layer, expected_layer)

    def test_return_empty_list_on_no_layers(self):
        resources = {"Function": {"Type": "AWS::Serverless::Function", "Properties": {}}}

        actual = SamFunctionProvider._parse_layer_info(
            Mock(stack_path=STACK_PATH, location="template.yaml", resources=resources), []
        )

        self.assertEqual(actual, [])

    @patch.object(SamFunctionProvider, "_locate_layer_from_nested")
    def test_layers_with_search_layer(self, locate_layer_mock):
        layer = {"Ref", "layer"}
        func_temp = {"Properties": {"Layers": [layer]}}

        resources = {"Layer": {"Type": "AWS::Lambda::LayerVersion", "Properties": {"Content": "/somepath"}}}

        list_of_layers = [{"Ref": "Layer"}]

        locate_layer_mock.return_value = LayerVersion("Layer", "/somepath", stack_path=STACK_PATH)

        mock_stack = Mock(
            stack_path=STACK_PATH,
            location="template.yaml",
            resources=resources,
            template_dict={"Resources": {"function_id": func_temp}},
        )

        expected_layer = [locate_layer_mock.return_value]

        actual = SamFunctionProvider._parse_layer_info(
            mock_stack, list_of_layers, stacks=[mock_stack], function_id="function_id", locate_layer_nested=True
        )

        locate_layer_mock.assert_called_with(mock_stack, [mock_stack], layer, False, False)

        self.assertEqual(actual, expected_layer)


class TestSamFunctionProvider_get(TestCase):
    def test_raise_on_invalid_name(self):
        provider = SamFunctionProvider([])

        with self.assertRaises(ValueError):
            provider.get(None)

    def test_must_return_function_value(self):
        provider = SamFunctionProvider([])
        # Cheat a bit here by setting the value of this property directly
        function = Function(
            function_id="not-value",
            name="not-value",
            functionname="value",
            runtime=None,
            handler=None,
            codeuri=None,
            memory=None,
            timeout=None,
            environment=None,
            rolearn=None,
            layers=[],
            events=None,
            metadata=None,
            inlinecode=None,
            imageuri=None,
            imageconfig=None,
            packagetype=None,
            codesign_config_arn=None,
            architectures=None,
            function_url_config=None,
            stack_path=STACK_PATH,
            function_build_info=Mock(),
        )
        provider.functions = {"func1": function}

        self.assertEqual(function, provider.get("value"))

    def test_found_by_different_ids(self):
        provider = SamFunctionProvider([])
        # Cheat a bit here by setting the value of this property directly
        function1 = Function(
            function_id="not-value",
            name="not-value",
            functionname="not-value",
            runtime=None,
            handler=None,
            codeuri=None,
            memory=None,
            timeout=None,
            environment=None,
            rolearn=None,
            layers=[],
            events=None,
            metadata=None,
            inlinecode=None,
            imageuri=None,
            imageconfig=None,
            packagetype=None,
            codesign_config_arn=None,
            architectures=None,
            function_url_config=None,
            stack_path=posixpath.join("this_is", "stack_path_C"),
            function_build_info=Mock(),
        )

        function2 = Function(
            function_id="expected_function_id",
            name="not-value",
            functionname="not-value",
            runtime=None,
            handler=None,
            codeuri=None,
            memory=None,
            timeout=None,
            environment=None,
            rolearn=None,
            layers=[],
            events=None,
            metadata=None,
            inlinecode=None,
            imageuri=None,
            imageconfig=None,
            packagetype=None,
            codesign_config_arn=None,
            architectures=None,
            function_url_config=None,
            stack_path=posixpath.join("this_is", "stack_path_B"),
            function_build_info=Mock(),
        )

        function3 = Function(
            function_id="not-value",
            name="expected_logical_id",
            functionname="not-value",
            runtime=None,
            handler=None,
            codeuri=None,
            memory=None,
            timeout=None,
            environment=None,
            rolearn=None,
            layers=[],
            events=None,
            metadata=None,
            inlinecode=None,
            imageuri=None,
            imageconfig=None,
            packagetype=None,
            codesign_config_arn=None,
            architectures=None,
            function_url_config=None,
            stack_path=posixpath.join("this_is", "stack_path_A"),
            function_build_info=Mock(),
        )

        function4 = Function(
            function_id="not-value",
            name="not-value",
            functionname="expected_function_name",
            runtime=None,
            handler=None,
            codeuri=None,
            memory=None,
            timeout=None,
            environment=None,
            rolearn=None,
            layers=[],
            events=None,
            metadata=None,
            inlinecode=None,
            imageuri=None,
            imageconfig=None,
            packagetype=None,
            codesign_config_arn=None,
            architectures=None,
            function_url_config=None,
            stack_path=posixpath.join("this_is", "stack_path_D"),
            function_build_info=Mock(),
        )
        provider.functions = {"func1": function1, "func2": function2, "func3": function3, "func4": function4}

        self.assertIsNone(provider.get("value"))
        self.assertEqual(function1, provider.get("func1"))
        self.assertEqual(function2, provider.get("expected_function_id"))
        self.assertEqual(function3, provider.get("expected_logical_id"))
        self.assertEqual(function4, provider.get("expected_function_name"))
        # The returned function is the full_path sorted one if multiple ones are matched
        self.assertEqual(function3, provider.get("not-value"))

    def test_return_none_if_function_not_found(self):
        provider = SamFunctionProvider([])

        self.assertIsNone(provider.get("somefunc"), "Must return None when Function is not found")


class TestSamFunctionProvider_get_all(TestCase):
    def test_must_work_with_no_functions(self):
        provider = SamFunctionProvider([])

        result = [f for f in provider.get_all()]
        self.assertEqual(result, [])


class TestRefreshableSamFunctionProvider(TestCase):
    def setUp(self):
        self.parameter_overrides = {}
        self.global_parameter_overrides = {}
        self.file_observer = Mock()
        self.file_observer.start = Mock()
        self.file_observer.watch = Mock()
        self.file_observer.unwatch = Mock()
        self.file_observer.stop = Mock()

    @patch("samcli.lib.providers.sam_function_provider.FileObserver")
    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_init_must_extract_functions_and_stacks_got_observed(
        self, get_template_mock, extract_mock, FileObserverMock
    ):
        FileObserverMock.return_value = self.file_observer

        extract_result = {"foo": "bar"}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        template2 = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, template2)
        provider = RefreshableSamFunctionProvider(
            [stack, stack2], self.parameter_overrides, self.global_parameter_overrides
        )

        extract_mock.assert_called_with([stack, stack2], False, False, False)
        get_template_mock.assert_called_with(template, self.parameter_overrides)
        self.assertEqual(provider.functions, extract_result)

        FileObserverMock.assert_called_with(provider._set_templates_changed)
        self.file_observer.start.assert_called_with()
        self.file_observer.watch.assert_has_calls([call("template.yaml"), call("child/template.yaml")])

        self.assertEqual(provider.parent_templates_paths, ["template.yaml"])
        self.assertEqual(provider.is_changed, False)

    @patch("samcli.lib.providers.sam_function_provider.FileObserver")
    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_reload_flag_set_to_true_incase_any_template_got_changed(
        self, get_template_mock, extract_mock, FileObserverMock
    ):
        FileObserverMock.return_value = self.file_observer

        extract_result = {"foo": "bar"}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        template2 = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, template2)
        provider = RefreshableSamFunctionProvider(
            [stack, stack2], self.parameter_overrides, self.global_parameter_overrides
        )
        provider._set_templates_changed(["child/template.yaml"])

        self.assertTrue(provider.is_changed)
        self.file_observer.unwatch.assert_has_calls([call("template.yaml"), call("child/template.yaml")])

    @patch("samcli.lib.providers.sam_function_provider.SamLocalStackProvider.get_stacks")
    @patch("samcli.lib.providers.sam_function_provider.FileObserver")
    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_reload_incase_if_change_flag_is_true_and_stacks_mathod_called(
        self, get_template_mock, extract_mock, FileObserverMock, get_stacks_mock
    ):
        FileObserverMock.return_value = self.file_observer

        extract_result = {"foo": "bar"}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        template2 = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, template2)
        provider = RefreshableSamFunctionProvider(
            [stack, stack2], self.parameter_overrides, self.global_parameter_overrides
        )
        provider._set_templates_changed(["child/template.yaml"])
        updated_template = {"Resources": {"a": "b", "c": "d"}}
        updated_template2 = {"Resources": {"a": "b"}}
        updated_template3 = {"Resources": {"a": "b"}}
        stack = make_root_stack(updated_template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, updated_template2)
        stack3 = Stack("", "childStack2", "child/child/template.yaml", self.parameter_overrides, updated_template3)
        get_stacks_mock.return_value = [stack, stack2, stack3], None

        updated_extract_result = {"foo": "bar", "foo2": "bar2"}
        extract_mock.return_value = updated_extract_result

        self.file_observer.watch.reset_mock()
        stacks = provider.stacks
        self.assertEqual(stacks, [stack, stack2, stack3])
        self.assertFalse(provider.is_changed)

        self.file_observer.watch.assert_has_calls(
            [call("template.yaml"), call("child/template.yaml"), call("child/child/template.yaml")]
        )

        functions = []
        for func in provider.get_all():
            functions.append(func)
        self.assertEqual(functions, ["bar", "bar2"])

    @patch("samcli.lib.providers.sam_function_provider.SamLocalStackProvider.get_stacks")
    @patch("samcli.lib.providers.sam_function_provider.FileObserver")
    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_reload_incase_if_change_flag_is_true_and_get_all_mathod_called(
        self, get_template_mock, extract_mock, FileObserverMock, get_stacks_mock
    ):
        FileObserverMock.return_value = self.file_observer

        extract_result = {"foo": "bar"}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        template2 = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, template2)
        provider = RefreshableSamFunctionProvider(
            [stack, stack2], self.parameter_overrides, self.global_parameter_overrides
        )
        provider._set_templates_changed(["child/template.yaml"])
        updated_template = {"Resources": {"a": "b", "c": "d"}}
        updated_template2 = {"Resources": {"a": "b"}}
        updated_template3 = {"Resources": {"a": "b"}}
        stack = make_root_stack(updated_template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, updated_template2)
        stack3 = Stack("", "childStack2", "child/child/template.yaml", self.parameter_overrides, updated_template3)
        get_stacks_mock.return_value = [stack, stack2, stack3], None

        updated_extract_result = {"foo": "bar", "foo2": "bar2"}
        extract_mock.return_value = updated_extract_result

        self.file_observer.watch.reset_mock()

        functions = []
        for func in provider.get_all():
            functions.append(func)
        self.assertEqual(functions, ["bar", "bar2"])
        self.assertFalse(provider.is_changed)

        self.file_observer.watch.assert_has_calls(
            [call("template.yaml"), call("child/template.yaml"), call("child/child/template.yaml")]
        )

    @patch("samcli.lib.providers.sam_function_provider.SamLocalStackProvider.get_stacks")
    @patch("samcli.lib.providers.sam_function_provider.FileObserver")
    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_reload_incase_if_change_flag_is_true_and_get_mathod_called(
        self, get_template_mock, extract_mock, FileObserverMock, get_stacks_mock
    ):
        FileObserverMock.return_value = self.file_observer

        extract_result = {"foo": "bar"}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        template2 = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, template2)
        provider = RefreshableSamFunctionProvider(
            [stack, stack2], self.parameter_overrides, self.global_parameter_overrides
        )
        provider._set_templates_changed(["child/template.yaml"])
        updated_template = {"Resources": {"a": "b", "c": "d"}}
        updated_template2 = {"Resources": {"a": "b"}}
        updated_template3 = {"Resources": {"a": "b"}}
        stack = make_root_stack(updated_template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, updated_template2)
        stack3 = Stack("", "childStack2", "child/child/template.yaml", self.parameter_overrides, updated_template3)
        get_stacks_mock.return_value = [stack, stack2, stack3], None

        func1 = Mock()
        func2 = Mock()
        updated_extract_result = {"foo": func1, "foo2": func2}
        extract_mock.return_value = updated_extract_result

        self.file_observer.watch.reset_mock()
        self.assertEqual(provider.get("foo2"), func2)
        self.assertFalse(provider.is_changed)

        self.file_observer.watch.assert_has_calls(
            [call("template.yaml"), call("child/template.yaml"), call("child/child/template.yaml")]
        )

    @patch("samcli.lib.providers.sam_function_provider.SamLocalStackProvider.get_stacks")
    @patch("samcli.lib.providers.sam_function_provider.FileObserver")
    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_reload_incase_if_change_flag_is_true_and_get_resources_by_stack_path_mathod_called(
        self, get_template_mock, extract_mock, FileObserverMock, get_stacks_mock
    ):
        FileObserverMock.return_value = self.file_observer

        extract_result = {"foo": "bar"}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        template2 = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, template2)
        provider = RefreshableSamFunctionProvider(
            [stack, stack2], self.parameter_overrides, self.global_parameter_overrides
        )
        provider._set_templates_changed(["child/template.yaml"])
        updated_template = {"Resources": {"a": "b", "c": "d"}}
        updated_template2 = {"Resources": {"a": "b"}}
        updated_template3 = {"Resources": {"c": "d"}}
        get_template_mock.return_value = updated_template
        stack = make_root_stack(updated_template, self.parameter_overrides)
        get_template_mock.return_value = updated_template2
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, updated_template2)
        get_template_mock.return_value = updated_template3
        stack3 = Stack(
            "childStack", "childStack2", "child/child/template.yaml", self.parameter_overrides, updated_template3
        )
        get_stacks_mock.return_value = [stack, stack2, stack3], None

        func1 = Mock()
        func2 = Mock()
        updated_extract_result = {"foo": func1, "foo2": func2}
        extract_mock.return_value = updated_extract_result

        self.file_observer.watch.reset_mock()

        self.assertEqual(provider.get_resources_by_stack_path("childStack/childStack2"), {"c": "d"})
        self.assertFalse(provider.is_changed)

        self.file_observer.watch.assert_has_calls(
            [call("template.yaml"), call("child/template.yaml"), call("child/child/template.yaml")]
        )

    @patch("samcli.lib.providers.sam_function_provider.FileObserver")
    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_does_not_reload_incase_if_change_flag_is_false_and_stacks_mathod_called(
        self, get_template_mock, extract_mock, FileObserverMock
    ):
        FileObserverMock.return_value = self.file_observer

        extract_result = {"foo": "bar"}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        template2 = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, template2)
        provider = RefreshableSamFunctionProvider(
            [stack, stack2], self.parameter_overrides, self.global_parameter_overrides
        )

        self.file_observer.watch.reset_mock()
        stacks = provider.stacks
        self.assertEqual(stacks, [stack, stack2])
        self.assertFalse(provider.is_changed)

        self.file_observer.watch.assert_not_called()

        functions = []
        for func in provider.get_all():
            functions.append(func)
        self.assertEqual(functions, ["bar"])

    @patch("samcli.lib.providers.sam_function_provider.FileObserver")
    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_does_not_reload_incase_if_change_flag_is_false_and_get_all_mathod_called(
        self, get_template_mock, extract_mock, FileObserverMock
    ):
        FileObserverMock.return_value = self.file_observer

        extract_result = {"foo": "bar"}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        template2 = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, template2)
        provider = RefreshableSamFunctionProvider(
            [stack, stack2], self.parameter_overrides, self.global_parameter_overrides
        )

        self.file_observer.watch.reset_mock()
        functions = []
        for func in provider.get_all():
            functions.append(func)
        self.assertEqual(functions, ["bar"])
        self.assertFalse(provider.is_changed)
        self.file_observer.watch.assert_not_called()

    @patch("samcli.lib.providers.sam_function_provider.FileObserver")
    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_does_not_reload_incase_if_change_flag_is_false_and_get_mathod_called(
        self, get_template_mock, extract_mock, FileObserverMock
    ):
        FileObserverMock.return_value = self.file_observer

        func = Mock()
        extract_result = {"foo": func}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        template2 = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, template2)
        provider = RefreshableSamFunctionProvider(
            [stack, stack2], self.parameter_overrides, self.global_parameter_overrides
        )

        self.file_observer.watch.reset_mock()
        self.assertEqual(provider.get("foo"), func)
        self.assertIsNone(provider.get("foo2"))
        self.assertFalse(provider.is_changed)
        self.file_observer.watch.assert_not_called()

    @patch("samcli.lib.providers.sam_function_provider.SamLocalStackProvider.get_stacks")
    @patch("samcli.lib.providers.sam_function_provider.FileObserver")
    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_does_not_reload_incase_if_change_flag_is_false_and_get_resources_by_stack_path_mathod_called(
        self, get_template_mock, extract_mock, FileObserverMock, get_stacks_mock
    ):
        FileObserverMock.return_value = self.file_observer

        func = Mock()
        extract_result = {"foo": func}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        template2 = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, template2)
        provider = RefreshableSamFunctionProvider(
            [stack, stack2], self.parameter_overrides, self.global_parameter_overrides
        )

        self.file_observer.watch.reset_mock()
        self.assertEqual(provider.get_resources_by_stack_path("childStack"), {"a": "b"})
        with self.assertRaises(RuntimeError):
            provider.get_resources_by_stack_path("childStack/childStack2")
        self.assertFalse(provider.is_changed)
        self.file_observer.watch.assert_not_called()

    @patch("samcli.lib.providers.sam_function_provider.FileObserver")
    @patch.object(SamFunctionProvider, "_extract_functions")
    @patch("samcli.lib.providers.provider.SamBaseProvider.get_template")
    def test_provider_stop_will_stop_all_observers(self, get_template_mock, extract_mock, FileObserverMock):
        FileObserverMock.return_value = self.file_observer

        extract_result = {"foo": "bar"}
        extract_mock.return_value = extract_result

        template = {"Resources": {"a": "b"}}
        template2 = {"Resources": {"a": "b"}}
        get_template_mock.return_value = template
        stack = make_root_stack(template, self.parameter_overrides)
        stack2 = Stack("", "childStack", "child/template.yaml", self.parameter_overrides, template2)
        provider = RefreshableSamFunctionProvider(
            [stack, stack2], self.parameter_overrides, self.global_parameter_overrides
        )
        provider.stop_observer()

        self.file_observer.stop.assert_called_once()


class TestSamFunctionProvider_search_layer(TestCase):
    root_stack_template = {
        "Resources": {
            "LayerStack": {
                "Type": "AWS::Serverless::Application",
                "Properties": {"Location": "child_layer/template.yaml"},
            },
            "FunctionStack": {
                "Type": "AWS::Serverless::Application",
                "Properties": {
                    "Location": "child_function/template.yaml",
                    "Parameters": {"Layer": {"Fn::GetAtt": ["LayerStack", "Outputs.LayerName"]}},
                },
            },
        }
    }

    layer_stack_template = {
        "Resources": {
            "SamLayer": {
                "Type": "AWS::Serverless::LayerVersion",
                "Properties": {
                    "LayerName": "SamLayer",
                    "Description": "Sam",
                    "ContentUri": "layer/",
                    "CompatibleRuntimes": ["python3.7"],
                },
            }
        },
        "Outputs": {"LayerName": {"Description": "The name of the layer", "Value": {"Ref": "SamLayer"}}},
    }

    function_stack_template = {
        "Parameters": {"Layer": {"Type": "String"}},
        "Resources": {
            "SamFunctions": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "FunctionName": "SamFunc1",
                    "CodeUri": "/usr/foo/bar",
                    "Runtime": "nodejs4.3",
                    "Handler": "index.handler",
                    "Layers": {"Ref": "Layer"},
                },
            }
        },
    }

    @patch.object(SamFunctionProvider, "_locate_layer_from_ref")
    def test_search_layer_with_layer_arn(self, locate_layer_ref_mock):
        stack = Stack("", "", "template.yaml", None, self.root_stack_template)
        layer_version = SamFunctionProvider._locate_layer_from_nested(stack, [stack], "layer_arn")
        self.assertIsNone(layer_version)
        locate_layer_ref_mock.assert_not_called()

    @patch.object(SamFunctionProvider, "_locate_layer_from_ref")
    def test_search_layer_in_outputs(self, locate_layer_ref_mock):
        stack = Stack("", "", "template.yaml", None, self.layer_stack_template)
        SamFunctionProvider._locate_layer_from_nested(stack, [stack], "LayerName")
        locate_layer_ref_mock.assert_called_with(stack, {"Ref": "SamLayer"}, False, False)

    @patch.object(SamFunctionProvider, "_locate_layer_from_ref")
    def test_search_layer_ref_in_current_stack(self, locate_layer_ref_mock):
        stack = Stack("", "", "template.yaml", None, self.layer_stack_template)
        SamFunctionProvider._locate_layer_from_nested(stack, [stack], {"Ref": "SamLayer"})
        locate_layer_ref_mock.assert_called_with(stack, {"Ref": "SamLayer"}, False, False)

    @patch.object(SamFunctionProvider, "_locate_layer_from_ref")
    def test_search_layer_fn_get(self, locate_layer_ref_mock):
        root_stack = Stack("", "root", "template.yaml", None, self.root_stack_template)
        child_layer_stack = Stack("root", "LayerStack", "template.yaml", None, self.layer_stack_template)
        child_function_stack = Stack("root", "FunctionStack", "template.yaml", None, self.function_stack_template)
        SamFunctionProvider._locate_layer_from_nested(
            child_function_stack, [root_stack, child_layer_stack, child_function_stack], {"Ref": "Layer"}
        )
        locate_layer_ref_mock.assert_called_with(child_layer_stack, {"Ref": "SamLayer"}, False, False)

    @patch.object(SamFunctionProvider, "_locate_layer_from_ref")
    def test_search_layer_with_sub(self, locate_layer_ref_mock):
        root_stack = Stack("", "root", "template.yaml", None, self.root_stack_template)
        SamFunctionProvider._locate_layer_from_nested(
            root_stack,
            [root_stack],
            {"Fn::Sub": "arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:18"},
        )
        locate_layer_ref_mock.assert_called_with(
            root_stack,
            {"Fn::Sub": "arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:18"},
            False,
            False,
        )

    def test_validate_layer_get_attr_format(self):
        valid_layer = {"Fn::GetAtt": ["LayerStackName", "Outputs.LayerName"]}
        self.assertTrue(SamFunctionProvider._validate_layer_get_attr_format(valid_layer))

        invalid_layer_not_list = {"Fn::GetAtt": ""}
        self.assertFalse(SamFunctionProvider._validate_layer_get_attr_format(invalid_layer_not_list))

        invalid_layer_empty_list = {"Fn::GetAtt": []}
        self.assertFalse(SamFunctionProvider._validate_layer_get_attr_format(invalid_layer_empty_list))

        invalid_layer_str_format = {"Fn::GetAtt": ["LayerStackName", ""]}
        self.assertFalse(SamFunctionProvider._validate_layer_get_attr_format(invalid_layer_str_format))

        invalid_layer_str_format = {"Fn::GetAtt": ["LayerStackName", "Outputs.invalid.format"]}
        self.assertFalse(SamFunctionProvider._validate_layer_get_attr_format(invalid_layer_str_format))