@@ -282,6 +282,90 @@ public void FunctionUrlConfigRemovedWhenAttributeRemoved(CloudFormationTemplateF
282282 Assert . False ( templateWriter . Exists ( "Resources.TestMethod.Properties.FunctionUrlConfig" ) ) ;
283283 }
284284
285+ [ Theory ]
286+ [ InlineData ( CloudFormationTemplateFormat . Json ) ]
287+ [ InlineData ( CloudFormationTemplateFormat . Yaml ) ]
288+ public void ManualFunctionUrlConfigPreservedWhenNoAttribute ( CloudFormationTemplateFormat templateFormat )
289+ {
290+ // Simulate a template where FunctionUrlConfig was manually added (no SyncedFunctionUrlConfig metadata)
291+ var content = templateFormat == CloudFormationTemplateFormat . Json
292+ ? @"{
293+ 'AWSTemplateFormatVersion': '2010-09-09',
294+ 'Transform': 'AWS::Serverless-2016-10-31',
295+ 'Resources': {
296+ 'TestMethod': {
297+ 'Type': 'AWS::Serverless::Function',
298+ 'Metadata': {
299+ 'Tool': 'Amazon.Lambda.Annotations'
300+ },
301+ 'Properties': {
302+ 'Runtime': 'dotnet8',
303+ 'CodeUri': '',
304+ 'MemorySize': 512,
305+ 'Timeout': 30,
306+ 'Policies': ['AWSLambdaBasicExecutionRole'],
307+ 'PackageType': 'Image',
308+ 'ImageUri': '.',
309+ 'ImageConfig': { 'Command': ['MyAssembly::MyNamespace.MyType::Handler'] },
310+ 'FunctionUrlConfig': {
311+ 'AuthType': 'AWS_IAM'
312+ }
313+ }
314+ }
315+ }
316+ }"
317+ : "AWSTemplateFormatVersion: '2010-09-09'\n Transform: AWS::Serverless-2016-10-31\n Resources:\n TestMethod:\n Type: AWS::Serverless::Function\n Metadata:\n Tool: Amazon.Lambda.Annotations\n Properties:\n Runtime: dotnet8\n CodeUri: ''\n MemorySize: 512\n Timeout: 30\n Policies:\n - AWSLambdaBasicExecutionRole\n PackageType: Image\n ImageUri: .\n ImageConfig:\n Command:\n - 'MyAssembly::MyNamespace.MyType::Handler'\n FunctionUrlConfig:\n AuthType: AWS_IAM" ;
318+
319+ var mockFileManager = GetMockFileManager ( content ) ;
320+ var lambdaFunctionModel = GetLambdaFunctionModel ( "MyAssembly::MyNamespace.MyType::Handler" ,
321+ "TestMethod" , 30 , 512 , null , null ) ;
322+ // No FunctionUrl attribute
323+ lambdaFunctionModel . Attributes = new List < AttributeModel > ( ) ;
324+ var cloudFormationWriter = GetCloudFormationWriter ( mockFileManager , _directoryManager , templateFormat , _diagnosticReporter ) ;
325+ var report = GetAnnotationReport ( new List < ILambdaFunctionSerializable > { lambdaFunctionModel } ) ;
326+ ITemplateWriter templateWriter = templateFormat == CloudFormationTemplateFormat . Json ? new JsonWriter ( ) : new YamlWriter ( ) ;
327+
328+ cloudFormationWriter . ApplyReport ( report ) ;
329+
330+ templateWriter . Parse ( mockFileManager . ReadAllText ( ServerlessTemplateFilePath ) ) ;
331+ // The manually-added FunctionUrlConfig should be preserved
332+ Assert . True ( templateWriter . Exists ( "Resources.TestMethod.Properties.FunctionUrlConfig" ) ) ;
333+ Assert . Equal ( "AWS_IAM" , templateWriter . GetToken < string > ( "Resources.TestMethod.Properties.FunctionUrlConfig.AuthType" ) ) ;
334+ }
335+
336+ [ Theory ]
337+ [ InlineData ( CloudFormationTemplateFormat . Json ) ]
338+ [ InlineData ( CloudFormationTemplateFormat . Yaml ) ]
339+ public void FunctionUrlMetadataTrackedAndCleanedUp ( CloudFormationTemplateFormat templateFormat )
340+ {
341+ // First pass: create FunctionUrlConfig via attribute
342+ var mockFileManager = GetMockFileManager ( string . Empty ) ;
343+ var lambdaFunctionModel = GetLambdaFunctionModel ( "MyAssembly::MyNamespace.MyType::Handler" ,
344+ "TestMethod" , 30 , 512 , null , null ) ;
345+ lambdaFunctionModel . Attributes = new List < AttributeModel >
346+ {
347+ new AttributeModel < FunctionUrlAttribute > { Data = new FunctionUrlAttribute ( ) }
348+ } ;
349+ var cloudFormationWriter = GetCloudFormationWriter ( mockFileManager , _directoryManager , templateFormat , _diagnosticReporter ) ;
350+ ITemplateWriter templateWriter = templateFormat == CloudFormationTemplateFormat . Json ? new JsonWriter ( ) : new YamlWriter ( ) ;
351+
352+ cloudFormationWriter . ApplyReport ( GetAnnotationReport ( new List < ILambdaFunctionSerializable > { lambdaFunctionModel } ) ) ;
353+ templateWriter . Parse ( mockFileManager . ReadAllText ( ServerlessTemplateFilePath ) ) ;
354+
355+ // Verify metadata is set
356+ Assert . True ( templateWriter . Exists ( "Resources.TestMethod.Properties.FunctionUrlConfig" ) ) ;
357+ Assert . True ( templateWriter . GetToken < bool > ( "Resources.TestMethod.Metadata.SyncedFunctionUrlConfig" ) ) ;
358+
359+ // Second pass: remove the attribute
360+ lambdaFunctionModel . Attributes = new List < AttributeModel > ( ) ;
361+ cloudFormationWriter . ApplyReport ( GetAnnotationReport ( new List < ILambdaFunctionSerializable > { lambdaFunctionModel } ) ) ;
362+ templateWriter . Parse ( mockFileManager . ReadAllText ( ServerlessTemplateFilePath ) ) ;
363+
364+ // Verify both FunctionUrlConfig and metadata are cleaned up
365+ Assert . False ( templateWriter . Exists ( "Resources.TestMethod.Properties.FunctionUrlConfig" ) ) ;
366+ Assert . False ( templateWriter . Exists ( "Resources.TestMethod.Metadata.SyncedFunctionUrlConfig" ) ) ;
367+ }
368+
285369 [ Theory ]
286370 [ InlineData ( CloudFormationTemplateFormat . Json ) ]
287371 [ InlineData ( CloudFormationTemplateFormat . Yaml ) ]
0 commit comments