/*
* Copyright (c) 2009-2011 Lockheed Martin Corporation
*
* 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 org.eurekastreams.commons.client;
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import java.io.Serializable;
import java.util.logging.Logger;
import org.eurekastreams.commons.exceptions.SessionException;
import org.eurekastreams.server.testing.ParamInterceptor;
import org.eurekastreams.server.testing.TestHelper;
import org.jmock.Expectations;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Before;
import org.junit.Test;
import com.google.gwt.user.client.rpc.AsyncCallback;
/**
* Tests ActionProcessorImpl.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ActionProcessorImplTest
{
/** Used for mocking objects. */
private final JUnit4Mockery context = new JUnit4Mockery()
{
{
setImposteriser(ClassImposteriser.INSTANCE);
}
};
/** Test data: action key. */
private static final String ACTION1 = "action1";
/** Test data: action key. */
private static final String ACTION2 = "action2";
/** Test data: action key. */
private static final String ACTION3 = "action3";
/** Test data: action key. */
private static final String ACTION4 = "action4";
/** Test data: session ID. */
private static final String SESSION_ID1 = "ABDEDB373";
/** Test data: session ID. */
private static final String SESSION_ID2 = "3983ABEDCFED";
/** Action parameter. */
private final Serializable param1 = context.mock(Serializable.class, "param1");
/** Action parameter. */
private final Serializable param2 = context.mock(Serializable.class, "param2");
/** Action result. */
private final Serializable result1 = context.mock(Serializable.class, "result1");
/** Action result. */
private final Serializable result2 = context.mock(Serializable.class, "result2");
/** ActionRPCServiceAsync. */
private final ActionRPCServiceAsync service = context.mock(ActionRPCServiceAsync.class);
/** Fixture: session established callback. */
private final AsyncCallback sessionCb = context.mock(AsyncCallback.class, "sessionCb");
/** Fixture: action callback. */
private final AsyncCallback actionCb1 = context.mock(AsyncCallback.class, "actionCb1");
/** Fixture: action callback. */
private final AsyncCallback actionCb2 = context.mock(AsyncCallback.class, "actionCb2");
/** Parameter interceptor. */
private final ParamInterceptor paramInt = new ParamInterceptor();
/** Fixture: log. */
private final Logger log = context.mock(Logger.class, "log");
/** SUT. */
private ActionProcessor sut;
/*
* Note: Many of the app callbacks are mocked to throw exceptions. This is done just to test the exception paths and
* insure that a ill-behaved callback doesn't interfere with the proper behavior of the SUT.
*/
/**
* Test setup.
*
* @throws NoSuchFieldException
* Only if SUT changes its log.
* @throws IllegalAccessException
* Shouldn't.
* @throws IllegalArgumentException
* Shouldn't.
*/
@Before
public final void setUp() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException
{
sut = new ActionProcessorImpl(service, sessionCb);
TestHelper.setPrivateField(sut, "log", log);
context.checking(new Expectations()
{
{
// mock logger and ignore log calls to keep errors from spilling into the output and looking like
// something actually went wrong. Would be ideal if the mock could be configured to ignore any
// trace/debug logging, but have expectations that some other logging would be done (but without
// specifying exactly which logging method is used) when a callback throws an exception.
ignoring(log);
}
});
}
/**
* Tests a normal startup sequence: send 2 messages before a session is established, everything succeeds.
*/
@Test
public void testNormalStartup()
{
sut.setQueueRequests(true);
sut.makeRequest(ACTION1, param1, actionCb1);
sut.makeRequest(ACTION2, param2, actionCb2);
context.checking(new Expectations()
{
{
oneOf(service).establishSession(with(any(AsyncCallback.class)));
will(paramInt);
}
});
sut.setQueueRequests(false);
context.assertIsSatisfied();
AsyncCallback<String> serviceSessionCb = (AsyncCallback<String>) paramInt.getParam(0);
context.checking(new Expectations()
{
{
oneOf(sessionCb).onSuccess(SESSION_ID1);
will(throwException(new RuntimeException("Naughty callback")));
oneOf(service).execute((ActionRequest[]) with(anything()), with(any(AsyncCallback.class)));
will(paramInt);
}
});
serviceSessionCb.onSuccess(SESSION_ID1);
context.assertIsSatisfied();
ActionRequest[] requests = (ActionRequest[]) paramInt.getParam(0);
AsyncCallback serviceExecCb = (AsyncCallback) paramInt.getParam(1);
assertEquals(2, requests.length);
assertEquals(ACTION1, requests[0].getActionKey());
assertSame(param1, requests[0].getParam());
assertEquals(SESSION_ID1, requests[0].getSessionId());
assertEquals(ACTION2, requests[1].getActionKey());
assertSame(param2, requests[1].getParam());
assertEquals(SESSION_ID1, requests[1].getSessionId());
ActionRequest[] results = new ActionRequest[] { new ActionResponse(requests[0], result1),
new ActionResponse(requests[1], result2) };
context.checking(new Expectations()
{
{
oneOf(actionCb1).onSuccess(with(same(result1)));
will(throwException(new RuntimeException("Naughty callback")));
oneOf(actionCb2).onSuccess(with(same(result2)));
will(throwException(new RuntimeException("Naughty callback")));
}
});
serviceExecCb.onSuccess(results);
context.assertIsSatisfied();
}
/**
* Tests losing a session and re-establishing it. Test adds an extra wrinkle by having the service reject the first
* attempt to re-establish.
*
* @throws IllegalAccessException
* Shouldn't.
* @throws IllegalArgumentException
* Shouldn't.
* @throws NoSuchFieldException
* Only if test is out of date.
* @throws SecurityException
* Shouldn't.
*/
@Test
public void testLoseSession() throws IllegalArgumentException, IllegalAccessException, SecurityException,
NoSuchFieldException
{
// cheat and set the session ID directly in the SUT to set up the initial state
TestHelper.setPrivateField(sut, "sessionId", SESSION_ID1);
// -- try to make request, but service will throw session exception --
context.checking(new Expectations()
{
{
oneOf(service).execute((ActionRequest[]) with(anything()), with(any(AsyncCallback.class)));
will(paramInt);
}
});
sut.makeRequest(ACTION1, param1, actionCb1);
context.assertIsSatisfied();
// -- SUT should ask service to establish new session --
ActionRequest[] requests = (ActionRequest[]) paramInt.getParam(0);
AsyncCallback serviceExecCb = (AsyncCallback) paramInt.getParam(1);
context.checking(new Expectations()
{
{
oneOf(service).establishSession(with(any(AsyncCallback.class)));
will(paramInt);
}
});
serviceExecCb.onSuccess(new ActionRequest[] { new ActionResponse(requests[0], new SessionException()) });
context.assertIsSatisfied();
// -- service replies with error. SUT should notify app --
AsyncCallback<String> serviceSessionCb = (AsyncCallback<String>) paramInt.getParam(0);
final Exception ex = new Exception("Can it handle it?");
context.checking(new Expectations()
{
{
oneOf(sessionCb).onFailure(with(equal(ex)));
will(throwException(new RuntimeException("Naughty callback")));
}
});
serviceSessionCb.onFailure(ex);
context.assertIsSatisfied();
// -- app asks SUT to try again --
context.checking(new Expectations()
{
{
oneOf(service).establishSession(with(any(AsyncCallback.class)));
will(paramInt);
}
});
sut.fireQueuedRequests();
context.assertIsSatisfied();
// -- service replies with success. SUT should notify app and then send the messages --
serviceSessionCb = (AsyncCallback<String>) paramInt.getParam(0);
context.checking(new Expectations()
{
{
oneOf(sessionCb).onSuccess(SESSION_ID2);
will(throwException(new RuntimeException("Naughty callback")));
oneOf(service).execute((ActionRequest[]) with(anything()), with(any(AsyncCallback.class)));
will(paramInt);
}
});
serviceSessionCb.onSuccess(SESSION_ID2);
context.assertIsSatisfied();
requests = (ActionRequest[]) paramInt.getParam(0);
assertEquals(1, requests.length);
assertEquals(ACTION1, requests[0].getActionKey());
assertSame(param1, requests[0].getParam());
assertEquals(SESSION_ID2, requests[0].getSessionId());
}
/**
* Tests session loss when multiple requests are out at once.
*
* @throws NoSuchFieldException
* Shouldn't.
* @throws IllegalAccessException
* Shouldn't.
* @throws IllegalArgumentException
* Shouldn't.
*/
@Test
public void testMultipleOutstandingRequests() throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException
{
// cheat and set the session ID directly in the SUT to set up the initial state
TestHelper.setPrivateField(sut, "sessionId", SESSION_ID1);
final Serializable param01 = context.mock(Serializable.class, "param01");
final Serializable param02 = context.mock(Serializable.class, "param02");
final Serializable param03 = context.mock(Serializable.class, "param03");
final Serializable param04 = context.mock(Serializable.class, "param04");
final Serializable result01 = context.mock(Serializable.class, "result01");
final Serializable result02 = context.mock(Serializable.class, "result02");
final Serializable result03 = context.mock(Serializable.class, "result03");
final Serializable result04 = context.mock(Serializable.class, "result04");
final AsyncCallback actionCb01 = context.mock(AsyncCallback.class, "actionCb01");
final AsyncCallback actionCb02 = context.mock(AsyncCallback.class, "actionCb02");
final AsyncCallback actionCb03 = context.mock(AsyncCallback.class, "actionCb03");
final AsyncCallback actionCb04 = context.mock(AsyncCallback.class, "actionCb04");
final ParamInterceptor paramInt01 = new ParamInterceptor();
final ParamInterceptor paramInt02 = new ParamInterceptor();
final ParamInterceptor paramInt03 = new ParamInterceptor();
final ParamInterceptor paramInt04 = new ParamInterceptor();
// -- send out a few requests --
context.checking(new Expectations()
{
{
oneOf(service).execute((ActionRequest[]) with(anything()), with(any(AsyncCallback.class)));
will(paramInt01);
oneOf(service).execute((ActionRequest[]) with(anything()), with(any(AsyncCallback.class)));
will(paramInt02);
oneOf(service).execute((ActionRequest[]) with(anything()), with(any(AsyncCallback.class)));
will(paramInt03);
oneOf(service).execute((ActionRequest[]) with(anything()), with(any(AsyncCallback.class)));
will(paramInt04);
}
});
sut.makeRequest(ACTION1, param01, actionCb01);
sut.makeRequest(ACTION2, param02, actionCb02);
sut.makeRequest(ACTION3, param03, actionCb03);
sut.makeRequest(ACTION4, param04, actionCb04);
context.assertIsSatisfied();
// -- make 1, 2, 4 fail with a session exception; 3 will succeed. once all are back, SUT should request session.
// out of order on purpose. --
// 1 fails
((AsyncCallback) paramInt01.getParam(1)).onSuccess(new ActionRequest[] { new ActionResponse(
((ActionRequest[]) paramInt01.getParam(0))[0], new SessionException()) });
// 4 fails
((AsyncCallback) paramInt04.getParam(1)).onSuccess(new ActionRequest[] { new ActionResponse(
((ActionRequest[]) paramInt04.getParam(0))[0], new SessionException()) });
// 3 succeeds
context.checking(new Expectations()
{
{
oneOf(actionCb03).onSuccess(with(same(result03)));
}
});
((AsyncCallback) paramInt03.getParam(1)).onSuccess(new ActionRequest[] { new ActionResponse(
((ActionRequest[]) paramInt03.getParam(0))[0], result03) });
context.assertIsSatisfied();
// 2 fails. since it's the last outstanding, the return will trigger re-establishment
context.checking(new Expectations()
{
{
oneOf(service).establishSession(with(any(AsyncCallback.class)));
will(paramInt);
}
});
((AsyncCallback) paramInt02.getParam(1)).onSuccess(new ActionRequest[] { new ActionResponse(
((ActionRequest[]) paramInt02.getParam(0))[0], new SessionException()) });
context.assertIsSatisfied();
// -- establishing new session should send request for all 3 that failed in one message --
context.checking(new Expectations()
{
{
oneOf(sessionCb).onSuccess(SESSION_ID2);
oneOf(service).execute((ActionRequest[]) with(anything()), with(any(AsyncCallback.class)));
will(paramInt);
}
});
((AsyncCallback) paramInt.getParam(0)).onSuccess(SESSION_ID2);
context.assertIsSatisfied();
ActionRequest[] requests = (ActionRequest[]) paramInt.getParam(0);
assertEquals(3, requests.length);
assertEquals(ACTION1, requests[0].getActionKey());
assertSame(param01, requests[0].getParam());
assertEquals(SESSION_ID2, requests[0].getSessionId());
assertEquals(ACTION2, requests[1].getActionKey());
assertSame(param02, requests[1].getParam());
assertEquals(SESSION_ID2, requests[1].getSessionId());
assertEquals(ACTION4, requests[2].getActionKey());
assertSame(param04, requests[2].getParam());
assertEquals(SESSION_ID2, requests[2].getSessionId());
// -- reply to all messages to insure callbacks working --
context.checking(new Expectations()
{
{
oneOf(actionCb01).onSuccess(with(same(result01)));
oneOf(actionCb02).onSuccess(with(same(result02)));
oneOf(actionCb04).onSuccess(with(same(result04)));
}
});
ActionRequest[] results = new ActionRequest[] { new ActionResponse(requests[0], result01),
new ActionResponse(requests[1], result02), new ActionResponse(requests[2], result04) };
((AsyncCallback) paramInt.getParam(1)).onSuccess(results);
context.assertIsSatisfied();
}
/**
* Tests that callbacks are not required. Use same basic scenario as startup test.
*
* @throws NoSuchFieldException
* Only if SUT changes its log.
* @throws IllegalAccessException
* Shouldn't.
* @throws IllegalArgumentException
* Shouldn't.
*/
@Test
public void testNoCallbacks() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException
{
sut = new ActionProcessorImpl(service, null);
TestHelper.setPrivateField(sut, "log", log);
// -- send over messages with no session set. SUT should request a session --
sut.setQueueRequests(true);
sut.makeRequest(ACTION1, param1, null);
sut.makeRequest(ACTION2, param2, null);
context.checking(new Expectations()
{
{
oneOf(service).establishSession(with(any(AsyncCallback.class)));
will(paramInt);
}
});
sut.setQueueRequests(false);
context.assertIsSatisfied();
// -- have server reject the request --
((AsyncCallback) paramInt.getParam(0)).onFailure(new Exception());
// -- make SUT try again --
context.checking(new Expectations()
{
{
oneOf(service).establishSession(with(any(AsyncCallback.class)));
will(paramInt);
}
});
sut.fireQueuedRequests();
context.assertIsSatisfied();
// -- accept this time. SUT should send messages --
context.checking(new Expectations()
{
{
oneOf(service).execute((ActionRequest[]) with(anything()), with(any(AsyncCallback.class)));
will(paramInt);
}
});
((AsyncCallback) paramInt.getParam(0)).onSuccess(SESSION_ID1);
context.assertIsSatisfied();
// -- reply to messages - one success and one failure --
ActionRequest[] requests = (ActionRequest[]) paramInt.getParam(0);
ActionRequest[] results = new ActionRequest[] { new ActionResponse(requests[0], new Exception()),
new ActionResponse(requests[1], result2) };
((AsyncCallback) paramInt.getParam(1)).onSuccess(results);
// -- try to send messages; there should be none. --
sut.fireQueuedRequests();
}
/**
* Tests some additional session establishment request cases, such as exceptions trying to send a message, and
* sending multiple session requests.
*
* @throws IllegalAccessException
* Shouldn't.
* @throws IllegalArgumentException
* Shouldn't.
* @throws NoSuchFieldException
* Only if test is out of date.
* @throws SecurityException
* Shouldn't.
*/
@Test
public void testSomeAdditionalSessionRequestCases() throws IllegalArgumentException, IllegalAccessException,
SecurityException, NoSuchFieldException
{
// cheat and set the session ID directly in the SUT to set up the initial state
TestHelper.setPrivateField(sut, "sessionId", SESSION_ID1);
// -- try to make request, but service will throw session exception --
context.checking(new Expectations()
{
{
oneOf(service).execute((ActionRequest[]) with(anything()), with(any(AsyncCallback.class)));
will(paramInt);
}
});
sut.makeRequest(ACTION1, param1, actionCb1);
context.assertIsSatisfied();
// -- return a session exception - SUT should ask service to establish new session, but have service throw an
// exception - SUT should tell app --
ActionRequest[] requests = (ActionRequest[]) paramInt.getParam(0);
AsyncCallback serviceExecCb = (AsyncCallback) paramInt.getParam(1);
final Exception ex1 = new RuntimeException("What will it do?");
context.checking(new Expectations()
{
{
oneOf(service).establishSession(with(any(AsyncCallback.class)));
will(throwException(ex1));
oneOf(sessionCb).onFailure(with(equal(ex1)));
}
});
serviceExecCb.onSuccess(new ActionRequest[] { new ActionResponse(requests[0], new SessionException()) });
context.assertIsSatisfied();
// -- app asks SUT to try again --
context.checking(new Expectations()
{
{
oneOf(service).establishSession(with(any(AsyncCallback.class)));
will(paramInt);
}
});
sut.fireQueuedRequests();
context.assertIsSatisfied();
// -- app gets impatient and asks SUT to try again again - SUT should do nothing --
sut.fireQueuedRequests();
// -- service replies with success. SUT should notify app and then send the messages --
AsyncCallback<String> serviceSessionCb = (AsyncCallback<String>) paramInt.getParam(0);
context.checking(new Expectations()
{
{
oneOf(sessionCb).onSuccess(SESSION_ID2);
oneOf(service).execute((ActionRequest[]) with(anything()), with(any(AsyncCallback.class)));
will(paramInt);
}
});
serviceSessionCb.onSuccess(SESSION_ID2);
context.assertIsSatisfied();
requests = (ActionRequest[]) paramInt.getParam(0);
assertEquals(1, requests.length);
assertEquals(ACTION1, requests[0].getActionKey());
assertSame(param1, requests[0].getParam());
assertEquals(SESSION_ID2, requests[0].getSessionId());
// -- server replies with wrong id - SUT should do nothing. --
ActionResponse result = new ActionResponse(requests[0], "Ha ha!");
result.setId((result.getId() + 9) * 9);
((AsyncCallback) paramInt.getParam(1)).onSuccess(new ActionRequest[] { result
});
}
/**
* Concrete class for testing.
*/
static class ActionResponse implements ActionRequest
{
/** id. */
int id;
/** actionKey. */
String actionKey;
/** param. */
Serializable param;
/** response. */
Serializable response;
/** sessionId. */
String sessionId;
/**
* Quasi-copy constructor (behaves generally like the server).
*
* @param orig
* Request to "clone".
* @param inResponse
* Response.
*/
public ActionResponse(final ActionRequest orig, final Serializable inResponse)
{
id = orig.getId();
actionKey = orig.getActionKey();
response = inResponse;
sessionId = orig.getSessionId();
}
/**
* @return the id
*/
public int getId()
{
return id;
}
/**
* @param inId
* the id to set
*/
public void setId(final int inId)
{
id = inId;
}
/**
* @return the actionKey
*/
public String getActionKey()
{
return actionKey;
}
/**
* @param inActionKey
* the actionKey to set
*/
public void setActionKey(final String inActionKey)
{
actionKey = inActionKey;
}
/**
* @return the param
*/
public Serializable getParam()
{
return param;
}
/**
* @param inParam
* the param to set
*/
public void setParam(final Serializable inParam)
{
param = inParam;
}
/**
* @return the response
*/
public Serializable getResponse()
{
return response;
}
/**
* @param inResponse
* the response to set
*/
public void setResponse(final Serializable inResponse)
{
response = inResponse;
}
/**
* @return the sessionId
*/
public String getSessionId()
{
return sessionId;
}
/**
* @param inSessionId
* the sessionId to set
*/
public void setSessionId(final String inSessionId)
{
sessionId = inSessionId;
}
}
}