package com.lambdaworks.redis; import static com.google.code.tempusfugit.temporal.Duration.millis; import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import com.lambdaworks.Delay; import com.lambdaworks.Wait; import com.lambdaworks.redis.api.StatefulRedisConnection; import com.lambdaworks.redis.api.rx.RedisReactiveCommands; import rx.Observable; import rx.Subscriber; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; public class ReactiveConnectionTest extends AbstractRedisClientTest { private RedisReactiveCommands<String, String> reactive; @Rule public ExpectedException exception = ExpectedException.none(); private StatefulRedisConnection<String, String> stateful; @Before public void openReactiveConnection() throws Exception { stateful = client.connect(); reactive = stateful.reactive(); } @After public void closeReactiveConnection() throws Exception { reactive.close(); } @Test public void doNotFireCommandUntilObservation() throws Exception { Observable<String> set = reactive.set(key, value); Delay.delay(millis(200)); assertThat(redis.get(key)).isNull(); set.subscribe(); Wait.untilEquals(value, () -> redis.get(key)).waitOrTimeout(); assertThat(redis.get(key)).isEqualTo(value); } @Test public void fireCommandAfterObserve() throws Exception { assertThat(reactive.set(key, value).toBlocking().first()).isEqualTo("OK"); assertThat(redis.get(key)).isEqualTo(value); } @Test public void isOpen() throws Exception { assertThat(reactive.isOpen()).isTrue(); } @Test public void getStatefulConnection() throws Exception { assertThat(reactive.getStatefulConnection()).isSameAs(stateful); } @Test public void testCancelCommand() throws Exception { List<Object> result = new ArrayList<>(); reactive.clientPause(1000).subscribe(); reactive.set(key, value).subscribe(new CompletionSubscriber(result)); Delay.delay(millis(100)); reactive.reset(); assertThat(result).hasSize(1).contains("completed"); } @Test public void testEcho() throws Exception { String result = reactive.echo("echo").toBlocking().first(); assertThat(result).isEqualTo("echo"); } @Test public void testMultiCancel() throws Exception { List<Object> result = new ArrayList<>(); reactive.clientPause(1000).subscribe(); Observable<String> set = reactive.set(key, value); set.subscribe(new CompletionSubscriber(result)); set.subscribe(new CompletionSubscriber(result)); set.subscribe(new CompletionSubscriber(result)); Delay.delay(millis(100)); reactive.reset(); assertThat(result).hasSize(3).contains("completed"); } @Test public void multiSubscribe() throws Exception { reactive.set(key, "1").subscribe(); Observable<Long> incr = reactive.incr(key); incr.subscribe(); incr.subscribe(); incr.subscribe(); Wait.untilEquals("4", () -> redis.get(key)).waitOrTimeout(); assertThat(redis.get(key)).isEqualTo("4"); } @Test public void transactional() throws Exception { final CountDownLatch sync = new CountDownLatch(1); RedisReactiveCommands<String, String> reactive = client.connect().reactive(); reactive.multi().subscribe(multiResponse -> { reactive.set(key, "1").subscribe(); reactive.incr(key).subscribe(getResponse -> { sync.countDown(); }); reactive.exec().subscribe(); }); sync.await(5, TimeUnit.SECONDS); String result = redis.get(key); assertThat(result).isEqualTo("2"); } @Test public void reactiveChain() throws Exception { Map<String, String> map = new HashMap<>(); map.put(key, value); map.put("key1", "value1"); reactive.mset(map).toBlocking().first(); List<String> values = reactive.keys("*").flatMap(s -> reactive.get(s)).toList().subscribeOn(Schedulers.immediate()) .toBlocking().first(); assertThat(values).hasSize(2).contains(value, "value1"); } @Test public void auth() throws Exception { List<Throwable> errors = new ArrayList<>(); reactive.auth("error").doOnError(errors::add).subscribe(new TestSubscriber<>()); Delay.delay(millis(50)); assertThat(errors).hasSize(1); } @Test public void subscriberCompletingWithExceptionShouldBeHandledSafely() throws Exception { Observable.concat(reactive.set("keyA", "valueA"), reactive.set("keyB", "valueB")).toBlocking().last(); reactive.get("keyA").subscribe(createSubscriberWithExceptionOnComplete()); reactive.get("keyA").subscribe(createSubscriberWithExceptionOnComplete()); String valueB = reactive.get("keyB").toBlocking().toFuture().get(); assertThat(valueB).isEqualTo("valueB"); } private static Subscriber<String> createSubscriberWithExceptionOnComplete() { return new Subscriber<String>() { @Override public void onCompleted() { throw new RuntimeException("throwing something"); } @Override public void onError(Throwable e) { } @Override public void onNext(String s) { } }; } private static class CompletionSubscriber extends Subscriber<Object> { private final List<Object> result; public CompletionSubscriber(List<Object> result) { this.result = result; } @Override public void onCompleted() { result.add("completed"); } @Override public void onError(Throwable e) { result.add(e); } @Override public void onNext(Object o) { result.add(o); } } }