2020import static org .awaitility .Awaitility .await ;
2121
2222import java .time .Duration ;
23+ import java .util .Date ;
2324import java .util .concurrent .atomic .AtomicReference ;
2425
26+ import org .assertj .core .api .InstanceOfAssertFactories ;
2527import org .junit .After ;
2628import org .junit .Before ;
2729import org .junit .Test ;
3234import org .springframework .context .annotation .Configuration ;
3335import org .springframework .data .redis .connection .stream .PendingMessagesSummary ;
3436import org .springframework .data .redis .connection .stream .ReadOffset ;
35- import org .springframework .data .redis .connection .stream .StreamInfo ;
3637import org .springframework .data .redis .core .ReactiveRedisTemplate ;
3738import org .springframework .data .redis .serializer .RedisSerializationContext ;
3839import org .springframework .data .redis .stream .StreamReceiver ;
3940import org .springframework .integration .IntegrationMessageHeaderAccessor ;
4041import org .springframework .integration .StaticMessageHeaderAccessor ;
4142import org .springframework .integration .acks .SimpleAcknowledgment ;
4243import org .springframework .integration .channel .FluxMessageChannel ;
44+ import org .springframework .integration .channel .QueueChannel ;
4345import org .springframework .integration .handler .ReactiveMessageHandlerAdapter ;
4446import org .springframework .integration .redis .outbound .ReactiveRedisStreamMessageHandler ;
4547import org .springframework .integration .redis .rules .RedisAvailable ;
4850import org .springframework .integration .redis .support .RedisHeaders ;
4951import org .springframework .integration .redis .util .Address ;
5052import org .springframework .integration .redis .util .Person ;
53+ import org .springframework .messaging .Message ;
54+ import org .springframework .messaging .MessagingException ;
55+ import org .springframework .messaging .PollableChannel ;
56+ import org .springframework .messaging .support .ErrorMessage ;
5157import org .springframework .messaging .support .GenericMessage ;
5258import org .springframework .test .annotation .DirtiesContext ;
5359import org .springframework .test .context .junit4 .SpringRunner ;
@@ -75,7 +81,7 @@ public class ReactiveRedisStreamMessageProducerTests extends RedisAvailableTests
7581 FluxMessageChannel fluxMessageChannel ;
7682
7783 @ Autowired
78- ReactiveRedisStreamMessageProducer redisStreamMessageProducer ;
84+ ReactiveRedisStreamMessageProducer reactiveRedisStreamProducer ;
7985
8086 @ Autowired
8187 ReactiveRedisTemplate <String , ?> template ;
@@ -85,42 +91,32 @@ public class ReactiveRedisStreamMessageProducerTests extends RedisAvailableTests
8591
8692 @ Before
8793 public void delKey () {
88- this .template .hasKey (STREAM_KEY )
89- .filter (Boolean ::booleanValue )
90- .flatMapMany (b ->
91- this .template .opsForStream ()
92- .groups (STREAM_KEY )
93- .map (StreamInfo .XInfoGroup ::groupName )
94- .flatMap (groupName ->
95- this .template .opsForStream ()
96- .destroyGroup (STREAM_KEY , groupName )))
97- .blockLast ();
9894 this .template .delete (STREAM_KEY ).block ();
9995 }
10096
10197 @ After
10298 public void tearDown () {
103- this .redisStreamMessageProducer .stop ();
99+ this .reactiveRedisStreamProducer .stop ();
104100 RedisAvailableRule .connectionFactory .resetConnection ();
105101 }
106102
107103 @ Test
108104 @ RedisAvailable
109105 public void testConsumerGroupCreation () {
110- this .redisStreamMessageProducer .setCreateConsumerGroup (true );
111- this .redisStreamMessageProducer .setConsumerName (CONSUMER );
112- this .redisStreamMessageProducer .afterPropertiesSet ();
106+ this .reactiveRedisStreamProducer .setCreateConsumerGroup (true );
107+ this .reactiveRedisStreamProducer .setConsumerName (CONSUMER );
108+ this .reactiveRedisStreamProducer .afterPropertiesSet ();
113109
114110 Flux .from (this .fluxMessageChannel ).subscribe ();
115111
116- this .redisStreamMessageProducer .start ();
112+ this .reactiveRedisStreamProducer .start ();
117113
118114 this .template .opsForStream ()
119115 .groups (STREAM_KEY )
120116 .next ()
121117 .as (StepVerifier ::create )
122118 .assertNext ((infoGroup ) ->
123- assertThat (infoGroup .groupName ()).isEqualTo (this .redisStreamMessageProducer .getBeanName ()))
119+ assertThat (infoGroup .groupName ()).isEqualTo (this .reactiveRedisStreamProducer .getBeanName ()))
124120 .thenCancel ()
125121 .verify (Duration .ofSeconds (10 ));
126122 }
@@ -132,10 +128,10 @@ public void testReadingMessageAsStandaloneClient() {
132128 Person person = new Person (address , "Attoumane" );
133129 this .messageHandler .handleMessage (new GenericMessage <>(person ));
134130
135- this .redisStreamMessageProducer .setCreateConsumerGroup (false );
136- this .redisStreamMessageProducer .setConsumerName (null );
137- this .redisStreamMessageProducer .setReadOffset (ReadOffset .from ("0-0" ));
138- this .redisStreamMessageProducer .afterPropertiesSet ();
131+ this .reactiveRedisStreamProducer .setCreateConsumerGroup (false );
132+ this .reactiveRedisStreamProducer .setConsumerName (null );
133+ this .reactiveRedisStreamProducer .setReadOffset (ReadOffset .from ("0-0" ));
134+ this .reactiveRedisStreamProducer .afterPropertiesSet ();
139135
140136 StepVerifier stepVerifier =
141137 Flux .from (this .fluxMessageChannel )
@@ -148,7 +144,7 @@ public void testReadingMessageAsStandaloneClient() {
148144 .thenCancel ()
149145 .verifyLater ();
150146
151- this .redisStreamMessageProducer .start ();
147+ this .reactiveRedisStreamProducer .start ();
152148
153149 stepVerifier .verify (Duration .ofSeconds (10 ));
154150 }
@@ -160,17 +156,17 @@ public void testReadingMessageAsConsumerInConsumerGroup() {
160156 Person person = new Person (address , "John Snow" );
161157
162158 this .template .opsForStream ()
163- .createGroup (STREAM_KEY , this .redisStreamMessageProducer .getBeanName ())
159+ .createGroup (STREAM_KEY , this .reactiveRedisStreamProducer .getBeanName ())
164160 .as (StepVerifier ::create )
165161 .assertNext (message -> assertThat (message ).isEqualTo ("OK" ))
166162 .thenCancel ()
167163 .verify (Duration .ofSeconds (10 ));
168164
169- this .redisStreamMessageProducer .setCreateConsumerGroup (false );
170- this .redisStreamMessageProducer .setConsumerName (CONSUMER );
171- this .redisStreamMessageProducer .setReadOffset (ReadOffset .latest ());
172- this .redisStreamMessageProducer .afterPropertiesSet ();
173- this .redisStreamMessageProducer .start ();
165+ this .reactiveRedisStreamProducer .setCreateConsumerGroup (false );
166+ this .reactiveRedisStreamProducer .setConsumerName (CONSUMER );
167+ this .reactiveRedisStreamProducer .setReadOffset (ReadOffset .latest ());
168+ this .reactiveRedisStreamProducer .afterPropertiesSet ();
169+ this .reactiveRedisStreamProducer .start ();
174170
175171 StepVerifier stepVerifier =
176172 Flux .from (this .fluxMessageChannel )
@@ -196,13 +192,13 @@ public void testReadingPendingMessageWithNoAutoACK() {
196192 String consumerGroup = "testGroup" ;
197193 String consumerName = "testConsumer" ;
198194
199- this .redisStreamMessageProducer .setCreateConsumerGroup (true );
200- this .redisStreamMessageProducer .setAutoAck (false );
201- this .redisStreamMessageProducer .setConsumerGroup (consumerGroup );
202- this .redisStreamMessageProducer .setConsumerName (consumerName );
203- this .redisStreamMessageProducer .setReadOffset (ReadOffset .latest ());
204- this .redisStreamMessageProducer .afterPropertiesSet ();
205- this .redisStreamMessageProducer .start ();
195+ this .reactiveRedisStreamProducer .setCreateConsumerGroup (true );
196+ this .reactiveRedisStreamProducer .setAutoAck (false );
197+ this .reactiveRedisStreamProducer .setConsumerGroup (consumerGroup );
198+ this .reactiveRedisStreamProducer .setConsumerName (consumerName );
199+ this .reactiveRedisStreamProducer .setReadOffset (ReadOffset .latest ());
200+ this .reactiveRedisStreamProducer .afterPropertiesSet ();
201+ this .reactiveRedisStreamProducer .start ();
206202
207203 AtomicReference <SimpleAcknowledgment > acknowledgmentReference = new AtomicReference <>();
208204
@@ -239,6 +235,65 @@ public void testReadingPendingMessageWithNoAutoACK() {
239235 .verifyComplete ();
240236 }
241237
238+ @ Autowired
239+ ReactiveRedisStreamMessageProducer reactiveErrorRedisStreamProducer ;
240+
241+ @ Autowired
242+ PollableChannel redisStreamErrorChannel ;
243+
244+ @ Test
245+ @ RedisAvailable
246+ public void testReadingNextMessagesWhenSerializationException () {
247+ Person person = new Person (new Address ("Winterfell, Westeros" ), "John Snow" );
248+ Date testDate = new Date ();
249+ this .reactiveErrorRedisStreamProducer .start ();
250+
251+ StepVerifier stepVerifier =
252+ Flux .from (this .fluxMessageChannel )
253+ .map (Message ::getPayload )
254+ .cast (Date .class )
255+ .as (StepVerifier ::create )
256+ .expectNext (testDate )
257+ .thenCancel ()
258+ .verifyLater ();
259+
260+ this .messageHandler .handleMessage (new GenericMessage <>(person ));
261+
262+ Message <?> errorMessage = this .redisStreamErrorChannel .receive (10_000 );
263+ assertThat (errorMessage ).isInstanceOf (ErrorMessage .class )
264+ .extracting ("payload.message" )
265+ .asInstanceOf (InstanceOfAssertFactories .STRING )
266+ .contains ("Cannot deserialize Redis Stream Record" )
267+ .contains ("Cannot parse date out of" );
268+
269+ Mono <PendingMessagesSummary > pendingMessage =
270+ template .opsForStream ()
271+ .pending (STREAM_KEY , this .reactiveErrorRedisStreamProducer .getBeanName ());
272+
273+ StepVerifier .create (pendingMessage )
274+ .assertNext (pendingMessagesSummary ->
275+ assertThat (pendingMessagesSummary .getTotalPendingMessages ()).isEqualTo (1L ))
276+ .verifyComplete ();
277+
278+ Message <?> failedMessage = ((MessagingException ) errorMessage .getPayload ()).getFailedMessage ();
279+ StaticMessageHeaderAccessor .getAcknowledgment (failedMessage ).acknowledge ();
280+
281+ pendingMessage =
282+ template .opsForStream ()
283+ .pending (STREAM_KEY , this .reactiveErrorRedisStreamProducer .getBeanName ());
284+
285+ StepVerifier .create (pendingMessage )
286+ .assertNext (pendingMessagesSummary ->
287+ assertThat (pendingMessagesSummary .getTotalPendingMessages ()).isEqualTo (0 ))
288+ .verifyComplete ();
289+
290+ this .messageHandler .handleMessage (new GenericMessage <>(testDate ));
291+
292+ stepVerifier .verify (Duration .ofSeconds (10 ));
293+
294+ this .reactiveErrorRedisStreamProducer .stop ();
295+ }
296+
242297 @ Configuration
243298 static class ContextConfig {
244299
@@ -263,6 +318,30 @@ FluxMessageChannel fluxMessageChannel() {
263318 return new FluxMessageChannel ();
264319 }
265320
321+ @ Bean
322+ PollableChannel redisStreamErrorChannel () {
323+ return new QueueChannel ();
324+ }
325+
326+ @ Bean
327+ ReactiveRedisStreamMessageProducer reactiveErrorRedisStreamProducer () {
328+ ReactiveRedisStreamMessageProducer messageProducer =
329+ new ReactiveRedisStreamMessageProducer (RedisAvailableRule .connectionFactory , STREAM_KEY );
330+ messageProducer .setStreamReceiverOptions (
331+ StreamReceiver .StreamReceiverOptions .builder ()
332+ .pollTimeout (Duration .ofMillis (100 ))
333+ .targetType (Date .class )
334+ .build ());
335+ messageProducer .setCreateConsumerGroup (true );
336+ messageProducer .setAutoAck (false );
337+ messageProducer .setConsumerName ("testConsumer" );
338+ messageProducer .setReadOffset (ReadOffset .latest ());
339+ messageProducer .setAutoStartup (false );
340+ messageProducer .setOutputChannel (fluxMessageChannel ());
341+ messageProducer .setErrorChannel (redisStreamErrorChannel ());
342+ return messageProducer ;
343+ }
344+
266345 @ Bean
267346 ReactiveRedisStreamMessageProducer reactiveRedisStreamProducer () {
268347 ReactiveRedisStreamMessageProducer messageProducer =
0 commit comments