/*
* Copyright 2014 Avanza Bank AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.avanza.astrix.contracts;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before;
import org.junit.Test;
import com.avanza.astrix.beans.core.ReactiveExecutionListener;
import com.avanza.astrix.beans.core.ReactiveTypeConverter;
import com.avanza.astrix.beans.core.ReactiveTypeConverterImpl;
import com.avanza.astrix.beans.core.ReactiveTypeHandlerPlugin;
import rx.Observable;
import rx.Subscriber;
public abstract class ReactiveTypeHandlerContract<T> {
private ReactiveTypeConverter reactiveTypeConverter;
private ReactiveTypeHandlerPlugin<T> reactiveTypeHandler;
@Before
public void setup() {
reactiveTypeHandler = newReactiveTypeHandler();
reactiveTypeConverter = new ReactiveTypeConverterImpl(Arrays.asList(reactiveTypeHandler));
}
protected abstract ReactiveTypeHandlerPlugin<T> newReactiveTypeHandler();
@Test(timeout=2000)
public final void reactiveTypeListenerIsNotifiedAsynchronouslyWhenReactiveExecutionCompletes() throws Exception {
T reactiveType = reactiveTypeHandler.newReactiveType();
ReactiveResultSpy resultSpy = new ReactiveResultSpy();
reactiveTypeHandler.subscribe(resultSpy, reactiveType); // Subscribe after execution completes
assertFalse(resultSpy.isDone());
assertNull(resultSpy.error);
assertNull(resultSpy.lastElement);
reactiveTypeHandler.complete("foo", reactiveType); // Complete reactive execution
assertTrue(resultSpy.isDone());
assertEquals("foo", resultSpy.lastElement);
assertNull(resultSpy.error);
}
@Test(timeout=2000)
public final void reactiveTypeListenerIsNotifiedSynchronouslyIfReactiveExecutionAlreadyCompleted() throws Exception {
T reactiveType = reactiveTypeHandler.newReactiveType();
reactiveTypeHandler.complete("foo", reactiveType); // Completes reactive execution
ReactiveResultSpy resultSpy = new ReactiveResultSpy();
reactiveTypeHandler.subscribe(resultSpy, reactiveType); // Subscribe after execution completes
assertTrue(resultSpy.isDone());
assertEquals("foo", resultSpy.lastElement);
assertNull(resultSpy.error);
}
@Test(timeout=2000)
public final void reactiveTypeToObservableShouldNotBlock() throws Exception {
T reactiveType = reactiveTypeHandler.newReactiveType();
Observable<Object> observable = reactiveTypeConverter.toObservable(reactiveTypeHandler.reactiveTypeHandled(), reactiveType);
ObservableSpy reactiveResultListener = new ObservableSpy();
observable.subscribe(reactiveResultListener);
assertFalse(reactiveResultListener.isDone());
reactiveTypeHandler.complete("foo", reactiveType);
assertTrue(reactiveResultListener.isDone());
assertEquals("foo", reactiveResultListener.lastElement);
assertNull(reactiveResultListener.error);
}
@Test(timeout=1000)
public final void reactiveTypeToObservable_CreatedObserverIsSubscribedInConversion() throws Exception {
AtomicInteger sourceSubscriptionCount = new AtomicInteger(0);
Observable<Object> emitsFoo = Observable.create((s) -> {
sourceSubscriptionCount.incrementAndGet();
s.onNext("foo");
s.onCompleted();
});
assertEquals(0, sourceSubscriptionCount.get());
T reactiveType = toCustomReactiveType(emitsFoo);
assertEquals(1, sourceSubscriptionCount.get());
ReactiveResultSpy resultSpy = new ReactiveResultSpy();
reactiveTypeHandler.subscribe(resultSpy, reactiveType);
assertEquals("foo", resultSpy.lastElement);
}
@Test
public final void onlySubscribesOneTimeToSourceObservableIfConvertingBackAndForth() throws Exception {
AtomicInteger sourceSubscriptionCount = new AtomicInteger(0);
Observable<Object> emitsFoo = Observable.create((s) -> {
sourceSubscriptionCount.incrementAndGet();
s.onNext("foo");
s.onCompleted();
});
T reactiveType = toCustomReactiveType(emitsFoo);
Observable<Object> reconstructedObservable = (Observable<Object>) reactiveTypeConverter.toObservable(reactiveTypeHandler.reactiveTypeHandled(), reactiveType);
reactiveType = toCustomReactiveType(reconstructedObservable);
assertEquals(1, sourceSubscriptionCount.get());
ReactiveResultSpy resultSpy = new ReactiveResultSpy();
reactiveTypeHandler.subscribe(resultSpy, reactiveType);
assertEquals("foo", resultSpy.lastElement);
}
@Test
public final void notifiesExceptionalResults() throws Exception {
T reactiveType = reactiveTypeHandler.newReactiveType();
ReactiveResultSpy resultSpy = new ReactiveResultSpy();
reactiveTypeHandler.subscribe(resultSpy, reactiveType); // Subscribe after execution completes
assertFalse(resultSpy.isDone());
assertNull(resultSpy.error);
assertNull(resultSpy.lastElement);
reactiveTypeHandler.completeExceptionally(new RuntimeException("foo"), reactiveType); // Complete reactive execution
assertTrue(resultSpy.isDone());
assertNull(resultSpy.lastElement);
assertNotNull(resultSpy.error);
assertEquals("foo", resultSpy.error.getMessage());
}
private T toCustomReactiveType(Observable<Object> emitsFoo) {
return (T) reactiveTypeConverter.toCustomReactiveType(reactiveType(), emitsFoo);
}
private Class<? super T> reactiveType() {
return this.reactiveTypeHandler.reactiveTypeHandled();
}
private static class ObservableSpy extends Subscriber<Object> {
private final CountDownLatch done = new CountDownLatch(1);
private Throwable error;
private Object lastElement;
@Override
public void onCompleted() {
done.countDown();
}
@Override
public void onError(Throwable e) {
this.error = e;
}
@Override
public void onNext(Object next) {
this.lastElement = next;
}
public boolean isDone() {
return done.getCount() == 0;
}
}
private static class ReactiveResultSpy implements ReactiveExecutionListener {
private final CountDownLatch done = new CountDownLatch(1);
private Throwable error;
private Object lastElement;
@Override
public void onError(Throwable e) {
this.error = e;
done.countDown();
}
@Override
public void onResult(Object result) {
this.lastElement = result;
done.countDown();
}
public boolean isDone() {
return done.getCount() == 0;
}
}
}