1+ /*
2+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License").
5+ * You may not use this file except in compliance with the License.
6+ * A copy of the License is located at
7+ *
8+ * http://aws.amazon.com/apache2.0
9+ *
10+ * or in the "license" file accompanying this file. This file is distributed
11+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+ * express or implied. See the License for the specific language governing
13+ * permissions and limitations under the License.
14+ */
15+
16+ using System ;
17+ using System . Collections . Generic ;
18+ using System . Text . Json . Serialization ;
19+ using System . Threading ;
20+ using System . Threading . Tasks ;
21+ using Amazon . Lambda . Core ;
22+ using Amazon . Lambda . SQSEvents ;
23+ using AWS . Lambda . Powertools . BatchProcessing . Sqs ;
24+ using NSubstitute ;
25+ using Xunit ;
26+
27+ namespace AWS . Lambda . Powertools . BatchProcessing . Tests ;
28+
29+ /// <summary>
30+ /// Tests for BatchProcessorAttribute validation and error handling scenarios.
31+ /// </summary>
32+ [ Collection ( "BatchProcessorTests" ) ]
33+ public partial class BatchProcessorAttributeValidationTests
34+ {
35+ public class TestData
36+ {
37+ public string Id { get ; set ; }
38+ public string Name { get ; set ; }
39+ }
40+
41+ public class ValidHandler : ITypedRecordHandler < TestData >
42+ {
43+ public async Task < RecordHandlerResult > HandleAsync ( TestData data , CancellationToken cancellationToken )
44+ {
45+ return await Task . FromResult ( RecordHandlerResult . None ) ;
46+ }
47+ }
48+
49+ public class ValidHandlerWithContext : ITypedRecordHandlerWithContext < TestData >
50+ {
51+ public async Task < RecordHandlerResult > HandleAsync ( TestData data , ILambdaContext context , CancellationToken cancellationToken )
52+ {
53+ return await Task . FromResult ( RecordHandlerResult . None ) ;
54+ }
55+ }
56+
57+ public class InvalidHandler
58+ {
59+ // Does not implement any batch processing interface
60+ }
61+
62+ public class ValidHandlerProvider : ITypedRecordHandlerProvider < TestData >
63+ {
64+ public ITypedRecordHandler < TestData > Create ( )
65+ {
66+ return new ValidHandler ( ) ;
67+ }
68+ }
69+
70+ public class InvalidHandlerProvider
71+ {
72+ // Missing Create method
73+ }
74+
75+ public class ValidHandlerWithContextProvider : ITypedRecordHandlerWithContextProvider < TestData >
76+ {
77+ public ITypedRecordHandlerWithContext < TestData > Create ( )
78+ {
79+ return new ValidHandlerWithContext ( ) ;
80+ }
81+ }
82+
83+ public class InvalidHandlerWithContextProvider
84+ {
85+ // Missing Create method
86+ }
87+
88+ [ JsonSerializable ( typeof ( TestData ) ) ]
89+ public partial class TestJsonContext : JsonSerializerContext
90+ {
91+ }
92+
93+ public class InvalidJsonContext
94+ {
95+ // Does not inherit from JsonSerializerContext
96+ }
97+
98+ public class TestFunctions
99+ {
100+ [ BatchProcessor ( TypedRecordHandler = typeof ( ValidHandler ) , TypedRecordHandlerWithContext = typeof ( ValidHandlerWithContext ) ) ]
101+ public BatchItemFailuresResponse ProcessWithMultipleHandlers ( SQSEvent sqsEvent )
102+ {
103+ return TypedSqsBatchProcessor . Result . BatchItemFailuresResponse ;
104+ }
105+
106+ [ BatchProcessor ( JsonSerializerContext = typeof ( TestJsonContext ) , TypedRecordHandler = typeof ( ValidHandler ) ) ]
107+ public BatchItemFailuresResponse ProcessWithJsonContext ( SQSEvent sqsEvent )
108+ {
109+ TypedSqsBatchProcessor . Result ? . Clear ( ) ;
110+ return TypedSqsBatchProcessor . Result . BatchItemFailuresResponse ;
111+ }
112+
113+ [ BatchProcessor ( JsonSerializerContext = typeof ( InvalidJsonContext ) , TypedRecordHandler = typeof ( ValidHandler ) ) ]
114+ public BatchItemFailuresResponse ProcessWithInvalidJsonContext ( SQSEvent sqsEvent )
115+ {
116+ return TypedSqsBatchProcessor . Result . BatchItemFailuresResponse ;
117+ }
118+
119+ [ BatchProcessor ( TypedRecordHandlerProvider = typeof ( ValidHandlerProvider ) ) ]
120+ public BatchItemFailuresResponse ProcessWithValidProvider ( SQSEvent sqsEvent )
121+ {
122+ TypedSqsBatchProcessor . Result ? . Clear ( ) ;
123+ return TypedSqsBatchProcessor . Result . BatchItemFailuresResponse ;
124+ }
125+
126+ [ BatchProcessor ( TypedRecordHandlerProvider = typeof ( InvalidHandlerProvider ) ) ]
127+ public BatchItemFailuresResponse ProcessWithInvalidProvider ( SQSEvent sqsEvent )
128+ {
129+ return TypedSqsBatchProcessor . Result . BatchItemFailuresResponse ;
130+ }
131+
132+ [ BatchProcessor ( TypedRecordHandlerWithContextProvider = typeof ( ValidHandlerWithContextProvider ) ) ]
133+ public BatchItemFailuresResponse ProcessWithValidContextProvider ( SQSEvent sqsEvent , ILambdaContext context )
134+ {
135+ TypedSqsBatchProcessor . Result ? . Clear ( ) ;
136+ return TypedSqsBatchProcessor . Result . BatchItemFailuresResponse ;
137+ }
138+
139+ [ BatchProcessor ( TypedRecordHandlerWithContextProvider = typeof ( InvalidHandlerWithContextProvider ) ) ]
140+ public BatchItemFailuresResponse ProcessWithInvalidContextProvider ( SQSEvent sqsEvent , ILambdaContext context )
141+ {
142+ return TypedSqsBatchProcessor . Result . BatchItemFailuresResponse ;
143+ }
144+
145+ public BatchItemFailuresResponse ProcessWithoutAttribute ( SQSEvent sqsEvent )
146+ {
147+ return new BatchItemFailuresResponse ( ) ;
148+ }
149+ }
150+
151+ [ Fact ]
152+ public void BatchProcessorAttribute_WithMultipleHandlers_ThrowsInvalidOperationException ( )
153+ {
154+ // Arrange
155+ var sqsEvent = new SQSEvent
156+ {
157+ Records = new List < SQSEvent . SQSMessage >
158+ {
159+ new SQSEvent . SQSMessage
160+ {
161+ MessageId = "1" ,
162+ Body = "{\" Id\" :\" test-1\" ,\" Name\" :\" Test Data\" }" ,
163+ EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue"
164+ }
165+ }
166+ } ;
167+
168+ var function = new TestFunctions ( ) ;
169+
170+ // Act & Assert
171+ Assert . Throws < InvalidOperationException > ( ( ) => function . ProcessWithMultipleHandlers ( sqsEvent ) ) ;
172+ }
173+
174+ [ Fact ]
175+ public void BatchProcessorAttribute_WithJsonContext_ProcessesSuccessfully ( )
176+ {
177+ // Arrange
178+ var sqsEvent = new SQSEvent
179+ {
180+ Records = new List < SQSEvent . SQSMessage >
181+ {
182+ new SQSEvent . SQSMessage
183+ {
184+ MessageId = "1" ,
185+ Body = "{\" Id\" :\" test-1\" ,\" Name\" :\" Test Data\" }" ,
186+ EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue"
187+ }
188+ }
189+ } ;
190+
191+ var function = new TestFunctions ( ) ;
192+
193+ // Act
194+ var result = function . ProcessWithJsonContext ( sqsEvent ) ;
195+
196+ // Assert
197+ Assert . NotNull ( result ) ;
198+ Assert . Empty ( result . BatchItemFailures ) ;
199+ }
200+
201+ [ Fact ]
202+ public void BatchProcessorAttribute_WithInvalidJsonContext_ThrowsInvalidOperationException ( )
203+ {
204+ // Arrange
205+ var sqsEvent = new SQSEvent
206+ {
207+ Records = new List < SQSEvent . SQSMessage >
208+ {
209+ new SQSEvent . SQSMessage
210+ {
211+ MessageId = "1" ,
212+ Body = "{\" Id\" :\" test-1\" ,\" Name\" :\" Test Data\" }" ,
213+ EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue"
214+ }
215+ }
216+ } ;
217+
218+ var function = new TestFunctions ( ) ;
219+
220+ // Act & Assert
221+ Assert . Throws < InvalidOperationException > ( ( ) => function . ProcessWithInvalidJsonContext ( sqsEvent ) ) ;
222+ }
223+
224+ [ Fact ]
225+ public void BatchProcessorAttribute_WithValidProvider_ProcessesSuccessfully ( )
226+ {
227+ // Arrange
228+ var sqsEvent = new SQSEvent
229+ {
230+ Records = new List < SQSEvent . SQSMessage >
231+ {
232+ new SQSEvent . SQSMessage
233+ {
234+ MessageId = "1" ,
235+ Body = "{\" Id\" :\" test-1\" ,\" Name\" :\" Test Data\" }" ,
236+ EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue"
237+ }
238+ }
239+ } ;
240+
241+ var function = new TestFunctions ( ) ;
242+
243+ // Act
244+ var result = function . ProcessWithValidProvider ( sqsEvent ) ;
245+
246+ // Assert
247+ Assert . NotNull ( result ) ;
248+ Assert . Empty ( result . BatchItemFailures ) ;
249+ }
250+
251+ [ Fact ]
252+ public void BatchProcessorAttribute_WithInvalidProvider_ThrowsInvalidOperationException ( )
253+ {
254+ // Arrange
255+ var sqsEvent = new SQSEvent
256+ {
257+ Records = new List < SQSEvent . SQSMessage >
258+ {
259+ new SQSEvent . SQSMessage
260+ {
261+ MessageId = "1" ,
262+ Body = "{\" Id\" :\" test-1\" ,\" Name\" :\" Test Data\" }" ,
263+ EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue"
264+ }
265+ }
266+ } ;
267+
268+ var function = new TestFunctions ( ) ;
269+
270+ // Act & Assert
271+ Assert . Throws < InvalidOperationException > ( ( ) => function . ProcessWithInvalidProvider ( sqsEvent ) ) ;
272+ }
273+
274+ [ Fact ]
275+ public void BatchProcessorAttribute_WithValidContextProvider_ProcessesSuccessfully ( )
276+ {
277+ // Arrange
278+ var sqsEvent = new SQSEvent
279+ {
280+ Records = new List < SQSEvent . SQSMessage >
281+ {
282+ new SQSEvent . SQSMessage
283+ {
284+ MessageId = "1" ,
285+ Body = "{\" Id\" :\" test-1\" ,\" Name\" :\" Test Data\" }" ,
286+ EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue"
287+ }
288+ }
289+ } ;
290+
291+ var mockContext = Substitute . For < ILambdaContext > ( ) ;
292+ var function = new TestFunctions ( ) ;
293+
294+ // Act
295+ var result = function . ProcessWithValidContextProvider ( sqsEvent , mockContext ) ;
296+
297+ // Assert
298+ Assert . NotNull ( result ) ;
299+ Assert . Empty ( result . BatchItemFailures ) ;
300+ }
301+
302+ [ Fact ]
303+ public void BatchProcessorAttribute_WithInvalidContextProvider_ThrowsInvalidOperationException ( )
304+ {
305+ // Arrange
306+ var sqsEvent = new SQSEvent
307+ {
308+ Records = new List < SQSEvent . SQSMessage >
309+ {
310+ new SQSEvent . SQSMessage
311+ {
312+ MessageId = "1" ,
313+ Body = "{\" Id\" :\" test-1\" ,\" Name\" :\" Test Data\" }" ,
314+ EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue"
315+ }
316+ }
317+ } ;
318+
319+ var mockContext = Substitute . For < ILambdaContext > ( ) ;
320+ var function = new TestFunctions ( ) ;
321+
322+ // Act & Assert
323+ Assert . Throws < InvalidOperationException > ( ( ) => function . ProcessWithInvalidContextProvider ( sqsEvent , mockContext ) ) ;
324+ }
325+
326+ [ Fact ]
327+ public void BatchProcessorAttribute_CreateAspectHandler_WithNullArgs_ThrowsArgumentException ( )
328+ {
329+ // Arrange
330+ var attribute = new BatchProcessorAttribute
331+ {
332+ TypedRecordHandler = typeof ( ValidHandler )
333+ } ;
334+
335+ // Act & Assert
336+ Assert . Throws < ArgumentException > ( ( ) => attribute . CreateAspectHandler ( null ) ) ;
337+ }
338+
339+ [ Fact ]
340+ public void BatchProcessorAttribute_CreateAspectHandler_WithEmptyArgs_ThrowsArgumentException ( )
341+ {
342+ // Arrange
343+ var attribute = new BatchProcessorAttribute
344+ {
345+ TypedRecordHandler = typeof ( ValidHandler )
346+ } ;
347+
348+ // Act & Assert
349+ Assert . Throws < ArgumentException > ( ( ) => attribute . CreateAspectHandler ( new object [ 0 ] ) ) ;
350+ }
351+
352+ [ Fact ]
353+ public void BatchProcessorAttribute_CreateAspectHandler_WithInvalidEventType_ThrowsArgumentException ( )
354+ {
355+ // Arrange
356+ var attribute = new BatchProcessorAttribute
357+ {
358+ TypedRecordHandler = typeof ( ValidHandler )
359+ } ;
360+
361+ // Act & Assert
362+ Assert . Throws < ArgumentException > ( ( ) => attribute . CreateAspectHandler ( new object [ ] { "invalid event" } ) ) ;
363+ }
364+ }
0 commit comments