|
99 | 99 | import org.springframework.kafka.test.utils.ContainerTestUtils; |
100 | 100 | import org.springframework.kafka.test.utils.KafkaTestUtils; |
101 | 101 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; |
| 102 | +import org.springframework.util.backoff.FixedBackOff; |
102 | 103 |
|
103 | 104 | /** |
104 | 105 | * Tests for the listener container. |
@@ -615,6 +616,71 @@ public void onMessage(ConsumerRecord<Integer, String> data) { |
615 | 616 | container.stop(); |
616 | 617 | } |
617 | 618 |
|
| 619 | + @SuppressWarnings("unchecked") |
| 620 | + @Test |
| 621 | + public void testRecordAckAfterRecoveryMock() throws Exception { |
| 622 | + ConsumerFactory<Integer, String> cf = mock(ConsumerFactory.class); |
| 623 | + Consumer<Integer, String> consumer = mock(Consumer.class); |
| 624 | + given(cf.createConsumer(eq("grp"), eq("clientId"), isNull(), any())).willReturn(consumer); |
| 625 | + final Map<TopicPartition, List<ConsumerRecord<Integer, String>>> records = new HashMap<>(); |
| 626 | + records.put(new TopicPartition("foo", 0), Arrays.asList( |
| 627 | + new ConsumerRecord<>("foo", 0, 0L, 1, "foo"), |
| 628 | + new ConsumerRecord<>("foo", 0, 1L, 1, "bar"))); |
| 629 | + ConsumerRecords<Integer, String> consumerRecords = new ConsumerRecords<>(records); |
| 630 | + given(consumer.poll(any(Duration.class))).willAnswer(i -> { |
| 631 | + Thread.sleep(50); |
| 632 | + return consumerRecords; |
| 633 | + }); |
| 634 | + TopicPartitionOffset[] topicPartition = new TopicPartitionOffset[] { |
| 635 | + new TopicPartitionOffset("foo", 0) }; |
| 636 | + ContainerProperties containerProps = new ContainerProperties(topicPartition); |
| 637 | + containerProps.setGroupId("grp"); |
| 638 | + containerProps.setAckMode(AckMode.RECORD); |
| 639 | + containerProps.setMissingTopicsFatal(false); |
| 640 | + final CountDownLatch latch = new CountDownLatch(2); |
| 641 | + MessageListener<Integer, String> messageListener = spy( |
| 642 | + new MessageListener<Integer, String>() { // Cannot be lambda: Mockito doesn't mock final classes |
| 643 | + |
| 644 | + @Override |
| 645 | + public void onMessage(ConsumerRecord<Integer, String> data) { |
| 646 | + latch.countDown(); |
| 647 | + if (latch.getCount() == 0) { |
| 648 | + records.clear(); |
| 649 | + } |
| 650 | + if (data.offset() == 1L) { |
| 651 | + throw new IllegalStateException(); |
| 652 | + } |
| 653 | + } |
| 654 | + |
| 655 | + }); |
| 656 | + |
| 657 | + final CountDownLatch commitLatch = new CountDownLatch(2); |
| 658 | + |
| 659 | + willAnswer(i -> { |
| 660 | + commitLatch.countDown(); |
| 661 | + return null; |
| 662 | + } |
| 663 | + ).given(consumer).commitSync(anyMap(), any()); |
| 664 | + |
| 665 | + containerProps.setMessageListener(messageListener); |
| 666 | + containerProps.setClientId("clientId"); |
| 667 | + KafkaMessageListenerContainer<Integer, String> container = |
| 668 | + new KafkaMessageListenerContainer<>(cf, containerProps); |
| 669 | + SeekToCurrentErrorHandler errorHandler = spy(new SeekToCurrentErrorHandler(new FixedBackOff(0L, 0))); |
| 670 | + container.setErrorHandler(errorHandler); |
| 671 | + container.start(); |
| 672 | + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); |
| 673 | + assertThat(commitLatch.await(10, TimeUnit.SECONDS)).isTrue(); |
| 674 | + InOrder inOrder = inOrder(messageListener, consumer, errorHandler); |
| 675 | + inOrder.verify(consumer).poll(Duration.ofMillis(ContainerProperties.DEFAULT_POLL_TIMEOUT)); |
| 676 | + inOrder.verify(messageListener).onMessage(any(ConsumerRecord.class)); |
| 677 | + inOrder.verify(consumer).commitSync(anyMap(), any()); |
| 678 | + inOrder.verify(messageListener).onMessage(any(ConsumerRecord.class)); |
| 679 | + inOrder.verify(errorHandler).handle(any(), any(), any(), any()); |
| 680 | + inOrder.verify(consumer).commitSync(anyMap(), any()); |
| 681 | + container.stop(); |
| 682 | + } |
| 683 | + |
618 | 684 | @SuppressWarnings("unchecked") |
619 | 685 | @Test |
620 | 686 | public void testRecordAckAfterStop() throws Exception { |
@@ -1121,6 +1187,66 @@ public void testBatchListenerErrors() throws Exception { |
1121 | 1187 | logger.info("Stop batch listener errors"); |
1122 | 1188 | } |
1123 | 1189 |
|
| 1190 | + @SuppressWarnings("unchecked") |
| 1191 | + @Test |
| 1192 | + public void testBatchListenerAckAfterRecoveryMock() throws Exception { |
| 1193 | + ConsumerFactory<Integer, String> cf = mock(ConsumerFactory.class); |
| 1194 | + Consumer<Integer, String> consumer = mock(Consumer.class); |
| 1195 | + given(cf.createConsumer(eq("grp"), eq("clientId"), isNull(), any())).willReturn(consumer); |
| 1196 | + final Map<TopicPartition, List<ConsumerRecord<Integer, String>>> records = new HashMap<>(); |
| 1197 | + records.put(new TopicPartition("foo", 0), Arrays.asList( |
| 1198 | + new ConsumerRecord<>("foo", 0, 0L, 1, "foo"), |
| 1199 | + new ConsumerRecord<>("foo", 0, 1L, 1, "bar"))); |
| 1200 | + ConsumerRecords<Integer, String> consumerRecords = new ConsumerRecords<>(records); |
| 1201 | + given(consumer.poll(any(Duration.class))).willAnswer(i -> { |
| 1202 | + Thread.sleep(50); |
| 1203 | + return consumerRecords; |
| 1204 | + }); |
| 1205 | + TopicPartitionOffset[] topicPartition = new TopicPartitionOffset[] { |
| 1206 | + new TopicPartitionOffset("foo", 0) }; |
| 1207 | + ContainerProperties containerProps = new ContainerProperties(topicPartition); |
| 1208 | + containerProps.setGroupId("grp"); |
| 1209 | + containerProps.setMissingTopicsFatal(false); |
| 1210 | + final CountDownLatch latch = new CountDownLatch(1); |
| 1211 | + BatchMessageListener<Integer, String> messageListener = spy( |
| 1212 | + new BatchMessageListener<Integer, String>() { // Cannot be lambda: Mockito doesn't mock final classes |
| 1213 | + |
| 1214 | + @Override |
| 1215 | + public void onMessage(List<ConsumerRecord<Integer, String>> data) { |
| 1216 | + latch.countDown(); |
| 1217 | + throw new IllegalStateException(); |
| 1218 | + } |
| 1219 | + |
| 1220 | + |
| 1221 | + }); |
| 1222 | + |
| 1223 | + final CountDownLatch commitLatch = new CountDownLatch(1); |
| 1224 | + |
| 1225 | + willAnswer(i -> { |
| 1226 | + commitLatch.countDown(); |
| 1227 | + records.clear(); |
| 1228 | + return null; |
| 1229 | + } |
| 1230 | + ).given(consumer).commitSync(anyMap(), any()); |
| 1231 | + |
| 1232 | + containerProps.setMessageListener(messageListener); |
| 1233 | + containerProps.setClientId("clientId"); |
| 1234 | + KafkaMessageListenerContainer<Integer, String> container = |
| 1235 | + new KafkaMessageListenerContainer<>(cf, containerProps); |
| 1236 | + BatchErrorHandler errorHandler = mock(BatchErrorHandler.class); |
| 1237 | + given(errorHandler.isAckAfterHandle()).willReturn(true); |
| 1238 | + container.setBatchErrorHandler(errorHandler); |
| 1239 | + container.start(); |
| 1240 | + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); |
| 1241 | + assertThat(commitLatch.await(10, TimeUnit.SECONDS)).isTrue(); |
| 1242 | + InOrder inOrder = inOrder(messageListener, consumer, errorHandler); |
| 1243 | + inOrder.verify(consumer).poll(Duration.ofMillis(ContainerProperties.DEFAULT_POLL_TIMEOUT)); |
| 1244 | + inOrder.verify(messageListener).onMessage(any()); |
| 1245 | + inOrder.verify(errorHandler).handle(any(), any(), any()); |
| 1246 | + inOrder.verify(consumer).commitSync(anyMap(), any()); |
| 1247 | + container.stop(); |
| 1248 | + } |
| 1249 | + |
1124 | 1250 | @Test |
1125 | 1251 | public void testSeek() throws Exception { |
1126 | 1252 | Map<String, Object> props = KafkaTestUtils.consumerProps("test11", "false", embeddedKafka); |
|
0 commit comments