@@ -119,6 +119,133 @@ def test_short_circuit_on_empty_parameters(self):
119119 self .assertEqual (resolver .resolve_parameter_refs (input ), expected )
120120 resolver ._try_resolve_parameter_refs .assert_not_called ()
121121
122+ def test_cloudformation_internal_placeholder_not_resolved (self ):
123+ """Test that CloudFormation internal placeholders are not resolved"""
124+ parameter_values = {
125+ "UserPoolArn" : "{{IntrinsicFunction:debugging-cloudformation-issues4/Cognito.Outputs.UserPoolArn/Fn::GetAtt}}" ,
126+ "NormalParam" : "normal-value" ,
127+ }
128+ resolver = IntrinsicsResolver (parameter_values )
129+
130+ # CloudFormation placeholder should not be resolved
131+ input1 = {"Ref" : "UserPoolArn" }
132+ expected1 = {"Ref" : "UserPoolArn" }
133+ output1 = resolver .resolve_parameter_refs (input1 )
134+ self .assertEqual (output1 , expected1 )
135+
136+ # Normal parameter should still be resolved
137+ input2 = {"Ref" : "NormalParam" }
138+ expected2 = "normal-value"
139+ output2 = resolver .resolve_parameter_refs (input2 )
140+ self .assertEqual (output2 , expected2 )
141+
142+ def test_cloudformation_placeholders_in_nested_structure (self ):
143+ """Test CloudFormation placeholders in nested structures"""
144+ parameter_values = {
145+ "Placeholder1" : "{{IntrinsicFunction:stack/Output1/Fn::GetAtt}}" ,
146+ "Placeholder2" : "{{IntrinsicFunction:stack/Output2/Fn::GetAtt}}" ,
147+ "NormalParam" : "value" ,
148+ }
149+ resolver = IntrinsicsResolver (parameter_values )
150+
151+ input = {
152+ "Resources" : {
153+ "Resource1" : {
154+ "Properties" : {
155+ "Prop1" : {"Ref" : "Placeholder1" },
156+ "Prop2" : {"Ref" : "NormalParam" },
157+ "Prop3" : {"Ref" : "Placeholder2" },
158+ }
159+ }
160+ }
161+ }
162+
163+ expected = {
164+ "Resources" : {
165+ "Resource1" : {
166+ "Properties" : {
167+ "Prop1" : {"Ref" : "Placeholder1" }, # Not resolved
168+ "Prop2" : "value" , # Resolved
169+ "Prop3" : {"Ref" : "Placeholder2" }, # Not resolved
170+ }
171+ }
172+ }
173+ }
174+
175+ output = resolver .resolve_parameter_refs (input )
176+ self .assertEqual (output , expected )
177+
178+ def test_cloudformation_placeholders_in_lists (self ):
179+ """Test CloudFormation placeholders in list structures"""
180+ parameter_values = {
181+ "VpceId" : "{{IntrinsicFunction:stack/VpcEndpoint.Outputs.Id/Fn::GetAtt}}" ,
182+ "Region" : "us-east-1" ,
183+ }
184+ resolver = IntrinsicsResolver (parameter_values )
185+
186+ input = [{"Ref" : "VpceId" }, {"Ref" : "Region" }, "static-value" , {"Ref" : "VpceId" }]
187+
188+ expected = [
189+ {"Ref" : "VpceId" }, # Not resolved
190+ "us-east-1" , # Resolved
191+ "static-value" ,
192+ {"Ref" : "VpceId" }, # Not resolved
193+ ]
194+
195+ output = resolver .resolve_parameter_refs (input )
196+ self .assertEqual (output , expected )
197+
198+ def test_cloudformation_placeholders_with_sub (self ):
199+ """Test that CloudFormation placeholders inside Fn::Sub are not substituted
200+
201+ Similar to Ref, Fn::Sub should not substitute CloudFormation internal placeholders.
202+ This prevents the placeholders from being embedded in strings where they can't be
203+ properly handled by CloudFormation.
204+ """
205+ parameter_values = {
206+ "Placeholder" : "{{IntrinsicFunction:stack/Output/Fn::GetAtt}}" ,
207+ "NormalParam" : "normal-value" ,
208+ }
209+ resolver = IntrinsicsResolver (parameter_values )
210+
211+ # Sub should not substitute CloudFormation placeholders, but should substitute normal params
212+ input = {"Fn::Sub" : "Value is ${Placeholder} and ${NormalParam}" }
213+ expected = {"Fn::Sub" : "Value is ${Placeholder} and normal-value" }
214+
215+ output = resolver .resolve_parameter_refs (input )
216+ self .assertEqual (output , expected )
217+
218+ def test_various_cloudformation_placeholder_formats (self ):
219+ """Test various CloudFormation placeholder formats"""
220+ parameter_values = {
221+ "Valid1" : "{{IntrinsicFunction:stack/Resource.Outputs.Value/Fn::GetAtt}}" ,
222+ "Valid2" : "{{IntrinsicFunction:name-with-dashes/Out/Fn::GetAtt}}" ,
223+ "Valid3" : "{{IntrinsicFunction:stack123/Resource.Out/Fn::GetAtt}}" ,
224+ "NotPlaceholder1" : "{{SomethingElse}}" ,
225+ "NotPlaceholder2" : "{{intrinsicfunction:lowercase}}" ,
226+ "NotPlaceholder3" : "normal-string" ,
227+ }
228+ resolver = IntrinsicsResolver (parameter_values )
229+
230+ # Valid placeholders should not be resolved
231+ for param in ["Valid1" , "Valid2" , "Valid3" ]:
232+ input = {"Ref" : param }
233+ expected = {"Ref" : param }
234+ output = resolver .resolve_parameter_refs (input )
235+ self .assertEqual (output , expected , f"Failed for { param } " )
236+
237+ # Non-placeholders should be resolved
238+ test_cases = [
239+ ("NotPlaceholder1" , "{{SomethingElse}}" ),
240+ ("NotPlaceholder2" , "{{intrinsicfunction:lowercase}}" ),
241+ ("NotPlaceholder3" , "normal-string" ),
242+ ]
243+
244+ for param , expected_value in test_cases :
245+ input = {"Ref" : param }
246+ output = resolver .resolve_parameter_refs (input )
247+ self .assertEqual (output , expected_value , f"Failed for { param } " )
248+
122249
123250class TestResourceReferenceResolution (TestCase ):
124251 def setUp (self ):
0 commit comments