1313import org .junit .jupiter .api .extension .ExtendWith ;
1414import org .mockito .junit .jupiter .MockitoExtension ;
1515import org .opensearch .dataprepper .model .codec .OutputCodec ;
16+ import org .opensearch .dataprepper .model .configuration .PipelineDescription ;
17+ import org .opensearch .dataprepper .model .pipeline .HeadlessPipeline ;
1618import org .opensearch .dataprepper .model .sink .OutputCodecContext ;
1719import org .opensearch .dataprepper .model .plugin .PluginFactory ;
1820import org .opensearch .dataprepper .metrics .PluginMetrics ;
2830import org .junit .jupiter .params .ParameterizedTest ;
2931import org .junit .jupiter .params .provider .ValueSource ;
3032import org .mockito .Mock ;
33+ import static org .mockito .Mockito .doAnswer ;
3134import static org .mockito .Mockito .mock ;
35+ import static org .mockito .Mockito .never ;
3236import static org .mockito .Mockito .when ;
3337import static org .mockito .Mockito .lenient ;
3438import static org .mockito .Mockito .verify ;
3741import static org .junit .jupiter .api .Assertions .assertThrows ;
3842import static org .hamcrest .CoreMatchers .equalTo ;
3943import static org .hamcrest .CoreMatchers .not ;
44+ import static org .mockito .ArgumentMatchers .anyBoolean ;
4045import static org .mockito .ArgumentMatchers .anyString ;
4146import static org .mockito .ArgumentMatchers .any ;
4247import static org .mockito .ArgumentMatchers .eq ;
@@ -92,6 +97,12 @@ class SqsSinkServiceTest {
9297 @ Mock (strictness = Mock .Strictness .LENIENT )
9398 private SinkContext sinkContext ;
9499
100+ private HeadlessPipeline dlqPipeline ;
101+ @ Mock (strictness = Mock .Strictness .LENIENT )
102+ private PipelineDescription pipelineDescription ;
103+ @ Mock (strictness = Mock .Strictness .LENIENT )
104+ private PluginSetting pluginSetting ;
105+
95106 @ Mock
96107 private Counter eventsSuccessCounter ;
97108 @ Mock
@@ -118,7 +129,8 @@ class SqsSinkServiceTest {
118129 private String queueUrl ;
119130
120131 private SqsSinkService createObjectUnderTest () {
121- return new SqsSinkService (sqsSinkConfig , sqsClient , expressionEvaluator , outputCodec , sinkContext , dlqPushHandler , pluginMetrics );
132+ return new SqsSinkService (sqsSinkConfig , sqsClient , expressionEvaluator , outputCodec , sinkContext , dlqPushHandler ,
133+ pluginMetrics , pluginSetting , pipelineDescription );
122134 }
123135
124136 @ BeforeEach
@@ -139,12 +151,12 @@ void setup() {
139151 when (thresholdConfig .getMaxMessageSizeBytes ()).thenReturn (256 *1024L );
140152 when (thresholdConfig .getMaxEventsPerMessage ()).thenReturn (1 );
141153 when (sqsSinkConfig .getThresholdConfig ()).thenReturn (thresholdConfig );
154+ when (pipelineDescription .getPipelineName ()).thenReturn ("pipeline" );
142155 lenient ().when (sqsSinkConfig .getMaxRetries ()).thenReturn (3 );
143156 lenient ().when (flushResponse .hasFailed ()).thenReturn (false );
144157 when (sqsClient .sendMessageBatch (any (SendMessageBatchRequest .class ))).thenReturn (flushResponse );
145158 when (expressionEvaluator .isValidFormatExpression (anyString ())).thenReturn (true );
146159 when (dlqPushHandler .perform (any (List .class ))).thenReturn (true );
147- PluginSetting pluginSetting = mock (PluginSetting .class );
148160 lenient ().when (pluginSetting .getName ()).thenReturn ("name" );
149161 lenient ().when (pluginSetting .getPipelineName ()).thenReturn ("pipeline" );
150162 when (dlqPushHandler .getPluginSetting ()).thenReturn (pluginSetting );
@@ -520,6 +532,83 @@ void TestWithOneBatch_RetryFlushes() throws Exception {
520532 verify (eventHandle , times (numRecords )).release (true );
521533 }
522534
535+ @ Test
536+ void TestLargeRecordToPipelineDLQ () {
537+ dlqPipeline = mock (HeadlessPipeline .class );
538+
539+ List <Record <Event >> capturedRecords = new ArrayList <>();
540+ doAnswer (invocation -> {
541+ List <Record <Event >> arg = invocation .getArgument (0 );
542+ capturedRecords .addAll (arg );
543+ return null ;
544+ }).when (dlqPipeline ).sendEvents (any ());
545+ SqsSinkService sqsSinkService = createObjectUnderTest ();
546+ sqsSinkService .setDlqPipeline (dlqPipeline );
547+ Record <Event > record = getLargeRecord (300 * 1024 );
548+ sqsSinkService .execute (List .of (record ));
549+
550+ verify (dlqPipeline , times (1 )).sendEvents (any ());
551+ assertThat (capturedRecords .size (), equalTo (1 ));
552+
553+ Event event = capturedRecords .get (0 ).getData ();
554+ assertThat (event .get ("_failure_metadata/pluginId" , String .class ), equalTo ("name" ));
555+ assertThat (event .get ("_failure_metadata/pluginName" , String .class ), equalTo ("name" ));
556+ assertThat (event .get ("_failure_metadata/pipelineName" , String .class ), equalTo ("pipeline" ));
557+ assertThat (event .get ("_failure_metadata/sqsSinkQueueUrl" , String .class ), equalTo (queueUrl ));
558+ verify (eventHandle , never ()).release (anyBoolean ());
559+ }
560+
561+ @ Test
562+ void TestSendingToPipelineDLQAfterMultipleRetries () {
563+ dlqPipeline = mock (HeadlessPipeline .class );
564+ List <Record <Event >> capturedRecords = new ArrayList <>();
565+ doAnswer (invocation -> {
566+ List <Record <Event >> arg = invocation .getArgument (0 );
567+ capturedRecords .addAll (arg );
568+ return null ;
569+ }).when (dlqPipeline ).sendEvents (any ());
570+
571+ final int numRecords = 10 ;
572+ RequestThrottledException requestThrottledException = mock (RequestThrottledException .class );
573+ when (sqsClient .sendMessageBatch (any (SendMessageBatchRequest .class ))).thenThrow (requestThrottledException );
574+
575+ SqsSinkService sqsSinkService = createObjectUnderTest ();
576+ sqsSinkService .setDlqPipeline (dlqPipeline );
577+ List <Record <Event >> records = getRecordList (numRecords );
578+ sqsSinkService .execute (records );
579+
580+ assertThat (capturedRecords .size (), equalTo (numRecords ));
581+ assertThat (sqsSinkService .getBatchUrlMap ().size (), equalTo (0 ));
582+ assertThat (eventsSuccessCount .get (), equalTo (0 ));
583+ assertThat (requestsSuccessCount .get (), equalTo (0 ));
584+ verify (eventHandle , never ()).release (anyBoolean ());
585+ }
586+
587+ @ Test
588+ void TestSendingToPipelineDLQAfterNonRetryableException () {
589+ dlqPipeline = mock (HeadlessPipeline .class );
590+ List <Record <Event >> capturedRecords = new ArrayList <>();
591+ doAnswer (invocation -> {
592+ List <Record <Event >> arg = invocation .getArgument (0 );
593+ capturedRecords .addAll (arg );
594+ return null ;
595+ }).when (dlqPipeline ).sendEvents (any ());
596+
597+ final int numRecords = 10 ;
598+ UnsupportedOperationException unsupportedOperationException = mock (UnsupportedOperationException .class );
599+ when (unsupportedOperationException .getMessage ()).thenReturn ("Unsupported operation" );
600+ when (sqsClient .sendMessageBatch (any (SendMessageBatchRequest .class ))).thenThrow (unsupportedOperationException );
601+
602+ SqsSinkService sqsSinkService = createObjectUnderTest ();
603+ sqsSinkService .setDlqPipeline (dlqPipeline );
604+
605+ List <Record <Event >> records = getRecordList (numRecords );
606+ sqsSinkService .execute (records );
607+
608+ assertThat (capturedRecords .size (), equalTo (numRecords ));
609+ verify (eventHandle , never ()).release (anyBoolean ());
610+ }
611+
523612 private List <Record <Event >> getLargeRecordList (int numberOfRecords ) {
524613 final List <Record <Event >> recordList = new ArrayList <>();
525614 for (int i = 0 ; i < numberOfRecords ; i ++) {
0 commit comments