Path: blob/develop/tests/unit/customizations/cloudformation/test_artifact_exporter.py
1569 views
import botocore.session1import tempfile2import os3import string4import random5import zipfile67import pytest89from contextlib import contextmanager, closing10from botocore.stub import Stubber11from awscli.testutils import mock, unittest, FileCreator12from awscli.customizations.cloudformation import exceptions13from awscli.customizations.cloudformation.artifact_exporter \14import is_s3_url, parse_s3_url, is_local_file, is_local_folder, \15upload_local_artifacts, zip_folder, make_abs_path, make_zip, \16Template, Resource, ResourceWithS3UrlDict, ServerlessApiResource, \17ServerlessFunctionResource, GraphQLSchemaResource, \18LambdaFunctionResource, ApiGatewayRestApiResource, \19ElasticBeanstalkApplicationVersion, CloudFormationStackResource, \20ServerlessApplicationResource, LambdaLayerVersionResource, \21copy_to_temp_dir, include_transform_export_handler, GLOBAL_EXPORT_DICT, \22ServerlessLayerVersionResource, ServerlessRepoApplicationLicense, \23ServerlessRepoApplicationReadme, \24AppSyncResolverRequestTemplateResource, \25AppSyncResolverResponseTemplateResource, \26AppSyncFunctionConfigurationRequestTemplateResource, \27AppSyncFunctionConfigurationResponseTemplateResource, \28GlueJobCommandScriptLocationResource, \29StepFunctionsStateMachineDefinitionResource, \30ServerlessStateMachineDefinitionResource, \31CodeCommitRepositoryS3Resource323334VALID_CASES = [35"s3://foo/bar",36"s3://foo/bar/baz/cat/dog",37"s3://foo/bar?versionId=abc",38"s3://foo/bar/baz?versionId=abc&versionId=123",39"s3://foo/bar/baz?versionId=abc",40"s3://www.amazon.com/foo/bar",41"s3://my-new-bucket/foo/bar?a=1&a=2&a=3&b=1",42]4344INVALID_CASES = [45# For purposes of exporter, we need S3 URLs to point to an object46# and not a bucket47"s3://foo",4849# two versionIds is invalid50"https://s3-eu-west-1.amazonaws.com/bucket/key",51"https://www.amazon.com"52]535455@pytest.mark.parametrize(56"url",57VALID_CASES58)59def test_is_valid_s3_url(url):60assert is_s3_url(url), f"{url} should be valid"616263@pytest.mark.parametrize(64"url",65INVALID_CASES66)67def test_is_invalid_s3_url(url):68assert not is_s3_url(url), f"{url} should be invalid"697071UPLOADED_S3_URL = "s3://foo/bar?versionId=baz"7273RESOURCE_EXPORT_TEST_CASES = [74{75"class": ServerlessFunctionResource,76"expected_result": UPLOADED_S3_URL77},7879{80"class": ServerlessApiResource,81"expected_result": UPLOADED_S3_URL82},8384{85"class": GraphQLSchemaResource,86"expected_result": UPLOADED_S3_URL87},8889{90"class": AppSyncResolverRequestTemplateResource,91"expected_result": UPLOADED_S3_URL92},9394{95"class": AppSyncResolverResponseTemplateResource,96"expected_result": UPLOADED_S3_URL97},9899{100"class": AppSyncFunctionConfigurationRequestTemplateResource,101"expected_result": UPLOADED_S3_URL102},103104{105"class": AppSyncFunctionConfigurationResponseTemplateResource,106"expected_result": UPLOADED_S3_URL107},108109{110"class": ApiGatewayRestApiResource,111"expected_result": {112"Bucket": "foo", "Key": "bar", "Version": "baz"113}114},115116{117"class": LambdaFunctionResource,118"expected_result": {119"S3Bucket": "foo", "S3Key": "bar", "S3ObjectVersion": "baz"120}121},122123{124"class": ElasticBeanstalkApplicationVersion,125"expected_result": {126"S3Bucket": "foo", "S3Key": "bar"127}128},129{130"class": LambdaLayerVersionResource,131"expected_result": {132"S3Bucket": "foo", "S3Key": "bar", "S3ObjectVersion": "baz"133}134},135{136"class": ServerlessLayerVersionResource,137"expected_result": UPLOADED_S3_URL138},139{140"class": ServerlessRepoApplicationReadme,141"expected_result": UPLOADED_S3_URL142},143{144"class": ServerlessRepoApplicationLicense,145"expected_result": UPLOADED_S3_URL146},147{148"class": ServerlessRepoApplicationLicense,149"expected_result": UPLOADED_S3_URL150},151{152"class": GlueJobCommandScriptLocationResource,153"expected_result": {154"ScriptLocation": UPLOADED_S3_URL155}156},157{158"class": StepFunctionsStateMachineDefinitionResource,159"expected_result": {160"Bucket": "foo", "Key": "bar", "Version": "baz"161}162},163{164"class": ServerlessStateMachineDefinitionResource,165"expected_result": {166"Bucket": "foo", "Key": "bar", "Version": "baz"167}168},169]170171172@pytest.mark.parametrize(173"test",174RESOURCE_EXPORT_TEST_CASES175)176def test_all_resources_export(test):177mock_path = (178"awscli.customizations.cloudformation.artifact_exporter.upload_local_artifacts"179)180with mock.patch(mock_path) as upload_local_artifacts_mock:181_helper_verify_export_resources(182test["class"], upload_local_artifacts_mock, test["expected_result"]183)184185186def _helper_verify_export_resources(187test_class, upload_local_artifacts_mock, expected_result188):189190s3_uploader_mock = mock.Mock()191upload_local_artifacts_mock.reset_mock()192193resource_id = "id"194parent_dir = "dir"195196if '.' in test_class.PROPERTY_NAME:197reversed_property_names = test_class.PROPERTY_NAME.split('.')198reversed_property_names.reverse()199property_dict = {200reversed_property_names[0]: "foo"201}202for sub_property_name in reversed_property_names[1:]:203property_dict = {204sub_property_name: property_dict205}206resource_dict = property_dict207else:208resource_dict = {209test_class.PROPERTY_NAME: "foo"210}211212upload_local_artifacts_mock.return_value = UPLOADED_S3_URL213resource_obj = test_class(s3_uploader_mock)214resource_obj.export(resource_id, resource_dict, parent_dir)215216upload_local_artifacts_mock.assert_called_once_with(217resource_id, resource_dict, test_class.PROPERTY_NAME,218parent_dir, s3_uploader_mock219)220if '.' in test_class.PROPERTY_NAME:221top_level_property_name = test_class.PROPERTY_NAME.split('.')[0]222result = resource_dict[top_level_property_name]223else:224result = resource_dict[test_class.PROPERTY_NAME]225226assert result == expected_result227228229class TestArtifactExporter(unittest.TestCase):230231def setUp(self):232self.s3_uploader_mock = mock.Mock()233self.s3_uploader_mock.s3.meta.endpoint_url = "https://s3.some-valid-region.amazonaws.com"234235def test_parse_s3_url(self):236237valid = [238{239"url": "s3://foo/bar",240"result": {"Bucket": "foo", "Key": "bar"}241},242{243"url": "s3://foo/bar/cat/dog",244"result": {"Bucket": "foo", "Key": "bar/cat/dog"}245},246{247"url": "s3://foo/bar/baz?versionId=abc¶m1=val1¶m2=val2",248"result": {"Bucket": "foo", "Key": "bar/baz", "VersionId": "abc"}249},250{251# VersionId is not returned if there are more than one versionId252# keys in query parameter253"url": "s3://foo/bar/baz?versionId=abc&versionId=123",254"result": {"Bucket": "foo", "Key": "bar/baz"}255}256]257258invalid = [259260# For purposes of exporter, we need S3 URLs to point to an object261# and not a bucket262"s3://foo",263264# two versionIds is invalid265"https://s3-eu-west-1.amazonaws.com/bucket/key",266"https://www.amazon.com"267]268269for config in valid:270result = parse_s3_url(config["url"],271bucket_name_property="Bucket",272object_key_property="Key",273version_property="VersionId")274275self.assertEqual(result, config["result"])276277for url in invalid:278with self.assertRaises(ValueError):279parse_s3_url(url)280281def test_is_local_file(self):282with tempfile.NamedTemporaryFile() as handle:283self.assertTrue(is_local_file(handle.name))284self.assertFalse(is_local_folder(handle.name))285286def test_is_local_folder(self):287with self.make_temp_dir() as filename:288self.assertTrue(is_local_folder(filename))289self.assertFalse(is_local_file(filename))290291@mock.patch("awscli.customizations.cloudformation.artifact_exporter.zip_and_upload")292def test_upload_local_artifacts_local_file(self, zip_and_upload_mock):293# Case 1: Artifact path is a relative path294# Verifies that we package local artifacts appropriately295property_name = "property"296resource_id = "resource_id"297expected_s3_url = "s3://foo/bar?versionId=baz"298299self.s3_uploader_mock.upload_with_dedup.return_value = expected_s3_url300301with tempfile.NamedTemporaryFile() as handle:302# Artifact is a file in the temporary directory303artifact_path = handle.name304parent_dir = tempfile.gettempdir()305306resource_dict = {property_name: artifact_path}307result = upload_local_artifacts(resource_id,308resource_dict,309property_name,310parent_dir,311self.s3_uploader_mock)312self.assertEqual(result, expected_s3_url)313314# Internally the method would convert relative paths to absolute315# path, with respect to the parent directory316absolute_artifact_path = make_abs_path(parent_dir, artifact_path)317self.s3_uploader_mock.upload_with_dedup.assert_called_with(absolute_artifact_path)318319zip_and_upload_mock.assert_not_called()320321@mock.patch("awscli.customizations.cloudformation.artifact_exporter.zip_and_upload")322def test_upload_local_artifacts_local_file_abs_path(self, zip_and_upload_mock):323# Case 2: Artifact path is an absolute path324# Verifies that we package local artifacts appropriately325property_name = "property"326resource_id = "resource_id"327expected_s3_url = "s3://foo/bar?versionId=baz"328329self.s3_uploader_mock.upload_with_dedup.return_value = expected_s3_url330331with tempfile.NamedTemporaryFile() as handle:332parent_dir = tempfile.gettempdir()333artifact_path = make_abs_path(parent_dir, handle.name)334335resource_dict = {property_name: artifact_path}336result = upload_local_artifacts(resource_id,337resource_dict,338property_name,339parent_dir,340self.s3_uploader_mock)341self.assertEqual(result, expected_s3_url)342343self.s3_uploader_mock.upload_with_dedup.assert_called_with(artifact_path)344zip_and_upload_mock.assert_not_called()345346@mock.patch("awscli.customizations.cloudformation.artifact_exporter.zip_and_upload")347def test_upload_local_artifacts_local_folder(self, zip_and_upload_mock):348property_name = "property"349resource_id = "resource_id"350expected_s3_url = "s3://foo/bar?versionId=baz"351352zip_and_upload_mock.return_value = expected_s3_url353354# Artifact path is a Directory355with self.make_temp_dir() as artifact_path:356# Artifact is a file in the temporary directory357parent_dir = tempfile.gettempdir()358resource_dict = {property_name: artifact_path}359360result = upload_local_artifacts(resource_id,361resource_dict,362property_name,363parent_dir,364mock.Mock())365self.assertEqual(result, expected_s3_url)366367absolute_artifact_path = make_abs_path(parent_dir, artifact_path)368369zip_and_upload_mock.assert_called_once_with(absolute_artifact_path,370mock.ANY)371372@mock.patch("awscli.customizations.cloudformation.artifact_exporter.zip_and_upload")373def test_upload_local_artifacts_no_path(self, zip_and_upload_mock):374property_name = "property"375resource_id = "resource_id"376expected_s3_url = "s3://foo/bar?versionId=baz"377378zip_and_upload_mock.return_value = expected_s3_url379380# If you don't specify a path, we will default to Current Working Dir381resource_dict = {}382parent_dir = tempfile.gettempdir()383384result = upload_local_artifacts(resource_id,385resource_dict,386property_name,387parent_dir,388self.s3_uploader_mock)389self.assertEqual(result, expected_s3_url)390391zip_and_upload_mock.assert_called_once_with(parent_dir, mock.ANY)392self.s3_uploader_mock.upload_with_dedup.assert_not_called()393394@mock.patch("awscli.customizations.cloudformation.artifact_exporter.zip_and_upload")395def test_upload_local_artifacts_s3_url(self,396zip_and_upload_mock):397property_name = "property"398resource_id = "resource_id"399object_s3_url = "s3://foo/bar?versionId=baz"400401# If URL is already S3 URL, this will be returned without zip/upload402resource_dict = {property_name: object_s3_url}403parent_dir = tempfile.gettempdir()404405result = upload_local_artifacts(resource_id,406resource_dict,407property_name,408parent_dir,409self.s3_uploader_mock)410self.assertEqual(result, object_s3_url)411412zip_and_upload_mock.assert_not_called()413self.s3_uploader_mock.upload_with_dedup.assert_not_called()414415@mock.patch("awscli.customizations.cloudformation.artifact_exporter.zip_and_upload")416def test_upload_local_artifacts_invalid_value(self, zip_and_upload_mock):417property_name = "property"418resource_id = "resource_id"419parent_dir = tempfile.gettempdir()420421with self.assertRaises(exceptions.InvalidLocalPathError):422non_existent_file = "some_random_filename"423resource_dict = {property_name: non_existent_file}424upload_local_artifacts(resource_id,425resource_dict,426property_name,427parent_dir,428self.s3_uploader_mock)429430with self.assertRaises(exceptions.InvalidLocalPathError):431non_existent_file = ["invalid datatype"]432resource_dict = {property_name: non_existent_file}433upload_local_artifacts(resource_id,434resource_dict,435property_name,436parent_dir,437self.s3_uploader_mock)438439zip_and_upload_mock.assert_not_called()440self.s3_uploader_mock.upload_with_dedup.assert_not_called()441442@mock.patch("awscli.customizations.cloudformation.artifact_exporter.make_zip")443def test_zip_folder(self, make_zip_mock):444zip_file_name = "name.zip"445make_zip_mock.return_value = zip_file_name446447with self.make_temp_dir() as dirname:448with zip_folder(dirname) as actual_zip_file_name:449self.assertEqual(actual_zip_file_name, zip_file_name)450451make_zip_mock.assert_called_once_with(mock.ANY, dirname)452453@mock.patch("awscli.customizations.cloudformation.artifact_exporter.upload_local_artifacts")454def test_resource(self, upload_local_artifacts_mock):455# Property value is a path to file456457class MockResource(Resource):458PROPERTY_NAME = "foo"459460resource = MockResource(self.s3_uploader_mock)461462resource_id = "id"463resource_dict = {}464resource_dict[resource.PROPERTY_NAME] = "/path/to/file"465parent_dir = "dir"466s3_url = "s3://foo/bar"467468upload_local_artifacts_mock.return_value = s3_url469470resource.export(resource_id, resource_dict, parent_dir)471472upload_local_artifacts_mock.assert_called_once_with(resource_id,473resource_dict,474resource.PROPERTY_NAME,475parent_dir,476self.s3_uploader_mock)477478self.assertEqual(resource_dict[resource.PROPERTY_NAME], s3_url)479480@mock.patch("shutil.rmtree")481@mock.patch("zipfile.is_zipfile")482@mock.patch("awscli.customizations.cloudformation.artifact_exporter.copy_to_temp_dir")483@mock.patch("awscli.customizations.cloudformation.artifact_exporter.zip_and_upload")484@mock.patch("awscli.customizations.cloudformation.artifact_exporter.is_local_file")485def test_resource_with_force_zip_on_regular_file(self, is_local_file_mock, \486zip_and_upload_mock, copy_to_temp_dir_mock, is_zipfile_mock, rmtree_mock):487# Property value is a path to file and FORCE_ZIP is True488489class MockResource(Resource):490PROPERTY_NAME = "foo"491FORCE_ZIP = True492493resource = MockResource(self.s3_uploader_mock)494495resource_id = "id"496resource_dict = {}497original_path = "/path/to/file"498resource_dict[resource.PROPERTY_NAME] = original_path499parent_dir = "dir"500s3_url = "s3://foo/bar"501502zip_and_upload_mock.return_value = s3_url503is_local_file_mock.return_value = True504505with self.make_temp_dir() as tmp_dir:506507copy_to_temp_dir_mock.return_value = tmp_dir508509# This is not a zip file510is_zipfile_mock.return_value = False511512resource.export(resource_id, resource_dict, parent_dir)513514zip_and_upload_mock.assert_called_once_with(tmp_dir, mock.ANY)515rmtree_mock.assert_called_once_with(tmp_dir)516is_zipfile_mock.assert_called_once_with(original_path)517assert resource_dict[resource.PROPERTY_NAME] == s3_url518519@mock.patch("shutil.rmtree")520@mock.patch("zipfile.is_zipfile")521@mock.patch("awscli.customizations.cloudformation.artifact_exporter.copy_to_temp_dir")522@mock.patch("awscli.customizations.cloudformation.artifact_exporter.zip_and_upload")523@mock.patch("awscli.customizations.cloudformation.artifact_exporter.is_local_file")524def test_resource_with_force_zip_on_zip_file(self, is_local_file_mock, \525zip_and_upload_mock, copy_to_temp_dir_mock, is_zipfile_mock, rmtree_mock):526# Property value is a path to zip file and FORCE_ZIP is True527# We should *not* re-zip an existing zip528529class MockResource(Resource):530PROPERTY_NAME = "foo"531FORCE_ZIP = True532533resource = MockResource(self.s3_uploader_mock)534535resource_id = "id"536resource_dict = {}537original_path = "/path/to/zip_file"538resource_dict[resource.PROPERTY_NAME] = original_path539parent_dir = "dir"540s3_url = "s3://foo/bar"541542# When the file is actually a zip-file, no additional zipping has to happen543is_zipfile_mock.return_value = True544is_local_file_mock.return_value = True545zip_and_upload_mock.return_value = s3_url546self.s3_uploader_mock.upload_with_dedup.return_value = s3_url547548resource.export(resource_id, resource_dict, parent_dir)549550copy_to_temp_dir_mock.assert_not_called()551zip_and_upload_mock.assert_not_called()552rmtree_mock.assert_not_called()553is_zipfile_mock.assert_called_once_with(original_path)554assert resource_dict[resource.PROPERTY_NAME] == s3_url555556@mock.patch("shutil.rmtree")557@mock.patch("zipfile.is_zipfile")558@mock.patch("awscli.customizations.cloudformation.artifact_exporter.copy_to_temp_dir")559@mock.patch("awscli.customizations.cloudformation.artifact_exporter.zip_and_upload")560@mock.patch("awscli.customizations.cloudformation.artifact_exporter.is_local_file")561def test_resource_without_force_zip(self, is_local_file_mock, \562zip_and_upload_mock, copy_to_temp_dir_mock, is_zipfile_mock, rmtree_mock):563564class MockResourceNoForceZip(Resource):565PROPERTY_NAME = "foo"566567resource = MockResourceNoForceZip(self.s3_uploader_mock)568569resource_id = "id"570resource_dict = {}571original_path = "/path/to/file"572resource_dict[resource.PROPERTY_NAME] = original_path573parent_dir = "dir"574s3_url = "s3://foo/bar"575576# This is not a zip file, but a valid local file. Since FORCE_ZIP is NOT set, this will not be zipped577is_zipfile_mock.return_value = False578is_local_file_mock.return_value = True579zip_and_upload_mock.return_value = s3_url580self.s3_uploader_mock.upload_with_dedup.return_value = s3_url581582resource.export(resource_id, resource_dict, parent_dir)583584copy_to_temp_dir_mock.assert_not_called()585zip_and_upload_mock.assert_not_called()586rmtree_mock.assert_not_called()587is_zipfile_mock.assert_called_once_with(original_path)588assert resource_dict[resource.PROPERTY_NAME] == s3_url589590@mock.patch("awscli.customizations.cloudformation.artifact_exporter.upload_local_artifacts")591def test_resource_empty_property_value(self, upload_local_artifacts_mock):592# Property value is empty593594class MockResource(Resource):595PROPERTY_NAME = "foo"596resource = MockResource(self.s3_uploader_mock)597598resource_id = "id"599resource_dict = {}600resource_dict[resource.PROPERTY_NAME] = "/path/to/file"601parent_dir = "dir"602s3_url = "s3://foo/bar"603604upload_local_artifacts_mock.return_value = s3_url605resource_dict = {}606resource.export(resource_id, resource_dict, parent_dir)607upload_local_artifacts_mock.assert_called_once_with(resource_id,608resource_dict,609resource.PROPERTY_NAME,610parent_dir,611self.s3_uploader_mock)612self.assertEqual(resource_dict[resource.PROPERTY_NAME], s3_url)613614@mock.patch("awscli.customizations.cloudformation.artifact_exporter.upload_local_artifacts")615def test_resource_property_value_dict(self, upload_local_artifacts_mock):616# Property value is a dictionary. Export should not upload anything617618class MockResource(Resource):619PROPERTY_NAME = "foo"620621resource = MockResource(self.s3_uploader_mock)622resource_id = "id"623resource_dict = {}624resource_dict[resource.PROPERTY_NAME] = "/path/to/file"625parent_dir = "dir"626s3_url = "s3://foo/bar"627628upload_local_artifacts_mock.return_value = s3_url629resource_dict = {}630resource_dict[resource.PROPERTY_NAME] = {"a": "b"}631resource.export(resource_id, resource_dict, parent_dir)632upload_local_artifacts_mock.assert_not_called()633self.assertEqual(resource_dict, {"foo": {"a": "b"}})634635@mock.patch("awscli.customizations.cloudformation.artifact_exporter.upload_local_artifacts")636def test_resource_has_package_null_property_to_false(self, upload_local_artifacts_mock):637# Should not upload anything if PACKAGE_NULL_PROPERTY is set to False638639class MockResource(Resource):640PROPERTY_NAME = "foo"641PACKAGE_NULL_PROPERTY = False642643resource = MockResource(self.s3_uploader_mock)644resource_id = "id"645resource_dict = {}646parent_dir = "dir"647s3_url = "s3://foo/bar"648649upload_local_artifacts_mock.return_value = s3_url650651resource.export(resource_id, resource_dict, parent_dir)652653upload_local_artifacts_mock.assert_not_called()654self.assertNotIn(resource.PROPERTY_NAME, resource_dict)655656@mock.patch("awscli.customizations.cloudformation.artifact_exporter.upload_local_artifacts")657def test_resource_export_fails(self, upload_local_artifacts_mock):658659class MockResource(Resource):660PROPERTY_NAME = "foo"661662resource = MockResource(self.s3_uploader_mock)663resource_id = "id"664resource_dict = {}665resource_dict[resource.PROPERTY_NAME] = "/path/to/file"666parent_dir = "dir"667s3_url = "s3://foo/bar"668669upload_local_artifacts_mock.side_effect = RuntimeError670resource_dict = {}671672with self.assertRaises(exceptions.ExportFailedError):673resource.export(resource_id, resource_dict, parent_dir)674675@mock.patch("awscli.customizations.cloudformation.artifact_exporter.upload_local_artifacts")676def test_resource_with_s3_url_dict(self, upload_local_artifacts_mock):677"""678Checks if we properly export from the Resource classc679:return:680"""681682self.assertTrue(issubclass(ResourceWithS3UrlDict, Resource))683684class MockResource(ResourceWithS3UrlDict):685PROPERTY_NAME = "foo"686BUCKET_NAME_PROPERTY = "b"687OBJECT_KEY_PROPERTY = "o"688VERSION_PROPERTY = "v"689690resource = MockResource(self.s3_uploader_mock)691692# Case 1: Property value is a path to file693resource_id = "id"694resource_dict = {}695resource_dict[resource.PROPERTY_NAME] = "/path/to/file"696parent_dir = "dir"697s3_url = "s3://bucket/key1/key2?versionId=SomeVersionNumber"698699upload_local_artifacts_mock.return_value = s3_url700701resource.export(resource_id, resource_dict, parent_dir)702703upload_local_artifacts_mock.assert_called_once_with(resource_id,704resource_dict,705resource.PROPERTY_NAME,706parent_dir,707self.s3_uploader_mock)708709self.assertEqual(resource_dict[resource.PROPERTY_NAME], {710"b": "bucket",711"o": "key1/key2",712"v": "SomeVersionNumber"713})714715@mock.patch("awscli.customizations.cloudformation.artifact_exporter.Template")716def test_export_cloudformation_stack(self, TemplateMock):717stack_resource = CloudFormationStackResource(self.s3_uploader_mock)718719resource_id = "id"720property_name = stack_resource.PROPERTY_NAME721exported_template_dict = {"foo": "bar"}722result_s3_url = "s3://hello/world"723result_path_style_s3_url = "http://s3.amazonws.com/hello/world"724725template_instance_mock = mock.Mock()726TemplateMock.return_value = template_instance_mock727template_instance_mock.export.return_value = exported_template_dict728729self.s3_uploader_mock.upload_with_dedup.return_value = result_s3_url730self.s3_uploader_mock.to_path_style_s3_url.return_value = result_path_style_s3_url731732with tempfile.NamedTemporaryFile() as handle:733template_path = handle.name734resource_dict = {property_name: template_path}735parent_dir = tempfile.gettempdir()736737stack_resource.export(resource_id, resource_dict, parent_dir)738739self.assertEqual(resource_dict[property_name], result_path_style_s3_url)740741TemplateMock.assert_called_once_with(template_path, parent_dir, self.s3_uploader_mock)742template_instance_mock.export.assert_called_once_with()743self.s3_uploader_mock.upload_with_dedup.assert_called_once_with(mock.ANY, "template")744self.s3_uploader_mock.to_path_style_s3_url.assert_called_once_with("world", None)745746def _assert_stack_template_url_is_not_changed(self, s3_url):747stack_resource = CloudFormationStackResource(self.s3_uploader_mock)748resource_id = "id"749property_name = stack_resource.PROPERTY_NAME750resource_dict = {property_name: s3_url}751752# Case 1: Path is already S3 url753stack_resource.export(resource_id, resource_dict, "dir")754self.assertEqual(resource_dict[property_name], s3_url)755self.s3_uploader_mock.upload_with_dedup.assert_not_called()756757def test_export_cloudformation_stack_no_upload_path_is_s3url(self):758s3_url = "s3://hello/world"759self._assert_stack_template_url_is_not_changed(s3_url)760761def test_export_cloudformation_stack_no_upload_path_is_httpsurl(self):762s3_url = "https://s3.amazonaws.com/hello/world"763self._assert_stack_template_url_is_not_changed(s3_url)764765def test_export_cloudformation_stack_no_upload_path_is_s3_region_httpsurl(self):766s3_url = "https://s3.some-valid-region.amazonaws.com/hello/world"767self._assert_stack_template_url_is_not_changed(s3_url)768769def test_export_cloudformation_stack_no_upload_path_is_virtual(self):770s3_url = "https://bucket.s3.amazonaws.com/key"771self._assert_stack_template_url_is_not_changed(s3_url)772773def test_export_cloudformation_stack_no_upload_path_is_virtual_region(self):774s3_url = "https://bucket.s3.some-region.amazonaws.com/key"775self._assert_stack_template_url_is_not_changed(s3_url)776777def test_export_cloudformation_stack_no_upload_path_is_empty(self):778stack_resource = CloudFormationStackResource(self.s3_uploader_mock)779resource_id = "id"780property_name = stack_resource.PROPERTY_NAME781s3_url = "s3://hello/world"782resource_dict = {property_name: s3_url}783784# Case 2: Path is empty785resource_dict = {}786stack_resource.export(resource_id, resource_dict, "dir")787self.assertEqual(resource_dict, {})788self.s3_uploader_mock.upload_with_dedup.assert_not_called()789790def test_export_cloudformation_stack_no_upload_path_not_file(self):791stack_resource = CloudFormationStackResource(self.s3_uploader_mock)792resource_id = "id"793property_name = stack_resource.PROPERTY_NAME794s3_url = "s3://hello/world"795796# Case 3: Path is not a file797with self.make_temp_dir() as dirname:798resource_dict = {property_name: dirname}799with self.assertRaises(exceptions.ExportFailedError):800stack_resource.export(resource_id, resource_dict, "dir")801self.s3_uploader_mock.upload_with_dedup.assert_not_called()802803@mock.patch("awscli.customizations.cloudformation.artifact_exporter.Template")804def test_export_serverless_application(self, TemplateMock):805stack_resource = ServerlessApplicationResource(self.s3_uploader_mock)806807resource_id = "id"808property_name = stack_resource.PROPERTY_NAME809exported_template_dict = {"foo": "bar"}810result_s3_url = "s3://hello/world"811result_path_style_s3_url = "http://s3.amazonws.com/hello/world"812813template_instance_mock = mock.Mock()814TemplateMock.return_value = template_instance_mock815template_instance_mock.export.return_value = exported_template_dict816817self.s3_uploader_mock.upload_with_dedup.return_value = result_s3_url818self.s3_uploader_mock.to_path_style_s3_url.return_value = result_path_style_s3_url819820with tempfile.NamedTemporaryFile() as handle:821template_path = handle.name822resource_dict = {property_name: template_path}823parent_dir = tempfile.gettempdir()824825stack_resource.export(resource_id, resource_dict, parent_dir)826827self.assertEqual(resource_dict[property_name], result_path_style_s3_url)828829TemplateMock.assert_called_once_with(template_path, parent_dir, self.s3_uploader_mock)830template_instance_mock.export.assert_called_once_with()831self.s3_uploader_mock.upload_with_dedup.assert_called_once_with(mock.ANY, "template")832self.s3_uploader_mock.to_path_style_s3_url.assert_called_once_with("world", None)833834def test_export_serverless_application_no_upload_path_is_s3url(self):835stack_resource = ServerlessApplicationResource(self.s3_uploader_mock)836resource_id = "id"837property_name = stack_resource.PROPERTY_NAME838s3_url = "s3://hello/world"839resource_dict = {property_name: s3_url}840841# Case 1: Path is already S3 url842stack_resource.export(resource_id, resource_dict, "dir")843self.assertEqual(resource_dict[property_name], s3_url)844self.s3_uploader_mock.upload_with_dedup.assert_not_called()845846def test_export_serverless_application_no_upload_path_is_httpsurl(self):847stack_resource = ServerlessApplicationResource(self.s3_uploader_mock)848resource_id = "id"849property_name = stack_resource.PROPERTY_NAME850s3_url = "https://s3.amazonaws.com/hello/world"851resource_dict = {property_name: s3_url}852853# Case 1: Path is already S3 url854stack_resource.export(resource_id, resource_dict, "dir")855self.assertEqual(resource_dict[property_name], s3_url)856self.s3_uploader_mock.upload_with_dedup.assert_not_called()857858def test_export_serverless_application_no_upload_path_is_empty(self):859stack_resource = ServerlessApplicationResource(self.s3_uploader_mock)860resource_id = "id"861property_name = stack_resource.PROPERTY_NAME862863# Case 2: Path is empty864resource_dict = {}865stack_resource.export(resource_id, resource_dict, "dir")866self.assertEqual(resource_dict, {})867self.s3_uploader_mock.upload_with_dedup.assert_not_called()868869def test_export_serverless_application_no_upload_path_not_file(self):870stack_resource = ServerlessApplicationResource(self.s3_uploader_mock)871resource_id = "id"872property_name = stack_resource.PROPERTY_NAME873874# Case 3: Path is not a file875with self.make_temp_dir() as dirname:876resource_dict = {property_name: dirname}877with self.assertRaises(exceptions.ExportFailedError):878stack_resource.export(resource_id, resource_dict, "dir")879self.s3_uploader_mock.upload_with_dedup.assert_not_called()880881def test_export_serverless_application_no_upload_path_is_dictionary(self):882stack_resource = ServerlessApplicationResource(self.s3_uploader_mock)883resource_id = "id"884property_name = stack_resource.PROPERTY_NAME885886# Case 4: Path is dictionary887location = {"ApplicationId": "id", "SemanticVersion": "1.0.1"}888resource_dict = {property_name: location}889stack_resource.export(resource_id, resource_dict, "dir")890self.assertEqual(resource_dict[property_name], location)891self.s3_uploader_mock.upload_with_dedup.assert_not_called()892893@mock.patch("awscli.customizations.cloudformation.artifact_exporter.yaml_parse")894def test_template_export_metadata(self, yaml_parse_mock):895parent_dir = os.path.abspath(os.path.sep)896template_dir = os.path.join(parent_dir, 'foo', 'bar')897template_path = os.path.join(template_dir, 'path')898template_str = self.example_yaml_template()899900metadata_type1_class = mock.Mock()901metadata_type1_class.RESOURCE_TYPE = "metadata_type1"902metadata_type1_class.PROPERTY_NAME = "property_1"903metadata_type1_instance = mock.Mock()904metadata_type1_class.return_value = metadata_type1_instance905906metadata_type2_class = mock.Mock()907metadata_type2_class.RESOURCE_TYPE = "metadata_type2"908metadata_type2_class.PROPERTY_NAME = "property_2"909metadata_type2_instance = mock.Mock()910metadata_type2_class.return_value = metadata_type2_instance911912metadata_to_export = [913metadata_type1_class,914metadata_type2_class915]916917template_dict = {918"Metadata": {919"metadata_type1": {920"property_1": "abc"921},922"metadata_type2": {923"property_2": "def"924}925}926}927open_mock = mock.mock_open()928yaml_parse_mock.return_value = template_dict929930# Patch the file open method to return template string931with mock.patch(932"awscli.customizations.cloudformation.artifact_exporter.open",933open_mock(read_data=template_str)) as open_mock:934935template_exporter = Template(936template_path, parent_dir, self.s3_uploader_mock,937metadata_to_export=metadata_to_export)938exported_template = template_exporter.export()939self.assertEqual(exported_template, template_dict)940941open_mock.assert_called_once_with(942make_abs_path(parent_dir, template_path), "r")943944self.assertEqual(1, yaml_parse_mock.call_count)945946metadata_type1_class.assert_called_once_with(self.s3_uploader_mock)947metadata_type1_instance.export.assert_called_once_with(948"metadata_type1", mock.ANY, template_dir)949metadata_type2_class.assert_called_once_with(self.s3_uploader_mock)950metadata_type2_instance.export.assert_called_once_with(951"metadata_type2", mock.ANY, template_dir)952953@mock.patch("awscli.customizations.cloudformation.artifact_exporter.yaml_parse")954def test_template_export(self, yaml_parse_mock):955parent_dir = os.path.abspath(os.path.sep)956template_dir = os.path.join(parent_dir, 'foo', 'bar')957template_path = os.path.join(template_dir, 'path')958template_str = self.example_yaml_template()959960resource_type1_class = mock.Mock()961resource_type1_class.RESOURCE_TYPE = "resource_type1"962resource_type1_instance = mock.Mock()963resource_type1_class.return_value = resource_type1_instance964resource_type2_class = mock.Mock()965resource_type2_class.RESOURCE_TYPE = "resource_type2"966resource_type2_instance = mock.Mock()967resource_type2_class.return_value = resource_type2_instance968969resources_to_export = [970resource_type1_class,971resource_type2_class972]973974properties = {"foo": "bar"}975template_dict = {976"Resources": {977"Resource1": {978"Type": "resource_type1",979"Properties": properties980},981"Resource2": {982"Type": "resource_type2",983"Properties": properties984},985"Resource3": {986"Type": "some-other-type",987"Properties": properties988}989}990}991992open_mock = mock.mock_open()993yaml_parse_mock.return_value = template_dict994995# Patch the file open method to return template string996with mock.patch(997"awscli.customizations.cloudformation.artifact_exporter.open",998open_mock(read_data=template_str)) as open_mock:9991000template_exporter = Template(1001template_path, parent_dir, self.s3_uploader_mock,1002resources_to_export)1003exported_template = template_exporter.export()1004self.assertEqual(exported_template, template_dict)10051006open_mock.assert_called_once_with(1007make_abs_path(parent_dir, template_path), "r")10081009self.assertEqual(1, yaml_parse_mock.call_count)10101011resource_type1_class.assert_called_once_with(self.s3_uploader_mock)1012resource_type1_instance.export.assert_called_once_with(1013"Resource1", mock.ANY, template_dir)1014resource_type2_class.assert_called_once_with(self.s3_uploader_mock)1015resource_type2_instance.export.assert_called_once_with(1016"Resource2", mock.ANY, template_dir)10171018@mock.patch("awscli.customizations.cloudformation.artifact_exporter.yaml_parse")1019def test_template_export_foreach_valid(self, yaml_parse_mock):1020parent_dir = os.path.abspath(os.path.sep)1021template_dir = os.path.join(parent_dir, 'foo', 'bar')1022template_path = os.path.join(template_dir, 'path')1023template_str = self.example_yaml_template()10241025resource_type1_class = mock.Mock()1026resource_type1_class.RESOURCE_TYPE = "resource_type1"1027resource_type1_instance = mock.Mock()1028resource_type1_class.return_value = resource_type1_instance1029resource_type2_class = mock.Mock()1030resource_type2_class.RESOURCE_TYPE = "resource_type2"1031resource_type2_instance = mock.Mock()1032resource_type2_class.return_value = resource_type2_instance10331034resources_to_export = [1035resource_type1_class,1036resource_type2_class1037]10381039properties = {"foo": "bar"}1040template_dict = {1041"Resources": {1042"Resource1": {1043"Type": "resource_type1",1044"Properties": properties1045},1046"Resource2": {1047"Type": "resource_type2",1048"Properties": properties1049},1050"Resource3": {1051"Type": "some-other-type",1052"Properties": properties1053},1054"Fn::ForEach::OuterLoopName": [1055"Identifier1",1056["4", "5"],1057{1058"Fn::ForEach::InnerLoopName": [1059"Identifier2",1060["6", "7"],1061{1062"Resource${Identifier1}${Identifier2}": {1063"Type": "resource_type2",1064"Properties": properties1065}1066}1067],1068"Resource${Identifier1}": {1069"Type": "resource_type1",1070"Properties": properties1071}1072}1073]1074}1075}10761077open_mock = mock.mock_open()1078yaml_parse_mock.return_value = template_dict10791080# Patch the file open method to return template string1081with mock.patch(1082"awscli.customizations.cloudformation.artifact_exporter.open",1083open_mock(read_data=template_str)) as open_mock:10841085template_exporter = Template(1086template_path, parent_dir, self.s3_uploader_mock,1087resources_to_export)1088exported_template = template_exporter.export()1089self.assertEqual(exported_template, template_dict)10901091open_mock.assert_called_once_with(1092make_abs_path(parent_dir, template_path), "r")10931094self.assertEqual(1, yaml_parse_mock.call_count)10951096resource_type1_class.assert_called_with(self.s3_uploader_mock)1097self.assertEqual(1098resource_type1_instance.export.call_args_list,1099[1100mock.call("Resource1", properties, template_dir),1101mock.call("Resource${Identifier1}", properties, template_dir)1102]1103)1104resource_type2_class.assert_called_with(self.s3_uploader_mock)1105self.assertEqual(1106resource_type2_instance.export.call_args_list,1107[1108mock.call("Resource2", properties, template_dir),1109mock.call("Resource${Identifier1}${Identifier2}", properties, template_dir)1110]1111)11121113@mock.patch("awscli.customizations.cloudformation.artifact_exporter.yaml_parse")1114def test_template_export_foreach_invalid(self, yaml_parse_mock):1115parent_dir = os.path.abspath(os.path.sep)1116template_dir = os.path.join(parent_dir, 'foo', 'bar')1117template_path = os.path.join(template_dir, 'path')1118template_str = self.example_yaml_template()11191120resource_type1_class = mock.Mock()1121resource_type1_class.RESOURCE_TYPE = "resource_type1"1122resource_type1_instance = mock.Mock()1123resource_type1_class.return_value = resource_type1_instance1124resource_type2_class = mock.Mock()1125resource_type2_class.RESOURCE_TYPE = "resource_type2"1126resource_type2_instance = mock.Mock()1127resource_type2_class.return_value = resource_type2_instance11281129resources_to_export = [1130resource_type1_class,1131resource_type2_class1132]11331134properties = {"foo": "bar"}1135template_dict = {1136"Resources": {1137"Resource1": {1138"Type": "resource_type1",1139"Properties": properties1140},1141"Resource2": {1142"Type": "resource_type2",1143"Properties": properties1144},1145"Resource3": {1146"Type": "some-other-type",1147"Properties": properties1148},1149"Fn::ForEach::OuterLoopName": [1150"Identifier1",1151{1152"Resource${Identifier1}": {1153}1154}1155]1156}1157}11581159open_mock = mock.mock_open()1160yaml_parse_mock.return_value = template_dict11611162# Patch the file open method to return template string1163with mock.patch(1164"awscli.customizations.cloudformation.artifact_exporter.open",1165open_mock(read_data=template_str)) as open_mock:1166template_exporter = Template(1167template_path, parent_dir, self.s3_uploader_mock,1168resources_to_export)1169with self.assertRaises(exceptions.InvalidForEachIntrinsicFunctionError):1170template_exporter.export()117111721173@mock.patch("awscli.customizations.cloudformation.artifact_exporter.yaml_parse")1174def test_template_global_export(self, yaml_parse_mock):1175parent_dir = os.path.abspath(os.path.sep)1176template_dir = os.path.join(parent_dir, 'foo', 'bar')1177template_path = os.path.join(template_dir, 'path')1178template_str = self.example_yaml_template()11791180resource_type1_class = mock.Mock()1181resource_type1_instance = mock.Mock()1182resource_type1_class.return_value = resource_type1_instance1183resource_type2_class = mock.Mock()1184resource_type2_instance = mock.Mock()1185resource_type2_class.return_value = resource_type2_instance11861187resources_to_export = {1188"resource_type1": resource_type1_class,1189"resource_type2": resource_type2_class1190}1191properties1 = {"foo": "bar", "Fn::Transform": {"Name": "AWS::Include", "Parameters": {"Location": "foo.yaml"}}}1192properties2 = {"foo": "bar", "Fn::Transform": {"Name": "AWS::OtherTransform"}}1193properties_in_list = {"Fn::Transform": {"Name": "AWS::Include", "Parameters": {"Location": "bar.yaml"}}}1194template_dict = {1195"Resources": {1196"Resource1": {1197"Type": "resource_type1",1198"Properties": properties11199},1200"Resource2": {1201"Type": "resource_type2",1202"Properties": properties2,1203}1204},1205"List": ["foo", properties_in_list]1206}1207open_mock = mock.mock_open()1208include_transform_export_handler_mock = mock.Mock()1209include_transform_export_handler_mock.return_value = {"Name": "AWS::Include", "Parameters": {"Location": "s3://foo"}}1210yaml_parse_mock.return_value = template_dict12111212with mock.patch(1213"awscli.customizations.cloudformation.artifact_exporter.open",1214open_mock(read_data=template_str)) as open_mock:1215with mock.patch.dict(GLOBAL_EXPORT_DICT, {"Fn::Transform": include_transform_export_handler_mock}):1216template_exporter = Template(1217template_path, parent_dir, self.s3_uploader_mock,1218resources_to_export)12191220exported_template = template_exporter.export_global_artifacts(template_exporter.template_dict)12211222first_call_args, kwargs = include_transform_export_handler_mock.call_args_list[0]1223second_call_args, kwargs = include_transform_export_handler_mock.call_args_list[1]1224third_call_args, kwargs = include_transform_export_handler_mock.call_args_list[2]1225call_args = [first_call_args[0], second_call_args[0], third_call_args[0]]1226self.assertTrue({"Name": "AWS::Include", "Parameters": {"Location": "foo.yaml"}} in call_args)1227self.assertTrue({"Name": "AWS::OtherTransform"} in call_args)1228self.assertTrue({"Name": "AWS::Include", "Parameters": {"Location": "bar.yaml"}} in call_args)1229self.assertTrue(template_dir in first_call_args)1230self.assertTrue(template_dir in second_call_args)1231self.assertTrue(template_dir in third_call_args)1232self.assertEqual(include_transform_export_handler_mock.call_count, 3)1233#new s3 url is added to include location1234self.assertEqual(exported_template["Resources"]["Resource1"]["Properties"]["Fn::Transform"], {"Name": "AWS::Include", "Parameters": {"Location": "s3://foo"}})1235self.assertEqual(exported_template["List"][1]["Fn::Transform"], {"Name": "AWS::Include", "Parameters": {"Location": "s3://foo"}})12361237@mock.patch("awscli.customizations.cloudformation.artifact_exporter.is_local_file")1238def test_include_transform_export_handler_with_relative_file_path(self, is_local_file_mock):1239#exports transform1240parent_dir = os.path.abspath("someroot")1241self.s3_uploader_mock.upload_with_dedup.return_value = "s3://foo"1242is_local_file_mock.return_value = True1243abs_file_path = os.path.join(parent_dir, "foo.yaml")12441245handler_output = include_transform_export_handler({"Name": "AWS::Include", "Parameters": {"Location": "foo.yaml"}}, self.s3_uploader_mock, parent_dir)1246self.s3_uploader_mock.upload_with_dedup.assert_called_once_with(abs_file_path)1247is_local_file_mock.assert_called_with(abs_file_path)1248self.assertEqual(handler_output, {'Name': 'AWS::Include', 'Parameters': {'Location': 's3://foo'}})12491250@mock.patch("awscli.customizations.cloudformation.artifact_exporter.is_local_file")1251def test_include_transform_export_handler_with_absolute_file_path(self, is_local_file_mock):1252#exports transform1253parent_dir = os.path.abspath("someroot")1254self.s3_uploader_mock.upload_with_dedup.return_value = "s3://foo"1255is_local_file_mock.return_value = True1256abs_file_path = os.path.abspath(os.path.join("my", "file.yaml"))12571258handler_output = include_transform_export_handler({"Name": "AWS::Include", "Parameters": {"Location": abs_file_path}}, self.s3_uploader_mock, parent_dir)1259self.s3_uploader_mock.upload_with_dedup.assert_called_once_with(abs_file_path)1260is_local_file_mock.assert_called_with(abs_file_path)1261self.assertEqual(handler_output, {'Name': 'AWS::Include', 'Parameters': {'Location': 's3://foo'}})12621263@mock.patch("awscli.customizations.cloudformation.artifact_exporter.is_local_file")1264def test_include_transform_export_handler_with_s3_uri(self, is_local_file_mock):12651266handler_output = include_transform_export_handler({"Name": "AWS::Include", "Parameters": {"Location": "s3://bucket/foo.yaml"}}, self.s3_uploader_mock, "parent_dir")1267# Input is returned unmodified1268self.assertEqual(handler_output, {"Name": "AWS::Include", "Parameters": {"Location": "s3://bucket/foo.yaml"}})12691270is_local_file_mock.assert_not_called()1271self.s3_uploader_mock.assert_not_called()12721273@mock.patch("awscli.customizations.cloudformation.artifact_exporter.is_local_file")1274def test_include_transform_export_handler_with_no_path(self, is_local_file_mock):12751276handler_output = include_transform_export_handler({"Name": "AWS::Include", "Parameters": {"Location": ""}}, self.s3_uploader_mock, "parent_dir")1277# Input is returned unmodified1278self.assertEqual(handler_output, {"Name": "AWS::Include", "Parameters": {"Location": ""}})12791280is_local_file_mock.assert_not_called()1281self.s3_uploader_mock.assert_not_called()12821283@mock.patch("awscli.customizations.cloudformation.artifact_exporter.is_local_file")1284def test_include_transform_export_handler_with_dict_value_for_location(self, is_local_file_mock):12851286handler_output = include_transform_export_handler(1287{"Name": "AWS::Include", "Parameters": {"Location": {"Fn::Sub": "${S3Bucket}/file.txt"}}},1288self.s3_uploader_mock,1289"parent_dir")1290# Input is returned unmodified1291self.assertEqual(handler_output, {"Name": "AWS::Include", "Parameters": {"Location": {"Fn::Sub": "${S3Bucket}/file.txt"}}})12921293is_local_file_mock.assert_not_called()1294self.s3_uploader_mock.assert_not_called()129512961297@mock.patch("awscli.customizations.cloudformation.artifact_exporter.is_local_file")1298def test_include_transform_export_handler_non_local_file(self, is_local_file_mock):1299#returns unchanged template dict if transform not a local file, and not a S3 URI1300is_local_file_mock.return_value = False13011302with self.assertRaises(exceptions.InvalidLocalPathError):1303include_transform_export_handler({"Name": "AWS::Include", "Parameters": {"Location": "http://foo.yaml"}}, self.s3_uploader_mock, "parent_dir")1304is_local_file_mock.assert_called_with("http://foo.yaml")1305self.s3_uploader_mock.assert_not_called()13061307@mock.patch("awscli.customizations.cloudformation.artifact_exporter.is_local_file")1308def test_include_transform_export_handler_non_include_transform(self, is_local_file_mock):1309#ignores transform that is not aws::include1310handler_output = include_transform_export_handler({"Name": "AWS::OtherTransform", "Parameters": {"Location": "foo.yaml"}}, self.s3_uploader_mock, "parent_dir")1311self.s3_uploader_mock.upload_with_dedup.assert_not_called()1312self.assertEqual(handler_output, {"Name": "AWS::OtherTransform", "Parameters": {"Location": "foo.yaml"}})13131314def test_template_export_path_be_folder(self):13151316template_path = "/path/foo"1317# Set parent_dir to be a non-existent folder1318with self.assertRaises(ValueError):1319Template(template_path, "somefolder", self.s3_uploader_mock)13201321# Set parent_dir to be a real folder, but just a relative path1322with self.make_temp_dir() as dirname:1323with self.assertRaises(ValueError):1324Template(template_path, os.path.relpath(dirname), self.s3_uploader_mock)13251326def test_make_zip(self):1327test_file_creator = FileCreator()1328test_file_creator.append_file('index.js',1329'exports handler = (event, context, callback) => {callback(null, event);}')13301331dirname = test_file_creator.rootdir13321333expected_files = set(['index.js'])13341335random_name = ''.join(random.choice(string.ascii_letters) for _ in range(10))1336outfile = os.path.join(tempfile.gettempdir(), random_name)13371338zipfile_name = None1339try:1340zipfile_name = make_zip(outfile, dirname)13411342test_zip_file = zipfile.ZipFile(zipfile_name, "r")1343with closing(test_zip_file) as zf:1344files_in_zip = set()1345for info in zf.infolist():1346files_in_zip.add(info.filename)13471348self.assertEqual(files_in_zip, expected_files)13491350finally:1351if zipfile_name:1352os.remove(zipfile_name)1353test_file_creator.remove_all()13541355@mock.patch("shutil.copy")1356@mock.patch("tempfile.mkdtemp")1357def test_copy_to_temp_dir(self, mkdtemp_mock, copyfile_mock):1358temp_dir = "/tmp/foo/"1359filename = "test.js"1360mkdtemp_mock.return_value = temp_dir13611362returned_dir = copy_to_temp_dir(filename)13631364self.assertEqual(returned_dir, temp_dir)1365copyfile_mock.assert_called_once_with(filename, temp_dir + filename)13661367@contextmanager1368def make_temp_dir(self):1369filename = tempfile.mkdtemp()1370try:1371yield filename1372finally:1373if filename:1374os.rmdir(filename)13751376def example_yaml_template(self):1377return """1378AWSTemplateFormatVersion: '2010-09-09'1379Description: Simple CRUD webservice. State is stored in a SimpleTable (DynamoDB) resource.1380Resources:1381MyFunction:1382Type: AWS::Lambda::Function1383Properties:1384Code: ./handler1385Handler: index.get1386Role:1387Fn::GetAtt:1388- MyFunctionRole1389- Arn1390Timeout: 201391Runtime: nodejs4.31392"""139313941395