Add support for SubscriptionListener using ReactiveRedisMessageListenerContainer.
Original Pull Request: #2052
This commit is contained in:
committed by
Christoph Strobl
parent
0f1c59813e
commit
58e7aa57a2
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user