Skip to content

Commit 20bc9e6

Browse files
committed
Add SNS annotation tests and release tracking
- SnsMessageProcessing.cs test source in TestServerlessApp - SNSEventsTests.cs CloudFormation writer tests - SNSEvents project reference in TestServerlessApp.csproj - AnalyzerReleases.Unshipped.md entry for AWSLambda0134
1 parent 9f9a0a7 commit 20bc9e6

4 files changed

Lines changed: 276 additions & 0 deletions

File tree

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ AWSLambda0128 | AWSLambdaCSharpGenerator | Error | Authorizer Payload Version Mi
1616
AWSLambda0129 | AWSLambdaCSharpGenerator | Error | Missing LambdaFunction Attribute
1717
AWSLambda0130 | AWSLambdaCSharpGenerator | Error | Invalid return type IAuthorizerResult
1818
AWSLambda0131 | AWSLambdaCSharpGenerator | Error | FromBody not supported on Authorizer functions
19+
AWSLambda0134 | AWSLambdaCSharpGenerator | Error | Invalid SNSEventAttribute
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
using Amazon.Lambda.Annotations.SourceGenerator;
2+
using Amazon.Lambda.Annotations.SourceGenerator.Models;
3+
using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes;
4+
using Amazon.Lambda.Annotations.SourceGenerator.Writers;
5+
using Amazon.Lambda.Annotations.SNS;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using Xunit;
9+
10+
namespace Amazon.Lambda.Annotations.SourceGenerators.Tests.WriterTests
11+
{
12+
public partial class CloudFormationWriterTests
13+
{
14+
const string topicArn1 = "arn:aws:sns:us-east-2:444455556666:topic1";
15+
const string topicArn2 = "arn:aws:sns:us-east-2:444455556666:topic2";
16+
17+
[Theory]
18+
[ClassData(typeof(SnsEventsTestData))]
19+
public void VerifySNSEventAttributes_AreCorrectlyApplied(CloudFormationTemplateFormat templateFormat, IEnumerable<SNSEventAttribute> snsEventAttributes)
20+
{
21+
// ARRANGE
22+
var mockFileManager = GetMockFileManager(string.Empty);
23+
var lambdaFunctionModel = GetLambdaFunctionModel();
24+
lambdaFunctionModel.PackageType = LambdaPackageType.Zip;
25+
foreach (var att in snsEventAttributes)
26+
{
27+
lambdaFunctionModel.Attributes.Add(new AttributeModel<SNSEventAttribute> { Data = att });
28+
}
29+
var cloudFormationWriter = GetCloudFormationWriter(mockFileManager, _directoryManager, templateFormat, _diagnosticReporter);
30+
var report = GetAnnotationReport([lambdaFunctionModel]);
31+
32+
// ACT
33+
cloudFormationWriter.ApplyReport(report);
34+
35+
// ASSERT
36+
ITemplateWriter templateWriter = templateFormat == CloudFormationTemplateFormat.Json ? new JsonWriter() : new YamlWriter();
37+
templateWriter.Parse(mockFileManager.ReadAllText(ServerlessTemplateFilePath));
38+
39+
foreach (var att in snsEventAttributes)
40+
{
41+
var eventName = att.Topic.StartsWith("@") ? att.Topic.Substring(1) : att.Topic.Split(':').ToList()[5];
42+
var eventPath = $"Resources.{lambdaFunctionModel.ResourceName}.Properties.Events.{eventName}";
43+
var eventPropertiesPath = $"{eventPath}.Properties";
44+
45+
Assert.True(templateWriter.Exists(eventPath));
46+
Assert.Equal("SNS", templateWriter.GetToken<string>($"{eventPath}.Type"));
47+
48+
if (!att.Topic.StartsWith("@"))
49+
{
50+
Assert.Equal(att.Topic, templateWriter.GetToken<string>($"{eventPropertiesPath}.Topic"));
51+
}
52+
else
53+
{
54+
Assert.Equal(att.Topic.Substring(1), templateWriter.GetToken<string>($"{eventPropertiesPath}.Topic.Ref"));
55+
}
56+
57+
Assert.Equal(att.IsFilterPolicySet, templateWriter.Exists($"{eventPropertiesPath}.FilterPolicy"));
58+
if (att.IsFilterPolicySet)
59+
{
60+
Assert.Equal(att.FilterPolicy, templateWriter.GetToken<string>($"{eventPropertiesPath}.FilterPolicy"));
61+
}
62+
63+
Assert.Equal(att.IsEnabledSet, templateWriter.Exists($"{eventPropertiesPath}.Enabled"));
64+
if (att.IsEnabledSet)
65+
{
66+
Assert.Equal(att.Enabled, templateWriter.GetToken<bool>($"{eventPropertiesPath}.Enabled"));
67+
}
68+
}
69+
}
70+
71+
[Theory]
72+
[InlineData(CloudFormationTemplateFormat.Json)]
73+
[InlineData(CloudFormationTemplateFormat.Yaml)]
74+
public void VerifySNSEventProperties_AreSyncedCorrectly(CloudFormationTemplateFormat templateFormat)
75+
{
76+
// ARRANGE
77+
var mockFileManager = GetMockFileManager(string.Empty);
78+
var lambdaFunctionModel = GetLambdaFunctionModel();
79+
lambdaFunctionModel.PackageType = LambdaPackageType.Zip;
80+
var eventResourceName = "MySNSEvent";
81+
var eventPropertiesPath = $"Resources.{lambdaFunctionModel.ResourceName}.Properties.Events.{eventResourceName}.Properties";
82+
var syncedEventPropertiesPath = $"Resources.{lambdaFunctionModel.ResourceName}.Metadata.SyncedEventProperties";
83+
84+
var initialAttribute = new SNSEventAttribute(topicArn1)
85+
{
86+
ResourceName = eventResourceName,
87+
FilterPolicy = "{ \"store\": [\"example_corp\"] }"
88+
};
89+
lambdaFunctionModel.Attributes = [new AttributeModel<SNSEventAttribute> { Data = initialAttribute }];
90+
var cloudFormationWriter = GetCloudFormationWriter(mockFileManager, _directoryManager, templateFormat, _diagnosticReporter);
91+
var report = GetAnnotationReport([lambdaFunctionModel]);
92+
93+
// ACT
94+
cloudFormationWriter.ApplyReport(report);
95+
96+
// ASSERT
97+
ITemplateWriter templateWriter = templateFormat == CloudFormationTemplateFormat.Json ? new JsonWriter() : new YamlWriter();
98+
templateWriter.Parse(mockFileManager.ReadAllText(ServerlessTemplateFilePath));
99+
100+
Assert.Equal(topicArn1, templateWriter.GetToken<string>($"{eventPropertiesPath}.Topic"));
101+
Assert.Equal("{ \"store\": [\"example_corp\"] }", templateWriter.GetToken<string>($"{eventPropertiesPath}.FilterPolicy"));
102+
Assert.False(templateWriter.Exists($"{eventPropertiesPath}.Enabled"));
103+
104+
var syncedEventProperties = templateWriter.GetToken<Dictionary<string, List<string>>>($"{syncedEventPropertiesPath}");
105+
Assert.Equal(2, syncedEventProperties[eventResourceName].Count);
106+
Assert.Contains("Topic", syncedEventProperties[eventResourceName]);
107+
Assert.Contains("FilterPolicy", syncedEventProperties[eventResourceName]);
108+
109+
// Update attribute - remove FilterPolicy, add Enabled
110+
var updatedAttribute = new SNSEventAttribute(topicArn2)
111+
{
112+
ResourceName = eventResourceName,
113+
Enabled = false
114+
};
115+
lambdaFunctionModel.Attributes = [new AttributeModel<SNSEventAttribute> { Data = updatedAttribute }];
116+
report = GetAnnotationReport([lambdaFunctionModel]);
117+
118+
cloudFormationWriter.ApplyReport(report);
119+
120+
templateWriter.Parse(mockFileManager.ReadAllText(ServerlessTemplateFilePath));
121+
122+
Assert.Equal(topicArn2, templateWriter.GetToken<string>($"{eventPropertiesPath}.Topic"));
123+
Assert.False(templateWriter.Exists($"{eventPropertiesPath}.FilterPolicy"));
124+
Assert.False(templateWriter.GetToken<bool>($"{eventPropertiesPath}.Enabled"));
125+
126+
syncedEventProperties = templateWriter.GetToken<Dictionary<string, List<string>>>($"{syncedEventPropertiesPath}");
127+
Assert.Equal(2, syncedEventProperties[eventResourceName].Count);
128+
Assert.Contains("Topic", syncedEventProperties[eventResourceName]);
129+
Assert.Contains("Enabled", syncedEventProperties[eventResourceName]);
130+
}
131+
132+
[Theory]
133+
[InlineData(CloudFormationTemplateFormat.Json)]
134+
[InlineData(CloudFormationTemplateFormat.Yaml)]
135+
public void VerifySNSTopicCanBeSet_FromCloudFormationParameter(CloudFormationTemplateFormat templateFormat)
136+
{
137+
// ARRANGE
138+
const string jsonContent = @"{
139+
'Parameters':{
140+
'MyTopic':{
141+
'Type':'String',
142+
'Default':'arn:aws:sns:us-east-2:444455556666:topic1'
143+
}
144+
}
145+
}";
146+
147+
const string yamlContent = @"Parameters:
148+
MyTopic:
149+
Type: String
150+
Default: arn:aws:sns:us-east-2:444455556666:topic1";
151+
152+
ITemplateWriter templateWriter = templateFormat == CloudFormationTemplateFormat.Json ? new JsonWriter() : new YamlWriter();
153+
var content = templateFormat == CloudFormationTemplateFormat.Json ? jsonContent : yamlContent;
154+
155+
var mockFileManager = GetMockFileManager(content);
156+
var lambdaFunctionModel = GetLambdaFunctionModel();
157+
var eventResourceName = "MySNSEvent";
158+
var snsEventAttribute = new SNSEventAttribute("@MyTopic") { ResourceName = eventResourceName };
159+
lambdaFunctionModel.Attributes = [new AttributeModel<SNSEventAttribute> { Data = snsEventAttribute }];
160+
var cloudFormationWriter = GetCloudFormationWriter(mockFileManager, _directoryManager, templateFormat, _diagnosticReporter);
161+
var report = GetAnnotationReport([lambdaFunctionModel]);
162+
163+
var snsEventPropertiesPath = $"Resources.{lambdaFunctionModel.ResourceName}.Properties.Events.{eventResourceName}.Properties";
164+
var syncedEventPropertiesPath = $"Resources.{lambdaFunctionModel.ResourceName}.Metadata.SyncedEventProperties";
165+
166+
// ACT
167+
cloudFormationWriter.ApplyReport(report);
168+
169+
// ASSERT - Topic uses Ref (SNS topics use Ref to get the ARN)
170+
templateWriter.Parse(mockFileManager.ReadAllText(ServerlessTemplateFilePath));
171+
Assert.Equal("MyTopic", templateWriter.GetToken<string>($"{snsEventPropertiesPath}.Topic.Ref"));
172+
Assert.False(templateWriter.Exists($"{snsEventPropertiesPath}.Topic.Fn::GetAtt"));
173+
174+
var syncedEventProperties = templateWriter.GetToken<Dictionary<string, List<string>>>($"{syncedEventPropertiesPath}");
175+
Assert.Single(syncedEventProperties[eventResourceName]);
176+
Assert.Equal("Topic.Ref", syncedEventProperties[eventResourceName][0]);
177+
}
178+
179+
[Theory]
180+
[InlineData(CloudFormationTemplateFormat.Json)]
181+
[InlineData(CloudFormationTemplateFormat.Yaml)]
182+
public void SwitchBetweenArnAndRef_ForTopic(CloudFormationTemplateFormat templateFormat)
183+
{
184+
// Arrange
185+
ITemplateWriter templateWriter = templateFormat == CloudFormationTemplateFormat.Json ? new JsonWriter() : new YamlWriter();
186+
var mockFileManager = GetMockFileManager(string.Empty);
187+
var cloudFormationWriter = GetCloudFormationWriter(mockFileManager, _directoryManager, templateFormat, _diagnosticReporter);
188+
189+
var lambdaFunctionModel = GetLambdaFunctionModel();
190+
var eventResourceName = "MySNSEvent";
191+
192+
var syncedEventPropertiesPath = $"Resources.{lambdaFunctionModel.ResourceName}.Metadata.SyncedEventProperties";
193+
var snsEventPropertiesPath = $"Resources.{lambdaFunctionModel.ResourceName}.Properties.Events.{eventResourceName}.Properties";
194+
195+
// Start with Topic ARN
196+
var snsEventAttribute = new SNSEventAttribute(topicArn1) { ResourceName = eventResourceName };
197+
lambdaFunctionModel.Attributes = [new AttributeModel<SNSEventAttribute> { Data = snsEventAttribute }];
198+
199+
// Act
200+
var report = GetAnnotationReport([lambdaFunctionModel]);
201+
cloudFormationWriter.ApplyReport(report);
202+
203+
// Assert - Topic is ARN
204+
templateWriter.Parse(mockFileManager.ReadAllText(ServerlessTemplateFilePath));
205+
Assert.Equal(topicArn1, templateWriter.GetToken<string>($"{snsEventPropertiesPath}.Topic"));
206+
Assert.False(templateWriter.Exists($"{snsEventPropertiesPath}.Topic.Ref"));
207+
208+
var syncedEventProperties = templateWriter.GetToken<Dictionary<string, List<string>>>($"{syncedEventPropertiesPath}");
209+
Assert.Single(syncedEventProperties[eventResourceName]);
210+
Assert.Equal("Topic", syncedEventProperties[eventResourceName][0]);
211+
212+
// Switch to Topic reference
213+
snsEventAttribute.Topic = "@MyTopic";
214+
cloudFormationWriter.ApplyReport(report);
215+
216+
// Assert - Topic is Ref
217+
templateWriter.Parse(mockFileManager.ReadAllText(ServerlessTemplateFilePath));
218+
Assert.Equal("MyTopic", templateWriter.GetToken<string>($"{snsEventPropertiesPath}.Topic.Ref"));
219+
220+
syncedEventProperties = templateWriter.GetToken<Dictionary<string, List<string>>>($"{syncedEventPropertiesPath}");
221+
Assert.Single(syncedEventProperties[eventResourceName]);
222+
Assert.Equal("Topic.Ref", syncedEventProperties[eventResourceName][0]);
223+
}
224+
225+
public class SnsEventsTestData : TheoryData<CloudFormationTemplateFormat, IEnumerable<SNSEventAttribute>>
226+
{
227+
public SnsEventsTestData()
228+
{
229+
foreach (var templateFormat in new List<CloudFormationTemplateFormat> { CloudFormationTemplateFormat.Json, CloudFormationTemplateFormat.Yaml })
230+
{
231+
// Simple attribute
232+
Add(templateFormat, [new(topicArn1)]);
233+
234+
// Multiple SNSEvent attributes
235+
Add(templateFormat, [new(topicArn1), new(topicArn2)]);
236+
237+
// Use topic reference
238+
Add(templateFormat, [new("@MyTopic")]);
239+
240+
// Use both ARN and topic reference
241+
Add(templateFormat, [new(topicArn1), new("@MyTopic")]);
242+
243+
// Specify filter policy
244+
Add(templateFormat, [new(topicArn1) { FilterPolicy = "{ \"store\": [\"example_corp\"] }" }]);
245+
246+
// Explicitly specify all properties
247+
Add(templateFormat,
248+
[new(topicArn1)
249+
{
250+
FilterPolicy = "{ \"store\": [\"example_corp\"] }",
251+
Enabled = false
252+
}]);
253+
}
254+
}
255+
}
256+
}
257+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Amazon.Lambda.Annotations;
2+
using Amazon.Lambda.Annotations.SNS;
3+
using Amazon.Lambda.Core;
4+
using Amazon.Lambda.SNSEvents;
5+
6+
namespace TestServerlessApp
7+
{
8+
public class SnsMessageProcessing
9+
{
10+
[LambdaFunction(ResourceName = "SNSMessageHandler", Policies = "AWSLambdaSNSTopicExecutionRole")]
11+
[SNSEvent("@TestTopic", ResourceName = "TestTopicEvent", FilterPolicy = "{ \"store\": [\"example_corp\"] }")]
12+
public void HandleMessage(SNSEvent evnt, ILambdaContext lambdaContext)
13+
{
14+
lambdaContext.Logger.Log($"Received {evnt.Records.Count} messages");
15+
}
16+
}
17+
}

Libraries/test/TestServerlessApp/TestServerlessApp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<ProjectReference Include="..\..\src\Amazon.Lambda.Annotations\Amazon.Lambda.Annotations.csproj" />
2828
<ProjectReference Include="..\..\src\Amazon.Lambda.APIGatewayEvents\Amazon.Lambda.APIGatewayEvents.csproj" />
2929
<ProjectReference Include="..\..\src\Amazon.Lambda.SQSEvents\Amazon.Lambda.SQSEvents.csproj" />
30+
<ProjectReference Include="..\..\src\Amazon.Lambda.SNSEvents\Amazon.Lambda.SNSEvents.csproj" />
3031
<ProjectReference Include="..\..\src\Amazon.Lambda.Core\Amazon.Lambda.Core.csproj" />
3132
<ProjectReference Include="..\..\src\Amazon.Lambda.Serialization.SystemTextJson\Amazon.Lambda.Serialization.SystemTextJson.csproj" />
3233
</ItemGroup>

0 commit comments

Comments
 (0)