Add support for SubscriptionListener using ReactiveRedisMessageListenerContainer.

Original Pull Request: #2052
This commit is contained in:
Mark Paluch
2021-04-26 13:59:02 +02:00
committed by Christoph Strobl
parent 0f1c59813e
commit 58e7aa57a2
11 changed files with 323 additions and 22 deletions

View File

@@ -21,6 +21,7 @@ import static org.mockito.Mockito.*;
import static org.springframework.data.redis.util.ByteUtils.*;
import io.lettuce.core.RedisConnectionException;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import io.lettuce.core.pubsub.api.reactive.RedisPubSubReactiveCommands;
import reactor.core.Disposable;
import reactor.core.publisher.DirectProcessor;
@@ -40,6 +41,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.ReactiveSubscription.Message;
import org.springframework.data.redis.connection.ReactiveSubscription.PatternMessage;
import org.springframework.data.redis.connection.SubscriptionListener;
/**
* Unit tests for {@link LettuceReactiveSubscription}.
@@ -52,11 +54,14 @@ class LettuceReactiveSubscriptionUnitTests {
private LettuceReactiveSubscription subscription;
@Mock StatefulRedisPubSubConnection<ByteBuffer, ByteBuffer> connectionMock;
@Mock RedisPubSubReactiveCommands<ByteBuffer, ByteBuffer> commandsMock;
@BeforeEach
void before() {
subscription = new LettuceReactiveSubscription(commandsMock, e -> new RedisSystemException(e.getMessage(), e));
when(connectionMock.reactive()).thenReturn(commandsMock);
subscription = new LettuceReactiveSubscription(mock(SubscriptionListener.class), connectionMock,
e -> new RedisSystemException(e.getMessage(), e));
}
@Test // DATAREDIS-612

View File

@@ -71,7 +71,7 @@ class ReactiveRedisTemplateUnitTests {
when(connectionMock.pubSubCommands()).thenReturn(pubSubCommands);
when(pubSubCommands.subscribe(any())).thenReturn(Mono.empty());
when(pubSubCommands.createSubscription()).thenReturn(Mono.just(subscription));
when(pubSubCommands.createSubscription(any())).thenReturn(Mono.just(subscription));
when(subscription.receive()).thenReturn(Flux.create(sink -> {}));
ReactiveRedisTemplate<String, String> template = new ReactiveRedisTemplate<>(connectionFactoryMock,

View File

@@ -22,19 +22,25 @@ import reactor.test.StepVerifier;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.ReactiveSubscription;
import org.springframework.data.redis.connection.ReactiveSubscription.ChannelMessage;
import org.springframework.data.redis.connection.ReactiveSubscription.PatternMessage;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.SubscriptionListener;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
@@ -101,6 +107,53 @@ public class ReactiveRedisMessageListenerContainerIntegrationTests {
container.destroy();
}
@ParameterizedRedisTest // GH-1622
void receiveChannelShouldNotifySubscriptionListener() throws Exception {
ReactiveRedisMessageListenerContainer container = new ReactiveRedisMessageListenerContainer(connectionFactory);
AtomicReference<String> onSubscribe = new AtomicReference<>();
AtomicReference<String> onUnsubscribe = new AtomicReference<>();
CompletableFuture<Void> subscribe = new CompletableFuture<>();
CompletableFuture<Void> unsubscribe = new CompletableFuture<>();
CompositeListener listener = new CompositeListener() {
@Override
public void onMessage(Message message, @Nullable byte[] pattern) {
}
@Override
public void onChannelSubscribed(byte[] channel, long count) {
onSubscribe.set(new String(channel));
subscribe.complete(null);
}
@Override
public void onChannelUnsubscribed(byte[] channel, long count) {
onUnsubscribe.set(new String(channel));
unsubscribe.complete(null);
}
};
container.receive(Collections.singletonList(ChannelTopic.of(CHANNEL1)), listener).as(StepVerifier::create) //
.then(awaitSubscription(container::getActiveSubscriptions))
.then(() -> connection.publish(CHANNEL1.getBytes(), MESSAGE.getBytes())) //
.assertNext(c -> {
assertThat(c.getChannel()).isEqualTo(CHANNEL1);
assertThat(c.getMessage()).isEqualTo(MESSAGE);
}) //
.thenCancel().verify();
unsubscribe.get(10, TimeUnit.SECONDS);
assertThat(onSubscribe).hasValue(CHANNEL1);
assertThat(onUnsubscribe).hasValue(CHANNEL1);
container.destroy();
}
@ParameterizedRedisTest // DATAREDIS-612
void shouldReceivePatternMessages() {
@@ -120,6 +173,56 @@ public class ReactiveRedisMessageListenerContainerIntegrationTests {
container.destroy();
}
@ParameterizedRedisTest // GH-1622
void receivePatternShouldNotifySubscriptionListener() throws Exception {
ReactiveRedisMessageListenerContainer container = new ReactiveRedisMessageListenerContainer(connectionFactory);
AtomicReference<String> onPsubscribe = new AtomicReference<>();
AtomicReference<String> onPunsubscribe = new AtomicReference<>();
CompletableFuture<Void> psubscribe = new CompletableFuture<>();
CompletableFuture<Void> punsubscribe = new CompletableFuture<>();
CompositeListener listener = new CompositeListener() {
@Override
public void onMessage(Message message, @Nullable byte[] pattern) {
}
@Override
public void onPatternSubscribed(byte[] pattern, long count) {
onPsubscribe.set(new String(pattern));
psubscribe.complete(null);
}
@Override
public void onPatternUnsubscribed(byte[] pattern, long count) {
onPunsubscribe.set(new String(pattern));
punsubscribe.complete(null);
}
};
container.receive(Collections.singletonList(PatternTopic.of(PATTERN1)), listener) //
.cast(PatternMessage.class) //
.as(StepVerifier::create) //
.then(awaitSubscription(container::getActiveSubscriptions))
.then(() -> connection.publish(CHANNEL1.getBytes(), MESSAGE.getBytes())) //
.assertNext(c -> {
assertThat(c.getPattern()).isEqualTo(PATTERN1);
assertThat(c.getChannel()).isEqualTo(CHANNEL1);
assertThat(c.getMessage()).isEqualTo(MESSAGE);
}) //
.thenCancel().verify();
punsubscribe.get(10, TimeUnit.SECONDS);
assertThat(onPsubscribe).hasValue(PATTERN1);
assertThat(onPunsubscribe).hasValue(PATTERN1);
container.destroy();
}
@ParameterizedRedisTest // DATAREDIS-612
void shouldPublishAndReceiveMessage() throws InterruptedException {
@@ -191,4 +294,8 @@ public class ReactiveRedisMessageListenerContainerIntegrationTests {
Awaitility.await().until(() -> !activeSubscriptions.get().isEmpty());
};
}
interface CompositeListener extends MessageListener, SubscriptionListener {
}
}

View File

@@ -67,7 +67,7 @@ class ReactiveRedisMessageListenerContainerUnitTests {
when(connectionFactoryMock.getReactiveConnection()).thenReturn(connectionMock);
when(connectionMock.pubSubCommands()).thenReturn(commandsMock);
when(connectionMock.closeLater()).thenReturn(Mono.empty());
when(commandsMock.createSubscription()).thenReturn(Mono.just(subscriptionMock));
when(commandsMock.createSubscription(any())).thenReturn(Mono.just(subscriptionMock));
when(subscriptionMock.subscribe(any())).thenReturn(Mono.empty());
when(subscriptionMock.pSubscribe(any())).thenReturn(Mono.empty());
when(subscriptionMock.unsubscribe()).thenReturn(Mono.empty());
@@ -182,7 +182,7 @@ class ReactiveRedisMessageListenerContainerUnitTests {
assertThat(container.getActiveSubscriptions()).isEmpty();
}
@Test // DATAREDIS-612
@Test // DATAREDIS-612, GH-1622
void shouldRegisterSubscriptionMultipleSubscribers() {
reset(subscriptionMock);
@@ -203,11 +203,11 @@ class ReactiveRedisMessageListenerContainerUnitTests {
second.dispose();
verify(subscriptionMock).unsubscribe();
verify(subscriptionMock).cancel();
assertThat(container.getActiveSubscriptions()).isEmpty();
}
@Test // DATAREDIS-612
@Test // DATAREDIS-612, GH-1622
void shouldUnsubscribeOnCancel() {
when(subscriptionMock.receive()).thenReturn(DirectProcessor.create());
@@ -221,7 +221,7 @@ class ReactiveRedisMessageListenerContainerUnitTests {
}).thenCancel().verify();
verify(subscriptionMock).unsubscribe();
verify(subscriptionMock).cancel();
}
@Test // DATAREDIS-612