@@ -1016,6 +1016,161 @@ def test_template_export(self, yaml_parse_mock):
10161016 resource_type2_instance .export .assert_called_once_with (
10171017 "Resource2" , mock .ANY , template_dir )
10181018
1019+ @mock .patch ("awscli.customizations.cloudformation.artifact_exporter.yaml_parse" )
1020+ def test_template_export_foreach_valid (self , yaml_parse_mock ):
1021+ parent_dir = os .path .sep
1022+ template_dir = os .path .join (parent_dir , 'foo' , 'bar' )
1023+ template_path = os .path .join (template_dir , 'path' )
1024+ template_str = self .example_yaml_template ()
1025+
1026+ resource_type1_class = mock .Mock ()
1027+ resource_type1_class .RESOURCE_TYPE = "resource_type1"
1028+ resource_type1_instance = mock .Mock ()
1029+ resource_type1_class .return_value = resource_type1_instance
1030+ resource_type2_class = mock .Mock ()
1031+ resource_type2_class .RESOURCE_TYPE = "resource_type2"
1032+ resource_type2_instance = mock .Mock ()
1033+ resource_type2_class .return_value = resource_type2_instance
1034+
1035+ resources_to_export = [
1036+ resource_type1_class ,
1037+ resource_type2_class
1038+ ]
1039+
1040+ properties = {"foo" : "bar" }
1041+ template_dict = {
1042+ "Resources" : {
1043+ "Resource1" : {
1044+ "Type" : "resource_type1" ,
1045+ "Properties" : properties
1046+ },
1047+ "Resource2" : {
1048+ "Type" : "resource_type2" ,
1049+ "Properties" : properties
1050+ },
1051+ "Resource3" : {
1052+ "Type" : "some-other-type" ,
1053+ "Properties" : properties
1054+ },
1055+ "Fn::ForEach::OuterLoopName" : [
1056+ "Identifier1" ,
1057+ ["4" , "5" ],
1058+ {
1059+ "Fn::ForEach::InnerLoopName" : [
1060+ "Identifier2" ,
1061+ ["6" , "7" ],
1062+ {
1063+ "Resource${Identifier1}${Identifier2}" : {
1064+ "Type" : "resource_type2" ,
1065+ "Properties" : properties
1066+ }
1067+ }
1068+ ],
1069+ "Resource${Identifier1}" : {
1070+ "Type" : "resource_type1" ,
1071+ "Properties" : properties
1072+ }
1073+ }
1074+ ]
1075+ }
1076+ }
1077+
1078+ open_mock = mock .mock_open ()
1079+ yaml_parse_mock .return_value = template_dict
1080+
1081+ # Patch the file open method to return template string
1082+ with mock .patch (
1083+ "awscli.customizations.cloudformation.artifact_exporter.open" ,
1084+ open_mock (read_data = template_str )) as open_mock :
1085+
1086+ template_exporter = Template (
1087+ template_path , parent_dir , self .s3_uploader_mock ,
1088+ resources_to_export )
1089+ exported_template = template_exporter .export ()
1090+ self .assertEqual (exported_template , template_dict )
1091+
1092+ open_mock .assert_called_once_with (
1093+ make_abs_path (parent_dir , template_path ), "r" )
1094+
1095+ self .assertEqual (1 , yaml_parse_mock .call_count )
1096+
1097+ resource_type1_class .assert_called_with (self .s3_uploader_mock )
1098+ self .assertEqual (
1099+ resource_type1_instance .export .call_args_list ,
1100+ [
1101+ mock .call ("Resource1" , properties , template_dir ),
1102+ mock .call ("Resource${Identifier1}" , properties , template_dir )
1103+ ]
1104+ )
1105+ resource_type2_class .assert_called_with (self .s3_uploader_mock )
1106+ self .assertEqual (
1107+ resource_type2_instance .export .call_args_list ,
1108+ [
1109+ mock .call ("Resource2" , properties , template_dir ),
1110+ mock .call ("Resource${Identifier1}${Identifier2}" , properties , template_dir )
1111+ ]
1112+ )
1113+
1114+ @mock .patch ("awscli.customizations.cloudformation.artifact_exporter.yaml_parse" )
1115+ def test_template_export_foreach_invalid (self , yaml_parse_mock ):
1116+ parent_dir = os .path .sep
1117+ template_dir = os .path .join (parent_dir , 'foo' , 'bar' )
1118+ template_path = os .path .join (template_dir , 'path' )
1119+ template_str = self .example_yaml_template ()
1120+
1121+ resource_type1_class = mock .Mock ()
1122+ resource_type1_class .RESOURCE_TYPE = "resource_type1"
1123+ resource_type1_instance = mock .Mock ()
1124+ resource_type1_class .return_value = resource_type1_instance
1125+ resource_type2_class = mock .Mock ()
1126+ resource_type2_class .RESOURCE_TYPE = "resource_type2"
1127+ resource_type2_instance = mock .Mock ()
1128+ resource_type2_class .return_value = resource_type2_instance
1129+
1130+ resources_to_export = [
1131+ resource_type1_class ,
1132+ resource_type2_class
1133+ ]
1134+
1135+ properties = {"foo" : "bar" }
1136+ template_dict = {
1137+ "Resources" : {
1138+ "Resource1" : {
1139+ "Type" : "resource_type1" ,
1140+ "Properties" : properties
1141+ },
1142+ "Resource2" : {
1143+ "Type" : "resource_type2" ,
1144+ "Properties" : properties
1145+ },
1146+ "Resource3" : {
1147+ "Type" : "some-other-type" ,
1148+ "Properties" : properties
1149+ },
1150+ "Fn::ForEach::OuterLoopName" : [
1151+ "Identifier1" ,
1152+ {
1153+ "Resource${Identifier1}" : {
1154+ }
1155+ }
1156+ ]
1157+ }
1158+ }
1159+
1160+ open_mock = mock .mock_open ()
1161+ yaml_parse_mock .return_value = template_dict
1162+
1163+ # Patch the file open method to return template string
1164+ with mock .patch (
1165+ "awscli.customizations.cloudformation.artifact_exporter.open" ,
1166+ open_mock (read_data = template_str )) as open_mock :
1167+ template_exporter = Template (
1168+ template_path , parent_dir , self .s3_uploader_mock ,
1169+ resources_to_export )
1170+ with self .assertRaises (exceptions .InvalidForEachIntrinsicFunctionError ):
1171+ template_exporter .export ()
1172+
1173+
10191174 @mock .patch ("awscli.customizations.cloudformation.artifact_exporter.yaml_parse" )
10201175 def test_template_global_export (self , yaml_parse_mock ):
10211176 parent_dir = os .path .sep
0 commit comments