package org.axonframework.springcloud.commandhandling;
import org.axonframework.commandhandling.CommandBus;
import org.axonframework.commandhandling.CommandCallback;
import org.axonframework.commandhandling.CommandMessage;
import org.axonframework.commandhandling.GenericCommandMessage;
import org.axonframework.commandhandling.callbacks.NoOpCallback;
import org.axonframework.commandhandling.distributed.Member;
import org.axonframework.commandhandling.distributed.SimpleMember;
import org.axonframework.messaging.MessageHandler;
import org.axonframework.serialization.*;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class SpringHttpCommandBusConnectorTest {
private static final String MEMBER_NAME = "memberName";
private static final URI ENDPOINT = URI.create("endpoint");
private static final Member DESTINATION = new SimpleMember<>(MEMBER_NAME, ENDPOINT, false, null);
private static final CommandMessage<String> COMMAND_MESSAGE = GenericCommandMessage.asCommandMessage("command");
private static final byte[] SERIALIZED_COMMAND_METADATA = {};
private static final byte[] SERIALIZED_COMMAND_PAYLOAD = {};
private static final String COMMAND_RESULT = "result";
private static final byte[] SERIALIZED_RESULT_DATA = new byte[]{};
private static final Exception COMMAND_ERROR = new Exception();
private static final byte[] SERIALIZED_ERROR_DATA = {};
@InjectMocks
private SpringHttpCommandBusConnector testSubject;
@Mock
private CommandBus localCommandBus;
@Mock
private RestTemplate restTemplate;
@Mock
private Serializer serializer;
private URI expectedUri;
@Mock
private SerializedObject<byte[]> serializedMetaData;
@Mock
private SerializedObject<byte[]> serializedPayload;
@Mock
private SerializedObject<byte[]> serializedResult;
@Mock
private SerializedObject<byte[]> serializedError;
@Mock
private CommandCallback<String, String> commandCallback;
@Mock
private MessageHandler<? super CommandMessage<?>> messageHandler;
@Before
public void setUp() throws Exception {
expectedUri = new URI(ENDPOINT.getScheme(), ENDPOINT.getUserInfo(), ENDPOINT.getHost(),
ENDPOINT.getPort(), ENDPOINT.getPath() + "/spring-command-bus-connector/command", null, null);
when(serializedMetaData.getContentType()).thenReturn(byte[].class);
when(serializedMetaData.getData()).thenReturn(SERIALIZED_COMMAND_METADATA);
SerializedType serializedPayloadType = mock(SerializedType.class);
when(serializedPayloadType.getName()).thenReturn(String.class.getName());
when(serializedPayloadType.getRevision()).thenReturn(null);
when(serializedPayload.getType()).thenReturn(serializedPayloadType);
when(serializedPayload.getContentType()).thenReturn(byte[].class);
when(serializedPayload.getData()).thenReturn(SERIALIZED_COMMAND_PAYLOAD);
SerializedType serializedResultType = mock(SerializedType.class);
when(serializedResultType.getName()).thenReturn(String.class.getName());
when(serializedResultType.getRevision()).thenReturn(null);
when(serializedResult.getType()).thenReturn(serializedResultType);
when(serializedResult.getData()).thenReturn(SERIALIZED_RESULT_DATA);
SerializedType serializedErrorType = mock(SerializedType.class);
when(serializedErrorType.getName()).thenReturn(Exception.class.getName());
when(serializedErrorType.getRevision()).thenReturn(null);
when(serializedError.getType()).thenReturn(serializedErrorType);
when(serializedError.getData()).thenReturn(SERIALIZED_ERROR_DATA);
when(serializer.serialize(COMMAND_MESSAGE.getMetaData(), byte[].class)).thenReturn(serializedMetaData);
when(serializer.serialize(COMMAND_MESSAGE.getPayload(), byte[].class)).thenReturn(serializedPayload);
when(serializer.serialize(COMMAND_RESULT, byte[].class)).thenReturn(serializedResult);
when(serializer.serialize(COMMAND_ERROR, byte[].class)).thenReturn(serializedError);
when(serializer.deserialize(new SimpleSerializedObject<>(SERIALIZED_COMMAND_PAYLOAD, byte[].class,
String.class.getName(), null))).thenReturn(COMMAND_MESSAGE.getPayload());
when(serializer.deserialize(new SerializedMetaData<>(SERIALIZED_COMMAND_METADATA, byte[].class)))
.thenReturn(COMMAND_MESSAGE.getMetaData());
when(serializer.getConverter()).thenReturn(new ChainingConverter());
}
@Test
public void testSendWithoutCallbackSucceeds() throws Exception {
HttpEntity<SpringHttpDispatchMessage> expectedHttpEntity = new HttpEntity<>(buildDispatchMessage(false));
testSubject.send(DESTINATION, COMMAND_MESSAGE);
verify(serializer).serialize(COMMAND_MESSAGE.getMetaData(), byte[].class);
verify(serializer).serialize(COMMAND_MESSAGE.getPayload(), byte[].class);
verify(restTemplate).exchange(eq(expectedUri), eq(HttpMethod.POST),
eq(expectedHttpEntity), argThat(new ParameterizedTypeReferenceMatcher<>()));
}
@Test(expected = IllegalArgumentException.class)
public void testSendWithoutCallbackThrowsExceptionForMissingDestinationURI() throws Exception {
SimpleMember<String> faultyDestination = new SimpleMember<>(MEMBER_NAME, null, false, null);
testSubject.send(faultyDestination, COMMAND_MESSAGE);
}
@Test
public void testSendWithCallbackSucceedsAndReturnsSucceeded() throws Exception {
SimpleSerializedObject<byte[]> expectedSerializedResult =
new SimpleSerializedObject<>(SERIALIZED_RESULT_DATA, byte[].class, String.class.getName(), null);
when(serializer.deserialize(expectedSerializedResult)).thenReturn(COMMAND_RESULT);
HttpEntity<SpringHttpDispatchMessage> expectedHttpEntity = new HttpEntity<>(buildDispatchMessage(true));
SpringHttpReplyMessage<String> testReplyMessage =
new SpringHttpReplyMessage<>(COMMAND_MESSAGE.getIdentifier(), true, COMMAND_RESULT, serializer);
ResponseEntity<SpringHttpReplyMessage<String>> testResponseEntity =
new ResponseEntity<>(testReplyMessage, HttpStatus.OK);
when(restTemplate.exchange(eq(expectedUri), eq(HttpMethod.POST), eq(expectedHttpEntity),
argThat(new ParameterizedTypeReferenceMatcher<String>()))).thenReturn(testResponseEntity);
testSubject.send(DESTINATION, COMMAND_MESSAGE, commandCallback);
verify(serializer).serialize(COMMAND_MESSAGE.getMetaData(), byte[].class);
verify(serializer).serialize(COMMAND_MESSAGE.getPayload(), byte[].class);
verify(restTemplate).exchange(eq(expectedUri), eq(HttpMethod.POST), eq(expectedHttpEntity),
argThat(new ParameterizedTypeReferenceMatcher<>()));
verify(serializer).deserialize(expectedSerializedResult);
verify(commandCallback).onSuccess(COMMAND_MESSAGE, COMMAND_RESULT);
}
@Test
public void testSendWithCallbackSucceedsAndReturnsFailed() throws Exception {
SimpleSerializedObject<byte[]> expectedSerializedError =
new SimpleSerializedObject<>(SERIALIZED_ERROR_DATA, byte[].class, Exception.class.getName(), null);
when(serializer.deserialize(expectedSerializedError)).thenReturn(COMMAND_ERROR);
HttpEntity<SpringHttpDispatchMessage> expectedHttpEntity = new HttpEntity<>(buildDispatchMessage(true));
SpringHttpReplyMessage<String> testReplyMessage =
new SpringHttpReplyMessage<>(COMMAND_MESSAGE.getIdentifier(), false, COMMAND_ERROR, serializer);
ResponseEntity<SpringHttpReplyMessage<String>> testResponseEntity =
new ResponseEntity<>(testReplyMessage, HttpStatus.OK);
when(restTemplate.exchange(eq(expectedUri), eq(HttpMethod.POST), eq(expectedHttpEntity),
argThat(new ParameterizedTypeReferenceMatcher<String>()))).thenReturn(testResponseEntity);
testSubject.send(DESTINATION, COMMAND_MESSAGE, commandCallback);
verify(serializer).serialize(COMMAND_MESSAGE.getMetaData(), byte[].class);
verify(serializer).serialize(COMMAND_MESSAGE.getPayload(), byte[].class);
verify(restTemplate).exchange(eq(expectedUri), eq(HttpMethod.POST), eq(expectedHttpEntity),
argThat(new ParameterizedTypeReferenceMatcher<>()));
verify(serializer).deserialize(expectedSerializedError);
verify(commandCallback).onFailure(COMMAND_MESSAGE, COMMAND_ERROR);
}
@Test(expected = IllegalArgumentException.class)
public void tesSendWithCallbackThrowsExceptionForMissingDestinationURI() throws Exception {
SimpleMember<String> faultyDestination = new SimpleMember<>(MEMBER_NAME, null, false, null);
testSubject.send(faultyDestination, COMMAND_MESSAGE, new NoOpCallback());
}
@Test
public void testSubscribeSubscribesCommandHandlerForCommandNameToLocalCommandBus() throws Exception {
String expectedCommandName = "commandName";
testSubject.subscribe(expectedCommandName, messageHandler);
verify(localCommandBus).subscribe(expectedCommandName, messageHandler);
}
@SuppressWarnings("unchecked")
@Test
public void testReceiveCommandHandlesCommandWithCallbackSucceedsAndCallsSuccess() throws Exception {
SimpleSerializedObject<byte[]> expectedSerializedResult =
new SimpleSerializedObject<>(SERIALIZED_RESULT_DATA, byte[].class, String.class.getName(), null);
when(serializer.deserialize(expectedSerializedResult)).thenReturn(COMMAND_RESULT);
doAnswer(a -> {
SpringHttpCommandBusConnector.SpringHttpReplyFutureCallback<String,String> callback =
(SpringHttpCommandBusConnector.SpringHttpReplyFutureCallback) a.getArguments()[1];
callback.onSuccess(COMMAND_MESSAGE, COMMAND_RESULT);
return a;
}).when(localCommandBus).dispatch(any(), any());
SpringHttpReplyMessage result =
(SpringHttpReplyMessage) testSubject.receiveCommand(buildDispatchMessage(true)).get();
assertEquals(COMMAND_MESSAGE.getIdentifier(), result.getCommandIdentifier());
assertTrue(result.isSuccess());
assertEquals(COMMAND_RESULT, result.getReturnValue(serializer));
verify(localCommandBus).dispatch(any(), any());
}
@Test
public void testReceiveCommandHandlesCommandWithCallbackSucceedsAndCallsFailure() throws Exception {
SimpleSerializedObject<byte[]> expectedSerializedError =
new SimpleSerializedObject<>(SERIALIZED_ERROR_DATA, byte[].class, Exception.class.getName(), null);
when(serializer.deserialize(expectedSerializedError)).thenReturn(COMMAND_ERROR);
doAnswer(a -> {
SpringHttpCommandBusConnector.SpringHttpReplyFutureCallback callback =
(SpringHttpCommandBusConnector.SpringHttpReplyFutureCallback) a.getArguments()[1];
callback.onFailure(COMMAND_MESSAGE, COMMAND_ERROR);
return a;
}).when(localCommandBus).dispatch(any(), any());
SpringHttpReplyMessage result =
(SpringHttpReplyMessage) testSubject.receiveCommand(buildDispatchMessage(true)).get();
assertEquals(COMMAND_MESSAGE.getIdentifier(), result.getCommandIdentifier());
assertFalse(result.isSuccess());
assertEquals(COMMAND_ERROR, result.getError(serializer));
verify(localCommandBus).dispatch(any(), any());
}
@Test
public void testReceiveCommandHandlesCommandWithCallbackFails() throws Exception {
doThrow(Exception.class).when(localCommandBus).dispatch(any(), any());
SpringHttpReplyMessage result =
(SpringHttpReplyMessage) testSubject.receiveCommand(buildDispatchMessage(true)).get();
assertEquals(COMMAND_MESSAGE.getIdentifier(), result.getCommandIdentifier());
assertFalse(result.isSuccess());
verify(localCommandBus).dispatch(any(), any());
}
@Test
public void testReceiveCommandHandlesCommandWithoutCallback() throws Exception {
String result = (String) testSubject.receiveCommand(buildDispatchMessage(false)).get();
assertEquals("", result);
verify(localCommandBus).dispatch(any());
}
@Test
public void testReceiveCommandHandlesCommandWithoutCallbackThrowsException() throws Exception {
doThrow(Exception.class).when(localCommandBus).dispatch(any());
SpringHttpReplyMessage result =
(SpringHttpReplyMessage) testSubject.receiveCommand(buildDispatchMessage(false)).get();
assertEquals(COMMAND_MESSAGE.getIdentifier(), result.getCommandIdentifier());
assertFalse(result.isSuccess());
verify(localCommandBus).dispatch(any());
}
@Test
public void tesSendWithCallbackToLocalMember() throws Exception {
SimpleMember<String> localDestination = new SimpleMember<>(MEMBER_NAME, null, true, null);
testSubject.send(localDestination, COMMAND_MESSAGE, new NoOpCallback());
verifyNoMoreInteractions(restTemplate);
verify(localCommandBus).dispatch(any(), any());
}
@Test
public void tesSendWithoutCallbackToLocalMember() throws Exception {
SimpleMember<String> localDestination = new SimpleMember<>(MEMBER_NAME, null, true, null);
testSubject.send(localDestination, COMMAND_MESSAGE);
verifyNoMoreInteractions(restTemplate);
verify(localCommandBus).dispatch(any());
}
private <C> SpringHttpDispatchMessage<C> buildDispatchMessage(boolean expectReply) {
return new SpringHttpDispatchMessage<>(COMMAND_MESSAGE, serializer, expectReply);
}
private class ParameterizedTypeReferenceMatcher<R> extends BaseMatcher<ParameterizedTypeReference<SpringHttpReplyMessage<R>>> {
private ParameterizedTypeReference<SpringHttpReplyMessage<R>> expected =
new ParameterizedTypeReference<SpringHttpReplyMessage<R>>() { };
@Override
public boolean matches(Object actual) {
return actual instanceof ParameterizedTypeReference &&
((ParameterizedTypeReference) actual).getType().getTypeName()
.equals(expected.getType().getTypeName());
}
@Override
public void describeTo(Description description) {
description.appendText("Failed to match expected ParameterizedTypeReference [" + expected + "]");
}
}
}