package com.workshare.msnos.core.routing;
import static com.workshare.msnos.core.CoreHelper.asPublicNetwork;
import static com.workshare.msnos.core.CoreHelper.createMockCloud;
import static com.workshare.msnos.core.CoreHelper.newAPPMesage;
import static com.workshare.msnos.core.CoreHelper.newAgentIden;
import static com.workshare.msnos.core.Message.Type.TRC;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import com.workshare.msnos.core.Cloud;
import com.workshare.msnos.core.Gateway;
import com.workshare.msnos.core.Identifiable;
import com.workshare.msnos.core.LocalAgent;
import com.workshare.msnos.core.Message;
import com.workshare.msnos.core.MessageBuilder;
import com.workshare.msnos.core.Receipt;
import com.workshare.msnos.core.RemoteAgent;
import com.workshare.msnos.core.Ring;
import com.workshare.msnos.core.cloud.MessageValidators;
import com.workshare.msnos.core.payloads.TracePayload;
import com.workshare.msnos.core.payloads.TracePayload.Crumb;
import com.workshare.msnos.core.protocols.ip.Endpoint;
import com.workshare.msnos.core.protocols.ip.HttpEndpoint;
import com.workshare.msnos.core.protocols.ip.Network;
import com.workshare.msnos.core.protocols.ip.http.HttpGateway;
import com.workshare.msnos.core.protocols.ip.udp.UDPGateway;
import com.workshare.msnos.core.protocols.ip.www.WWWGateway;
import com.workshare.msnos.core.receipts.SingleReceipt;
public abstract class RouterAbstractTest {
private static final Network PUBLIC_HOST = asPublicNetwork("25.25.25.25");
protected static final int MAXIMUM_HOPS_DIRECT = 11;
protected static final int MAXIMUM_HOPS_CLOUD = 3;
protected static final int MAXIMUM_MESSAGES_PER_RING = 2;
private Router router;
protected Cloud cloud;
protected Ring asia;
protected RemoteAgent asiaOne;
protected RemoteAgent asiaTwo;
protected Ring europe;
protected RemoteAgent europeOne;
protected RemoteAgent europeTwo;
protected Ring usa;
protected RemoteAgent usaOne;
protected RemoteAgent usaTwo;
protected RemoteAgent usaTre;
protected RemoteAgent usaFor;
protected UDPGateway udp;
protected HttpGateway http;
protected WWWGateway www;
protected LocalAgent self;
protected MessageValidators validators;
private Set<RemoteAgent> cloudAgents;
@Before
public void beforeEachTest() throws IOException {
Receipt receipt = mock(Receipt.class);
when(receipt.getStatus()).thenReturn(Message.Status.DELIVERED);
udp = mock(UDPGateway.class);
when(udp.send(any(Cloud.class), any(Message.class), any(Identifiable.class))).thenReturn(receipt);
when(udp.name()).thenReturn("UDP");
http = mock(HttpGateway.class);
when(http.send(any(Cloud.class), any(Message.class), any(Identifiable.class))).thenReturn(receipt);
when(http.name()).thenReturn("HTTP");
www = mock(WWWGateway.class);
when(www.send(any(Cloud.class), any(Message.class), any(Identifiable.class))).thenReturn(receipt);
when(www.name()).thenReturn("WWW");
cloud = createMockCloud();
cloudAgents = new HashSet<RemoteAgent>();
when(cloud.getRemoteAgents()).thenReturn(cloudAgents);
validators = mock(MessageValidators.class);
when(validators.isForwardable(any(Message.class))).thenReturn(MessageValidators.SUCCESS);
when(cloud.validators()).thenReturn(validators );
usa = Ring.random();
usaOne = installRemoteAgent(usa, "usaOne");
usaTwo = installRemoteAgent(usa, "usaTwo");
usaTre = installRemoteAgent(usa, "usaTre");
usaFor = installRemoteAgent(usa, "usaFor");
asia = Ring.random();
asiaOne = installRemoteAgent(asia, "asiaOne");
asiaTwo = installRemoteAgent(asia, "asiaTwo");
europe = cloud.getRing();
europeOne = installRemoteAgent(europe, "europeOne");
europeTwo = installRemoteAgent(europe, "europetwo");
self = mock(LocalAgent.class);
when(self.getIden()).thenReturn(newAgentIden());
when(self.getCloud()).thenReturn(cloud);
when(self.getRing()).thenReturn(europe);
System.setProperty(Router.SYSP_MAXIMUM_HOPS_DIRECT, Integer.toString(MAXIMUM_HOPS_DIRECT));
System.setProperty(Router.SYSP_MAXIMUM_HOPS_CLOUD, Integer.toString(MAXIMUM_HOPS_CLOUD));
System.setProperty(Router.SYSP_MAXIMUM_MESSAGES_PER_RING, Integer.toString(MAXIMUM_MESSAGES_PER_RING));
}
protected abstract Receipt process(Message message) throws IOException
;
@Test
public void shouldNotProcessMessagesWithZeroHops() throws Exception {
Message message = newAPPMesage(usaOne, asiaTwo).make().withHops(0);
process(message);
verifyZeroInteractions(udp, http, www);
}
@Test
public void shouldGoViaUDPBroadcastWithZeroHopsIfTargetIsInMyRing() throws Exception {
Message message = newAPPMesage(asiaOne, europeTwo).withHops(10).make();
process(message);
assertSentOnlyViaUDP(message, 0);
}
@Test
public void shouldGoViaHTTPWithZeroHopsIfTargetIsConnectedToMe() throws Exception {
connecMyselfViaHTTPTo(usaTwo);
Message message = newAPPMesage(asiaOne, usaTwo).withHops(10).make();
process(message);
assertSentOnlyViaHTTP(message, 0, usaTwo);
}
@Test
public void shouldGoViaHTTPWithOneHopIfTargetRingIsConnectedToMe() throws Exception {
connecMyselfViaHTTPTo(usaTwo);
Message message = newAPPMesage(asiaOne, usaOne).withHops(10).make();
process(message);
assertSentOnlyViaHTTP(message, 1, usaTwo);
}
@Test
public void shouldGoViaUDPOnWithZeroHopsIfTargetInMyRingAndNotConnectedToMe() throws Exception {
connecMyselfViaHTTPTo(europeTwo);
Message message = newAPPMesage(asiaOne, europeOne).withHops(10).make();
process(message);
assertSentOnlyViaUDP(message, 0);
}
@Test
public void shouldGoViaUDPBroadcastWithMaximumHopsIfNoConnectionAtAll() throws Exception {
Message message = newAPPMesage(asiaOne, usaOne).withHops(10).make();
process(message);
assertSentOnlyViaUDP(message, MAXIMUM_HOPS_DIRECT);
}
@Test
public void shouldGoViaUDPBroadcastWithMaximumHopsIfTargetUnknown() throws Exception {
Message message = newAPPMesage(asiaOne.getIden(), newAgentIden()).withHops(10).make();
process(message);
assertSentOnlyViaUDP(message, MAXIMUM_HOPS_DIRECT);
}
@Test
public void shouldGoViaWWWBroadcastWithUnchangedHops() throws Exception {
Message message = newAPPMesage(asiaOne, europeTwo).withHops(7).make();
process(message);
assertSentViaWWW(message, message.getHops());
}
@Test
public void shouldCloudMessageGoViaUDPBroadcastWithMaximumHopsIfNoConnectionAtAll() throws Exception {
Message message = newAPPMesage(asiaOne, cloud).withHops(10).make();
process(message);
assertSentOnlyViaUDP(message, MAXIMUM_HOPS_CLOUD);
}
@Test
public void shouldCloudMessageGoViaUDPBroadcastAndHTTPViaRingWithMaximumHopsIfConnectedToRings() throws Exception {
connecMyselfViaHTTPTo(usaTwo);
connecMyselfViaHTTPTo(asiaTwo);
Message message = newAPPMesage(europeOne, cloud).withHops(10).make();
process(message);
assertSentViaUDP(message, MAXIMUM_HOPS_CLOUD);
assertSentViaHTTP(message, MAXIMUM_HOPS_CLOUD, usaTwo);
assertSentViaHTTP(message, MAXIMUM_HOPS_CLOUD, asiaTwo);
assertEquals(2, anyMessagesOn(http).size());
}
@Test
public void shouldCloudMessageGoViaHTTPViaRingWithMaximumHopsIfConnectedToRingsWithMaximumTwoAgents() throws Exception {
connecMyselfViaHTTPTo(usaOne);
connecMyselfViaHTTPTo(usaTwo);
connecMyselfViaHTTPTo(usaTre);
connecMyselfViaHTTPTo(usaFor);
Message message = newAPPMesage(europeOne, cloud).withHops(10).make();
router().forward(message);
assertEquals(MAXIMUM_MESSAGES_PER_RING, anyMessagesOn(http).size());
}
@Test
public void shouldFallbackToUDPIfTargetIsConnectedToMeButSendFails() throws Exception {
connecMyselfViaHTTPTo(usaTwo);
Message message = newAPPMesage(asiaOne, usaTwo).withHops(10).make();
when(http.send(any(Cloud.class), any(Message.class), any(Identifiable.class))).thenReturn(SingleReceipt.failure(message));
router().forward(message);
assertSentViaUDP(message, MAXIMUM_HOPS_DIRECT);
}
@Test
public void shouldFallbackToHTTPViaRingIfTargetIsConnectedToMeButSendFails() throws Exception {
connecMyselfViaHTTPTo(usaTwo);
connecMyselfViaHTTPTo(usaTre);
Message message = newAPPMesage(asiaOne, usaTwo).withHops(10).make();
when(http.send(any(Cloud.class), any(Message.class), eq(usaTwo))).thenReturn(SingleReceipt.failure(message));
router().forward(message);
assertSentViaHTTP(message, 1, usaTre);
}
@Test
public void shouldFallbackToUDPIfTargetIsConnectedToMeButAllHttpSendFail() throws Exception {
connecMyselfViaHTTPTo(usaTwo);
connecMyselfViaHTTPTo(usaTre);
Message message = newAPPMesage(asiaOne, usaTwo).withHops(10).make();
when(http.send(any(Cloud.class), any(Message.class), any(Identifiable.class))).thenReturn(SingleReceipt.failure(message));
router().forward(message);
assertSentViaUDP(message, MAXIMUM_HOPS_DIRECT);
}
@Test
public void shouldSentTraceMessageCrumbed() throws Exception {
Message message = new MessageBuilder(TRC, usaOne, asiaTwo).withHops(10).with(new TracePayload(newAgentIden())).make();
process(message);
message = allMessages().iterator().next();
List<Crumb> crumbs = ((TracePayload)message.getData()).crumbs();
assertEquals(1, crumbs.size());
}
private RemoteAgent installRemoteAgent(final Ring ring, final String name) {
RemoteAgent remote = newRemoteAgent(ring);
when(remote.toString()).thenReturn(name);
when(cloud.getRemoteAgent(remote.getIden())).thenReturn(remote);
cloudAgents.add(remote);
return remote;
}
private RemoteAgent newRemoteAgent(Ring ring) {
RemoteAgent remote = mock(RemoteAgent.class);
when(remote.getCloud()).thenReturn(cloud);
when(remote.getRing()).thenReturn(ring);
when(remote.getIden()).thenReturn(newAgentIden());
return remote;
}
private void connecMyselfViaHTTPTo(RemoteAgent other) {
Set<Endpoint> points = new HashSet<Endpoint>();
points.add(new HttpEndpoint(PUBLIC_HOST, "http://url", other.getIden()));
when(other.getEndpoints(eq(Endpoint.Type.HTTP))).thenReturn(points );
}
protected Router router() {
if (router == null) {
router = new Router(cloud, udp, http, www);
}
return router;
}
protected void assertSentOnlyViaUDP(final Message message, final int hops) throws IOException {
verifyZeroInteractions(http);
assertSentViaUDP(message, hops);
assertEquals(1, anyMessagesOn(udp).size());
}
private void assertSentViaUDP(final Message message, final int hops) throws IOException {
final Message sent = findMessageOrFail(messagesOn(udp, null), message);
assertEquals(hops, sent.getHops());
}
private void assertSentOnlyViaHTTP(Message message, int hops, Identifiable to) throws IOException {
verifyZeroInteractions(udp);
assertSentViaHTTP(message, hops, to);
assertEquals(1, anyMessagesOn(http).size());
}
private void assertSentViaHTTP(Message message, int hops, Identifiable to) throws IOException {
Message sent = findMessageOrFail(messagesOn(http, to), message);
assertEquals(hops, sent.getHops());
}
protected void assertSentViaWWW(Message message, int hops) throws IOException {
Message sent = findMessageOrFail(messagesOn(www, null), message);
assertEquals(hops, sent.getHops());
}
private Message findMessageOrFail(List<Message> messages, Message tofind) {
for (Message message : messages) {
if (message.getUuid().equals(tofind.getUuid()))
return message;
}
throw new AssertionError("Message not found!");
}
private List<Message> messagesOn(final Gateway gate, Identifiable to) throws IOException {
ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
verify(gate, atMost(9999)).send(any(Cloud.class), captor.capture(), eq(to));
return captor.getAllValues();
}
protected List<Message> anyMessagesOn(final Gateway gate) throws IOException {
ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
verify(gate, atMost(9999)).send(any(Cloud.class), captor.capture(), any(Identifiable.class));
return captor.getAllValues();
}
protected Collection<Message> allMessages() throws IOException {
Map<UUID, Message> messages = new HashMap<UUID, Message>();
Gateway[] gates = new Gateway[]{udp, http, www};
for (Gateway gate: gates) {
for (Message message : anyMessagesOn(gate)) {
if (!messages.containsKey(message.getUuid()))
messages.put(message.getUuid(), message);
}
}
return messages.values();
}
}