2727import static org .mockito .BDDMockito .willReturn ;
2828import static org .mockito .Mockito .mock ;
2929import static org .mockito .Mockito .never ;
30+ import static org .mockito .Mockito .spy ;
3031import static org .mockito .Mockito .times ;
3132import static org .mockito .Mockito .verify ;
3233
3637import java .io .UncheckedIOException ;
3738import java .time .Duration ;
3839import java .util .Collections ;
40+ import java .util .HashMap ;
3941import java .util .LinkedHashMap ;
4042import java .util .Map ;
4143import java .util .concurrent .TimeUnit ;
4244import java .util .concurrent .TimeoutException ;
4345
4446import org .apache .kafka .clients .consumer .Consumer ;
4547import org .apache .kafka .clients .consumer .ConsumerRecord ;
48+ import org .apache .kafka .clients .producer .ProducerConfig ;
4649import org .apache .kafka .clients .producer .ProducerRecord ;
4750import org .apache .kafka .common .PartitionInfo ;
4851import org .apache .kafka .common .header .Header ;
5659import org .springframework .kafka .KafkaException ;
5760import org .springframework .kafka .core .KafkaOperations ;
5861import org .springframework .kafka .core .KafkaOperations .OperationsCallback ;
62+ import org .springframework .kafka .core .ProducerFactory ;
5963import org .springframework .kafka .support .KafkaHeaders ;
64+ import org .springframework .kafka .support .SendResult ;
6065import org .springframework .kafka .support .serializer .DeserializationException ;
6166import org .springframework .kafka .support .serializer .ErrorHandlingDeserializer ;
6267import org .springframework .util .concurrent .ListenableFuture ;
@@ -357,10 +362,15 @@ void replaceOriginalHeaders() {
357362 assertThat (anotherHeaders .lastHeader (KafkaHeaders .DLT_ORIGINAL_TIMESTAMP_TYPE )).isNotEqualTo (originalTimestampType );
358363 }
359364
360- @ SuppressWarnings ("unchecked" )
365+ @ SuppressWarnings ({ "unchecked" , "rawtypes" } )
361366 @ Test
362367 void failIfSendResultIsError () throws Exception {
363368 KafkaOperations <?, ?> template = mock (KafkaOperations .class );
369+ ProducerFactory pf = mock (ProducerFactory .class );
370+ Map <String , Object > props = new HashMap <>();
371+ props .put (ProducerConfig .DELIVERY_TIMEOUT_MS_CONFIG , 10L );
372+ given (pf .getConfigurationProperties ()).willReturn (props );
373+ given (template .getProducerFactory ()).willReturn (pf );
364374 ListenableFuture <?> future = mock (ListenableFuture .class );
365375 ArgumentCaptor <Long > timeoutCaptor = ArgumentCaptor .forClass (Long .class );
366376 given (template .send (any (ProducerRecord .class ))).willReturn (future );
@@ -370,11 +380,61 @@ void failIfSendResultIsError() throws Exception {
370380 recoverer .setFailIfSendResultIsError (true );
371381 Duration waitForSendResultTimeout = Duration .ofSeconds (1 );
372382 recoverer .setWaitForSendResultTimeout (waitForSendResultTimeout );
383+ recoverer .setTimeoutBuffer (0L );
373384 assertThatThrownBy (() -> recoverer .accept (record , new RuntimeException ()))
374385 .isExactlyInstanceOf (KafkaException .class );
375386 assertThat (timeoutCaptor .getValue ()).isEqualTo (waitForSendResultTimeout .toMillis ());
376387 }
377388
389+ @ SuppressWarnings ({ "unchecked" , "rawtypes" })
390+ @ Test
391+ void sendTimeoutDefault () throws Exception {
392+ KafkaOperations <?, ?> template = mock (KafkaOperations .class );
393+ ProducerFactory pf = mock (ProducerFactory .class );
394+ Map <String , Object > props = new HashMap <>();
395+ given (pf .getConfigurationProperties ()).willReturn (props );
396+ given (template .getProducerFactory ()).willReturn (pf );
397+ SettableListenableFuture <SendResult > future = spy (new SettableListenableFuture <>());
398+ ArgumentCaptor <Long > timeoutCaptor = ArgumentCaptor .forClass (Long .class );
399+ given (template .send (any (ProducerRecord .class ))).willReturn (future );
400+ willAnswer (inv -> {
401+ future .set (new SendResult (null , null ));
402+ return null ;
403+ }).given (future ).get (timeoutCaptor .capture (), eq (TimeUnit .MILLISECONDS ));
404+ ConsumerRecord <String , String > record = new ConsumerRecord <>("foo" , 0 , 0L , "bar" , null );
405+ DeadLetterPublishingRecoverer recoverer = new DeadLetterPublishingRecoverer (template );
406+ recoverer .setFailIfSendResultIsError (true );
407+ Duration waitForSendResultTimeout = Duration .ofSeconds (1 );
408+ recoverer .setWaitForSendResultTimeout (waitForSendResultTimeout );
409+ recoverer .accept (record , new RuntimeException ());
410+ assertThat (timeoutCaptor .getValue ()).isEqualTo (Duration .ofSeconds (125 ).toMillis ());
411+ }
412+
413+ @ SuppressWarnings ({ "unchecked" , "rawtypes" })
414+ @ Test
415+ void sendTimeoutConfig () throws Exception {
416+ KafkaOperations <?, ?> template = mock (KafkaOperations .class );
417+ ProducerFactory pf = mock (ProducerFactory .class );
418+ Map <String , Object > props = new HashMap <>();
419+ props .put (ProducerConfig .DELIVERY_TIMEOUT_MS_CONFIG , 30_000L );
420+ given (pf .getConfigurationProperties ()).willReturn (props );
421+ given (template .getProducerFactory ()).willReturn (pf );
422+ SettableListenableFuture <SendResult > future = spy (new SettableListenableFuture <>());
423+ ArgumentCaptor <Long > timeoutCaptor = ArgumentCaptor .forClass (Long .class );
424+ given (template .send (any (ProducerRecord .class ))).willReturn (future );
425+ willAnswer (inv -> {
426+ future .set (new SendResult (null , null ));
427+ return null ;
428+ }).given (future ).get (timeoutCaptor .capture (), eq (TimeUnit .MILLISECONDS ));
429+ ConsumerRecord <String , String > record = new ConsumerRecord <>("foo" , 0 , 0L , "bar" , null );
430+ DeadLetterPublishingRecoverer recoverer = new DeadLetterPublishingRecoverer (template );
431+ recoverer .setFailIfSendResultIsError (true );
432+ Duration waitForSendResultTimeout = Duration .ofSeconds (1 );
433+ recoverer .setWaitForSendResultTimeout (waitForSendResultTimeout );
434+ recoverer .accept (record , new RuntimeException ());
435+ assertThat (timeoutCaptor .getValue ()).isEqualTo (Duration .ofSeconds (35 ).toMillis ());
436+ }
437+
378438 @ SuppressWarnings ("unchecked" )
379439 @ Test
380440 void notFailIfSendResultIsError () throws Exception {
0 commit comments