1+ package software .amazon .lambda .powertools .sqs ;
2+
3+ import java .io .ByteArrayInputStream ;
4+ import java .io .IOException ;
5+ import java .util .HashMap ;
6+ import java .util .Map ;
7+ import java .util .stream .Stream ;
8+
9+ import com .amazonaws .AmazonServiceException ;
10+ import com .amazonaws .SdkClientException ;
11+ import com .amazonaws .services .lambda .runtime .events .SQSEvent ;
12+ import com .amazonaws .services .s3 .AmazonS3 ;
13+ import com .amazonaws .services .s3 .model .S3Object ;
14+ import com .amazonaws .services .s3 .model .S3ObjectInputStream ;
15+ import com .amazonaws .util .StringInputStream ;
16+ import org .apache .http .client .methods .HttpRequestBase ;
17+ import org .junit .jupiter .api .BeforeEach ;
18+ import org .junit .jupiter .api .Test ;
19+ import org .junit .jupiter .params .ParameterizedTest ;
20+ import org .junit .jupiter .params .provider .Arguments ;
21+ import org .junit .jupiter .params .provider .MethodSource ;
22+ import org .junit .jupiter .params .provider .ValueSource ;
23+ import org .mockito .Mock ;
24+ import software .amazon .lambda .powertools .sqs .internal .SqsMessageAspect ;
25+
26+ import static com .amazonaws .services .lambda .runtime .events .SQSEvent .SQSMessage ;
27+ import static java .util .Collections .singletonList ;
28+ import static org .apache .commons .lang3 .reflect .FieldUtils .writeStaticField ;
29+ import static org .assertj .core .api .Assertions .assertThat ;
30+ import static org .assertj .core .api .Assertions .assertThatExceptionOfType ;
31+ import static org .mockito .Mockito .mock ;
32+ import static org .mockito .Mockito .never ;
33+ import static org .mockito .Mockito .verify ;
34+ import static org .mockito .Mockito .verifyNoInteractions ;
35+ import static org .mockito .Mockito .when ;
36+ import static org .mockito .MockitoAnnotations .initMocks ;
37+
38+ class PowertoolsSqsTest {
39+
40+ @ Mock
41+ private AmazonS3 amazonS3 ;
42+ private static final String BUCKET_NAME = "ms-extended-sqs-client" ;
43+ private static final String BUCKET_KEY = "c71eb2ae-37e0-4265-8909-32f4153faddf" ;
44+
45+ @ BeforeEach
46+ void setUp () throws IllegalAccessException {
47+ initMocks (this );
48+ writeStaticField (SqsMessageAspect .class , "amazonS3" , amazonS3 , true );
49+ }
50+
51+ @ Test
52+ public void testLargeMessage () {
53+ S3Object s3Response = new S3Object ();
54+ s3Response .setObjectContent (new ByteArrayInputStream ("A big message" .getBytes ()));
55+
56+ when (amazonS3 .getObject (BUCKET_NAME , BUCKET_KEY )).thenReturn (s3Response );
57+ SQSEvent sqsEvent = messageWithBody ("[\" software.amazon.payloadoffloading.PayloadS3Pointer\" ,{\" s3BucketName\" :\" " + BUCKET_NAME + "\" ,\" s3Key\" :\" " + BUCKET_KEY + "\" }]" );
58+
59+ Map <String , String > sqsMessage = PowertoolsSqs .enrichedMessageFromS3 (sqsEvent , sqsMessages -> {
60+ Map <String , String > someBusinessLogic = new HashMap <>();
61+ someBusinessLogic .put ("Message" , sqsMessages .get (0 ).getBody ());
62+ return someBusinessLogic ;
63+ });
64+
65+ assertThat (sqsMessage )
66+ .hasSize (1 )
67+ .containsEntry ("Message" , "A big message" );
68+
69+ verify (amazonS3 ).deleteObject (BUCKET_NAME , BUCKET_KEY );
70+ }
71+
72+ @ ParameterizedTest
73+ @ ValueSource (booleans = {true , false })
74+ public void testLargeMessageDeleteFromS3Toggle (boolean deleteS3Payload ) {
75+ S3Object s3Response = new S3Object ();
76+ s3Response .setObjectContent (new ByteArrayInputStream ("A big message" .getBytes ()));
77+
78+ when (amazonS3 .getObject (BUCKET_NAME , BUCKET_KEY )).thenReturn (s3Response );
79+ SQSEvent sqsEvent = messageWithBody ("[\" software.amazon.payloadoffloading.PayloadS3Pointer\" ,{\" s3BucketName\" :\" " + BUCKET_NAME + "\" ,\" s3Key\" :\" " + BUCKET_KEY + "\" }]" );
80+
81+ Map <String , String > sqsMessage = PowertoolsSqs .enrichedMessageFromS3 (sqsEvent , deleteS3Payload , sqsMessages -> {
82+ Map <String , String > someBusinessLogic = new HashMap <>();
83+ someBusinessLogic .put ("Message" , sqsMessages .get (0 ).getBody ());
84+ return someBusinessLogic ;
85+ });
86+
87+ assertThat (sqsMessage )
88+ .hasSize (1 )
89+ .containsEntry ("Message" , "A big message" );
90+ if (deleteS3Payload ) {
91+ verify (amazonS3 ).deleteObject (BUCKET_NAME , BUCKET_KEY );
92+ } else {
93+ verify (amazonS3 , never ()).deleteObject (BUCKET_NAME , BUCKET_KEY );
94+ }
95+ }
96+
97+ @ Test
98+ public void shouldNotProcessSmallMessageBody () {
99+ S3Object s3Response = new S3Object ();
100+ s3Response .setObjectContent (new ByteArrayInputStream ("A big message" .getBytes ()));
101+
102+ when (amazonS3 .getObject (BUCKET_NAME , BUCKET_KEY )).thenReturn (s3Response );
103+ SQSEvent sqsEvent = messageWithBody ("This is small message" );
104+
105+ Map <String , String > sqsMessage = PowertoolsSqs .enrichedMessageFromS3 (sqsEvent , sqsMessages -> {
106+ Map <String , String > someBusinessLogic = new HashMap <>();
107+ someBusinessLogic .put ("Message" , sqsMessages .get (0 ).getBody ());
108+ return someBusinessLogic ;
109+ });
110+
111+ assertThat (sqsMessage )
112+ .containsEntry ("Message" , "This is small message" );
113+
114+ verifyNoInteractions (amazonS3 );
115+ }
116+
117+ @ ParameterizedTest
118+ @ MethodSource ("exception" )
119+ public void shouldFailEntireBatchIfFailedDownloadingFromS3 (RuntimeException exception ) {
120+ when (amazonS3 .getObject (BUCKET_NAME , BUCKET_KEY )).thenThrow (exception );
121+
122+ String messageBody = "[\" software.amazon.payloadoffloading.PayloadS3Pointer\" ,{\" s3BucketName\" :\" " + BUCKET_NAME + "\" ,\" s3Key\" :\" " + BUCKET_KEY + "\" }]" ;
123+ SQSEvent sqsEvent = messageWithBody (messageBody );
124+
125+ assertThatExceptionOfType (SqsMessageAspect .FailedProcessingLargePayloadException .class )
126+ .isThrownBy (() -> PowertoolsSqs .enrichedMessageFromS3 (sqsEvent , sqsMessages -> sqsMessages .get (0 ).getBody ()))
127+ .withCause (exception );
128+
129+ verify (amazonS3 , never ()).deleteObject (BUCKET_NAME , BUCKET_KEY );
130+ }
131+
132+ @ Test
133+ public void shouldFailEntireBatchIfFailedProcessingDownloadMessageFromS3 () throws IOException {
134+ S3Object s3Response = new S3Object ();
135+
136+ s3Response .setObjectContent (new S3ObjectInputStream (new StringInputStream ("test" ) {
137+ @ Override
138+ public void close () throws IOException {
139+ throw new IOException ("Failed" );
140+ }
141+ }, mock (HttpRequestBase .class )));
142+
143+ when (amazonS3 .getObject (BUCKET_NAME , BUCKET_KEY )).thenReturn (s3Response );
144+
145+ String messageBody = "[\" software.amazon.payloadoffloading.PayloadS3Pointer\" ,{\" s3BucketName\" :\" " + BUCKET_NAME + "\" ,\" s3Key\" :\" " + BUCKET_KEY + "\" }]" ;
146+ SQSEvent sqsEvent = messageWithBody (messageBody );
147+
148+ assertThatExceptionOfType (SqsMessageAspect .FailedProcessingLargePayloadException .class )
149+ .isThrownBy (() -> PowertoolsSqs .enrichedMessageFromS3 (sqsEvent , sqsMessages -> sqsMessages .get (0 ).getBody ()))
150+ .withCauseInstanceOf (IOException .class );
151+
152+ verify (amazonS3 , never ()).deleteObject (BUCKET_NAME , BUCKET_KEY );
153+ }
154+
155+ private static Stream <Arguments > exception () {
156+ return Stream .of (Arguments .of (new AmazonServiceException ("Service Exception" )),
157+ Arguments .of (new SdkClientException ("Client Exception" )));
158+ }
159+
160+ private SQSEvent messageWithBody (String messageBody ) {
161+ SQSMessage sqsMessage = new SQSMessage ();
162+ sqsMessage .setBody (messageBody );
163+ SQSEvent sqsEvent = new SQSEvent ();
164+ sqsEvent .setRecords (singletonList (sqsMessage ));
165+ return sqsEvent ;
166+ }
167+ }
0 commit comments