package org.activityinfo.legacy.client.remote;
/*
* #%L
* ActivityInfo Server
* %%
* Copyright (C) 2009 - 2013 UNICEF
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.core.client.testing.StubScheduler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import org.activityinfo.legacy.client.CommandCache;
import org.activityinfo.legacy.client.Dispatcher;
import org.activityinfo.legacy.client.remote.cache.CacheManager;
import org.activityinfo.legacy.client.remote.cache.CacheResult;
import org.activityinfo.legacy.client.remote.cache.CachingDispatcher;
import org.activityinfo.legacy.shared.command.Command;
import org.activityinfo.legacy.shared.command.GetSchema;
import org.activityinfo.legacy.shared.command.RemoteCommandServiceAsync;
import org.activityinfo.legacy.shared.command.result.CommandResult;
import org.activityinfo.legacy.shared.exception.CommandException;
import org.activityinfo.legacy.shared.model.SchemaDTO;
import org.activityinfo.legacy.shared.util.BackOff;
import org.activityinfo.legacy.shared.util.ExponentialBackOff;
import org.activityinfo.legacy.shared.util.NanoClock;
import org.activityinfo.model.auth.AuthenticatedUser;
import org.activityinfo.server.endpoint.gwtrpc.AdvisoryLock;
import org.activityinfo.ui.client.MockEventBus;
import org.easymock.Capture;
import org.easymock.IAnswer;
import org.junit.Before;
import org.junit.Test;
import java.util.Collections;
import static org.easymock.EasyMock.*;
public class RemoteDispatcherTest {
private static final String AUTH_TOKEN = "XYZ";
private RemoteCommandServiceAsync service;
private Dispatcher dispatcher;
private CommandCache proxy;
private CacheManager proxyManager = new CacheManager(new MockEventBus());
private Capture<AsyncCallback> remoteCallback = new Capture<AsyncCallback>();
private StubScheduler scheduler = new StubScheduler();
@Before
public void setUp() {
service = createMock("remoteService", RemoteCommandServiceAsync.class);
proxy = createMock("proxy", CommandCache.class);
AuthenticatedUser auth = new AuthenticatedUser(AUTH_TOKEN, 1,
"alex@alex.com");
BackOff backOff = new ExponentialBackOff.Builder()
.setInitialIntervalMillis(AdvisoryLock.ADVISORY_GET_LOCK_TIMEOUT)
.setMultiplier(2) // increase in 2 times
.setNanoClock(new NanoClock() {
@Override
public long nanoTime() {
return System.nanoTime();
}
})
.build();
dispatcher = new CachingDispatcher(proxyManager,
new MergingDispatcher(
new RemoteDispatcher(new MockEventBus(), auth, service),
scheduler,
backOff));
}
@Test
public void commandShouldBeSentToServerIfThereAreNoProxiesAndNoPendingCommands() {
// define our expectations
expectRemoteCall(new GetSchema());
replay(service);
// trigger a call
dispatcher.execute(new GetSchema(), makeNullCallback());
processPendingCommands();
// verify that the command was dispatched to the server
verify(service);
}
@Test
public void duplicateCommandsShouldBeMergedWithPendingRequests() {
expectRemoteCall(new GetSchema());
replay(service);
// simulate successive dispatches of the same command from different
// components of the application
dispatcher.execute(new GetSchema(), makeNullCallback());
dispatcher.execute(new GetSchema(), makeNullCallback());
processPendingCommands();
// verify that only one command was sent
verify(service);
}
@Test
public void duplicateCommandsShouldBeMergedWithExecutingRequests() {
expectRemoteCall(new GetSchema());
replay(service);
// simulate successive dispatches of the same command from different
// components of the application
dispatcher.execute(new GetSchema(), makeNullCallback());
processPendingCommands();
dispatcher.execute(new GetSchema(), makeNullCallback());
// verify that only one command was sent
verify(service);
}
@Test
public void mergedCommandsShouldEachReceiveACallback() {
expectRemoteCall(new GetSchema());
andCallbackWihSuccess(new SchemaDTO());
replay(service);
AsyncCallback callback1 = makeCallbackThatExpectsNonNullSuccess();
AsyncCallback callback2 = makeCallbackThatExpectsNonNullSuccess();
// simulate successive dispatches of the same command from different
// components of the application
dispatcher.execute(new GetSchema(), callback1);
dispatcher.execute(new GetSchema(), callback2);
processPendingCommands();
// verify that only one command was sent
verify(callback1);
verify(callback2);
}
@Test
public void successiveCommandsServedByProxyAreCorrectlyHandleded() {
GetSchema command = new GetSchema();
expect(proxy.maybeExecute(eq(command))).andReturn(
new CacheResult(new SchemaDTO())).anyTimes();
replay(proxy);
replay(service); // no calls should be made to the remote service
final AsyncCallback callback2 = makeCallbackThatExpectsNonNullSuccess();
proxyManager.registerProxy(GetSchema.class, proxy);
dispatcher.execute(new GetSchema(), new AsyncCallback<SchemaDTO>() {
@Override
public void onFailure(Throwable arg0) {
throw new AssertionError();
}
@Override
public void onSuccess(SchemaDTO arg0) {
dispatcher.execute(new GetSchema(), callback2);
}
});
processPendingCommands();
processPendingCommands();
verify(proxy, service, callback2);
}
@Test
public void commandsSuccessfullyExecutedThroughProxiesShouldNotBeSentToServer() {
GetSchema command = new GetSchema();
expect(proxy.maybeExecute(eq(command))).andReturn(
new CacheResult(new SchemaDTO()));
replay(proxy);
replay(service); // no calls should be made to the remote service
AsyncCallback callback = makeCallbackThatExpectsNonNullSuccess();
proxyManager.registerProxy(GetSchema.class, proxy);
dispatcher.execute(new GetSchema(), callback);
processPendingCommands();
verify(proxy, service, callback);
}
@Test
public void commandsUnsuccessfullyExecutedThroughProxiesShouldBeSentToServer() {
GetSchema command = new GetSchema();
expect(proxy.maybeExecute(eq(command))).andReturn(
CacheResult.couldNotExecute());
replay(proxy);
expectRemoteCall(command);
andCallbackWihSuccess(new SchemaDTO());
replay(service);
AsyncCallback callback = makeCallbackThatExpectsNonNullSuccess();
proxyManager.registerProxy(GetSchema.class, proxy);
dispatcher.execute(new GetSchema(), callback);
processPendingCommands();
verify(proxy, service, callback);
}
@Test
public void commandExceptionsShouldBeCalledBackWithFailure() {
expectRemoteCall(new GetSchema());
andCallbackWihSuccess(new CommandException()); // remote call succeeded,
// command failed
replay(service);
AsyncCallback callback = makeCallbackThatExpectsFailure();
dispatcher.execute(new GetSchema(), callback);
processPendingCommands();
verify(service, callback);
}
/**
* The RemoteDispatcher will group and bundle commands together-- we need to
* make sure that different components remain isolated from failures within
* other components.
*/
@Test
public void exceptionsThrownByCallbacksDoNotDistubOthers() {
expectRemoteCall(new GetSchema());
andCallbackWihSuccess(new SchemaDTO());
replay(service);
// Here we set up one component that will call request a command
// but something will go wrong when the command return (successfully)
// the error is unrelated to the remote command -- it just happens to be
// there.
dispatcher.execute(new GetSchema(), null,
new AsyncCallback<SchemaDTO>() {
@Override
public void onFailure(Throwable caught) {
}
@Override
public void onSuccess(SchemaDTO result) {
throw new RuntimeException();
}
});
// the second command independently requests the same command,
// we need to make sure we receive a result
AsyncCallback secondCallback = makeCallbackThatExpectsNonNullSuccess();
dispatcher.execute(new GetSchema(), null, secondCallback);
processPendingCommands();
verify(secondCallback);
}
private void processPendingCommands() {
for (RepeatingCommand command : scheduler.getRepeatingCommands()) {
command.execute();
}
}
private AsyncCallback<SchemaDTO> makeNullCallback() {
return new AsyncCallback<SchemaDTO>() {
@Override
public void onFailure(Throwable throwable) {
}
@Override
public void onSuccess(SchemaDTO o) {
}
};
}
private AsyncCallback makeCallbackThatExpectsNonNullSuccess() {
AsyncCallback callback = createMock(AsyncCallback.class);
callback.onSuccess(notNull());
replay(callback);
return callback;
}
private AsyncCallback makeCallbackThatExpectsFailure() {
AsyncCallback callback = createMock(AsyncCallback.class);
callback.onFailure(isA(Throwable.class));
replay(callback);
return callback;
}
private void expectRemoteCall(GetSchema command) {
service.execute(
eq(AUTH_TOKEN),
eq(Collections.<Command>singletonList(command)),
capture(remoteCallback));
}
private void andCallbackWihSuccess(final CommandResult result) {
expectLastCall().andAnswer(new IAnswer<Object>() {
@Override
public Object answer() throws Throwable {
((AsyncCallback) getCurrentArguments()[2])
.onSuccess(Collections.singletonList(result));
return null;
}
});
}
}