package com.temenos.interaction.core.rim;
/*
* #%L
* interaction-core
* %%
* Copyright (C) 2012 - 2013 Temenos Holdings N.V.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
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 javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import com.temenos.interaction.core.command.*;
import com.temenos.interaction.core.workflow.*;
import org.apache.wink.common.model.multipart.InMultiPart;
import org.apache.wink.common.model.multipart.InPart;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import com.temenos.interaction.core.MultivaluedMapImpl;
import com.temenos.interaction.core.command.InteractionCommand.Result;
import com.temenos.interaction.core.entity.Entity;
import com.temenos.interaction.core.entity.EntityMetadata;
import com.temenos.interaction.core.entity.Metadata;
import com.temenos.interaction.core.hypermedia.Action;
import com.temenos.interaction.core.hypermedia.BeanTransformer;
import com.temenos.interaction.core.hypermedia.ParameterAndValue;
import com.temenos.interaction.core.hypermedia.ResourceState;
import com.temenos.interaction.core.hypermedia.ResourceStateAndParameters;
import com.temenos.interaction.core.hypermedia.ResourceStateMachine;
import com.temenos.interaction.core.hypermedia.Transition;
import com.temenos.interaction.core.resource.EntityResource;
import com.temenos.interaction.core.web.RequestContext;
public class TestHTTPHypermediaRIM {
@Before
public void setup() {
// initialise the thread local request context with requestUri and
// baseUri
RequestContext ctx = new RequestContext("http://localhost/myservice.svc", "/baseuri/", null);
RequestContext.setRequestContext(ctx);
}
// create command returning the supplied entity
private InteractionCommand createCommand(final String entityName, final Entity entity, final InteractionCommand.Result result) {
InteractionCommand command = new InteractionCommand() {
public Result execute(InteractionContext ctx) {
if (entity == null) {
ctx.setResource(null);
} else {
ctx.setResource(new EntityResource<Entity>(entityName, entity));
}
return result;
}
};
return command;
}
private TransitionCommand createTransitionCommand(final String entityName, final Entity entity, final InteractionCommand.Result result) {
TransitionCommand command = new TransitionCommand() {
public Result execute(InteractionContext ctx) {
if (entity == null) {
ctx.setResource(null);
} else {
ctx.setResource(new EntityResource<Entity>(entityName, entity));
}
return result;
}
public boolean isInterim() {
return false;
}
};
return command;
}
private List<Action> mockActions() {
return mockActions(new Action("GET", Action.TYPE.VIEW),
new Action("PUT", Action.TYPE.ENTRY),
new Action("POST", Action.TYPE.ENTRY),
new Action("DELETE", Action.TYPE.ENTRY));
}
private List<Action> mockActions(Action...actions) {
List<Action> actionsList = new ArrayList<Action>();
for (Action a : actions) {
actionsList.add(a);
}
return actionsList;
}
private CommandController mockCommandController() {
return mockCommandController(createCommand("entity", null, Result.FAILURE));
}
private CommandController mockCommandController(InteractionCommand mockCommand) {
MapBasedCommandController cc = new MapBasedCommandController();
cc.getCommandMap().put("GET", mockCommand);
cc.getCommandMap().put("PUT", mockCommand);
cc.getCommandMap().put("POST", mockCommand);
cc.getCommandMap().put("DELETE", mockCommand);
return cc;
}
@Test
public void testResourcePath() throws InteractionException {
String ENTITY_NAME = "NOTE";
ResourceState initial = new ResourceState(ENTITY_NAME, "initial", mockActions(), "/notes/{id}");
HTTPHypermediaRIM resource = new HTTPHypermediaRIM(mockCommandController(), new ResourceStateMachine(initial),
createMockMetadata());
assertEquals("/notes/{id}", resource.getResourcePath());
}
/* We decode the query parameters to workaround an issue in Wink */
@SuppressWarnings({ "unchecked" })
@Test
public void testDecodeQueryParameters() throws InteractionException {
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/test");
// this test simply mocks a command to test the context query parameters
// is initialised properly
InteractionCommand mockCommand = mock(InteractionCommand.class);
when(mockCommand.execute(any(InteractionContext.class))).thenReturn(Result.FAILURE);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController(mockCommand), new ResourceStateMachine(
initialState), createMockMetadata());
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getPathParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
MultivaluedMap<String, String> queryMap = new MultivaluedMapImpl<String>();
queryMap.add("$filter", "this+that");
when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(queryMap);
rim.get(mock(HttpHeaders.class), "id", uriInfo);
verify(mockCommand).execute((InteractionContext) argThat(new InteractionContextArgumentMatcher()));
}
class InteractionContextArgumentMatcher extends ArgumentMatcher<InteractionContext> {
public boolean matches(Object o) {
if (o instanceof InteractionContext) {
InteractionContext ctx = (InteractionContext) o;
MultivaluedMap<String, String> mvmap = ctx.getQueryParameters();
if (!mvmap.getFirst("$filter").equals("this that")) {
return false;
}
return true;
}
return false;
}
}
/*
* We decode the query parameters containing escaped '%' to workaround an
* issue in Wink
*/
@SuppressWarnings({ "unchecked" })
@Test
public void testDecodeQueryParametersPercent() throws InteractionException {
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/test");
// this test simply mocks a command to test the context query parameters
// is initialised properly
InteractionCommand mockCommand = mock(InteractionCommand.class);
when(mockCommand.execute(any(InteractionContext.class))).thenReturn(Result.FAILURE);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController(mockCommand), new ResourceStateMachine(
initialState), createMockMetadata());
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getPathParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
MultivaluedMap<String, String> queryMap = new MultivaluedMapImpl<String>();
queryMap.add("$filter", "this%25that");
when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(queryMap);
rim.get(mock(HttpHeaders.class), "id", uriInfo);
verify(mockCommand).execute((InteractionContext) argThat(new InteractionContextArgumentMatcherPercent()));
}
class InteractionContextArgumentMatcherPercent extends ArgumentMatcher<InteractionContext> {
public boolean matches(Object o) {
if (o instanceof InteractionContext) {
InteractionContext ctx = (InteractionContext) o;
MultivaluedMap<String, String> mvmap = ctx.getQueryParameters();
if (!mvmap.getFirst("$filter").equals("this%that")) {
return false;
}
return true;
}
return false;
}
}
/*
* We decode the path parameters containing escaped '%' to workaround an
* issue in Wink.
*
* Because Wink itself decodes path parameters in the UriInfo we do NOT want
* to decode a second time. Expect the 'encoded' value back.
*/
@SuppressWarnings({ "unchecked" })
@Test
public void testDecodePathParametersPercent() throws InteractionException {
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/test");
// this test simply mocks a command to test the context query parameters
// is initialised properly
InteractionCommand mockCommand = mock(InteractionCommand.class);
when(mockCommand.execute(any(InteractionContext.class))).thenReturn(Result.FAILURE);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController(mockCommand), new ResourceStateMachine(
initialState), createMockMetadata());
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
// Mock decoded path map.
MultivaluedMap<String, String> decodedPathMap = new MultivaluedMapImpl<String>();
decodedPathMap.add("id", "ab%cd");
when(uriInfo.getPathParameters(true)).thenReturn(decodedPathMap);
// Mock encoded path map.
MultivaluedMap<String, String> encodedPathMap = new MultivaluedMapImpl<String>();
encodedPathMap.add("id", "ab%25cd");
when(uriInfo.getPathParameters(false)).thenReturn(encodedPathMap);
rim.get(mock(HttpHeaders.class), "id", uriInfo);
verify(mockCommand).execute((InteractionContext) argThat(new InteractionContextArgumentPathMatcherPercent()));
}
class InteractionContextArgumentPathMatcherPercent extends ArgumentMatcher<InteractionContext> {
public boolean matches(Object o) {
if (o instanceof InteractionContext) {
InteractionContext ctx = (InteractionContext) o;
MultivaluedMap<String, String> mvmap = ctx.getPathParameters();
if (!mvmap.getFirst("id").equals("ab%cd")) {
return false;
}
return true;
}
return false;
}
}
/* We decode the query parameters to workaround an issue in Wink */
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void testDecodeQueryParametersNullValue() {
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/test");
// RIM with command controller that issues commands that always return
// FAILURE
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController(), new ResourceStateMachine(initialState),
createMockMetadata());
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getPathParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
MultivaluedMap<String, String> queryMap = new MultivaluedMapImpl();
queryMap.add(null, null);
when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(queryMap);
// should get past here without a NullPointerException
rim.get(mock(HttpHeaders.class), "id", uriInfo);
}
/*
* This test is for a GET request where the command succeeds, but does not
* return a resource. A successful GET command should set the requested
* resource onto the InteractionContext; if it does not expect NO_CONTENT.
*/
@Test
public void testSuccessfulGETCommandNoResourceShouldFail() throws Exception {
ResourceState initialState = new ResourceState("entity", "state", mockActions(new Action("GET", Action.TYPE.VIEW)), "/path");
InteractionCommand mockCommand = createCommand("entity", null, Result.SUCCESS);
// create mock command controller
CommandController mockCommandController = mockCommandController(mockCommand);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController, new ResourceStateMachine(initialState),
createMockMetadata());
Response response = rim.get(mock(HttpHeaders.class), "id", mockEmptyUriInfo());
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
/*
* This test is for a GET request where the command does not return a
* result.
*/
@Test(expected = AssertionError.class)
public void testGETCommandNoResultShouldFail() throws Exception {
List<Action> actions = new ArrayList<Action>();
actions.add(new Action("GET", Action.TYPE.VIEW));
ResourceState initialState = new ResourceState("entity", "state", actions, "/path");
// this test mocks a command that incorrectly returns no result
InteractionCommand mockCommand = mock(InteractionCommand.class);
// create mock command controller
CommandController mockCommandController = mock(CommandController.class);
when(mockCommandController.fetchCommand("GET")).thenReturn(mockCommand);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController, new ResourceStateMachine(initialState),
createMockMetadata());
rim.get(mock(HttpHeaders.class), "id", mockEmptyUriInfo());
}
/*
* This test is for a GET request where the command does not return a
* result.
*/
@Test(expected = AssertionError.class)
public void testDELETECommandNoResultShouldFail() throws Exception {
List<Action> actions = new ArrayList<Action>();
actions.add(new Action("DELETE", Action.TYPE.ENTRY));
ResourceState initialState = new ResourceState("entity", "state", actions, "/path");
initialState.addTransition(new Transition.Builder().method("DELETE").target(initialState).build());
// this test mocks a command that incorrectly returns no result
InteractionCommand mockCommand = mock(InteractionCommand.class);
// create mock command controller
CommandController mockCommandController = mock(CommandController.class);
when(mockCommandController.fetchCommand("DELETE")).thenReturn(mockCommand);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController, new ResourceStateMachine(initialState),
createMockMetadata());
rim.delete(mock(HttpHeaders.class), "id", mockEmptyUriInfo());
}
@Test
public void testGETCommandInvalidRequest() throws Exception {
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/path");
// this test incorrectly supplies a resource as a result of the command.
InteractionCommand mockCommand = new InteractionCommand() {
public Result execute(InteractionContext ctx) {
ctx.setResource(null);
return Result.INVALID_REQUEST;
}
};
// create mock command controller
CommandController mockCommandController = mockCommandController(mockCommand);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController, new ResourceStateMachine(initialState),
createMockMetadata());
Response response = rim.get(mock(HttpHeaders.class), "id", mockEmptyUriInfo());
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
}
/*
* This test is for a GET request where the command succeeds. A successful
* GET command should set the requested resource onto the
* InteractionContext.
*/
public void testSuccessfulGETCommand() throws Exception {
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/path");
// this test incorrectly supplies a resource as a result of the command.
InteractionCommand mockCommand = new InteractionCommand() {
public Result execute(InteractionContext ctx) {
ctx.setResource(new EntityResource<Object>());
return Result.SUCCESS;
}
};
// create mock command controller
CommandController mockCommandController = mock(CommandController.class);
when(mockCommandController.fetchCommand("DO")).thenReturn(mockCommand);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController, new ResourceStateMachine(initialState),
mock(Metadata.class));
Response response = rim.get(mock(HttpHeaders.class), "id", mockEmptyUriInfo());
assertNotNull(response.getEntity());
}
/*
* This test is for a DELETE request where the command returns a resource. A
* successful DELETE command should not return a new resource and we test
* this with an assertion.
*/
@Test(expected = AssertionError.class)
public void testDeleteCommandReturnsResourceShouldFail() throws Exception {
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/path");
initialState.addTransition(new Transition.Builder().method("DELETE").target(initialState).build());
// this test incorrectly supplies a resource as a result of the command.
InteractionCommand mockCommand = new InteractionCommand() {
public Result execute(InteractionContext ctx) {
ctx.setResource(new EntityResource<Object>());
return Result.SUCCESS;
}
};
// create mock command controller
CommandController mockCommandController = mockCommandController(mockCommand);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController, new ResourceStateMachine(initialState),
createMockMetadata());
rim.delete(mock(HttpHeaders.class), "id", mockEmptyUriInfo());
}
/*
* Test the contract for PUT commands. A PUT command should should receive
* an InteractionContext that has the new resource set; enabling the command
* to getResource.
*/
@SuppressWarnings("unchecked")
@Test
public void testPutCommandReceivesResource() throws InteractionException {
ResourceState initialState = new ResourceState("entity", "state", mockActions(new Action("PUT", Action.TYPE.ENTRY)), "/test");
initialState.addTransition(new Transition.Builder().method("PUT").target(initialState).build());
// create a mock command to test the context is initialised correctly
InteractionCommand mockCommand = mock(InteractionCommand.class);
when(mockCommand.execute(any(InteractionContext.class))).thenReturn(Result.SUCCESS);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController(mockCommand), new ResourceStateMachine(
initialState), createMockMetadata());
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getPathParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
rim.put(mock(HttpHeaders.class), "id", uriInfo, new EntityResource<Object>("test resource"));
verify(mockCommand).execute((InteractionContext) argThat(new CommandReceivesResourceArgumentMatcher()));
}
/*
* Test the contract for multipart PUT commands. A PUT command should should
* receive an InteractionContext that has the new resource set; enabling the
* command to process the resource contained in the current part of the
* multipart request
*/
@SuppressWarnings("unchecked")
@Test
public void testMultipartPutCommandReceivesResource() throws InteractionException {
ResourceState initialState = new ResourceState("entity", "state", mockActions(new Action("PUT", Action.TYPE.ENTRY)), "/test");
initialState.addTransition(new Transition.Builder().method("PUT").target(initialState).build());
// create a mock command to test the context is initialised correctly
InteractionCommand mockCommand = mock(InteractionCommand.class);
when(mockCommand.execute(any(InteractionContext.class))).thenReturn(Result.SUCCESS);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController(mockCommand), new ResourceStateMachine(
initialState), createMockMetadata());
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getPathParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
InMultiPart inMP = mock(InMultiPart.class);
when(inMP.hasNext()).thenReturn(true, false);
when(inMP.next()).thenReturn(mock(InPart.class));
rim.put(mock(HttpHeaders.class), uriInfo, inMP);
verify(mockCommand).execute((InteractionContext) argThat(new CommandReceivesResourceArgumentMatcher()));
}
/*
* Test the contract for multipart POST commands. A POST command should
* should receive an InteractionContext that has the new resource set;
* enabling the command to process the resource contained in the current
* part of the multipart request
*/
@SuppressWarnings("unchecked")
@Test
public void testMultipartPostCommandReceivesResource() throws InteractionException {
ResourceState initialState = new ResourceState("entity", "state", mockActions(new Action("PUT", Action.TYPE.ENTRY)), "/test");
initialState.addTransition(new Transition.Builder().method("POST").target(initialState).build());
// create a mock command to test the context is initialised correctly
InteractionCommand mockCommand = mock(InteractionCommand.class);
when(mockCommand.execute(any(InteractionContext.class))).thenReturn(Result.SUCCESS);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController(mockCommand), new ResourceStateMachine(
initialState), createMockMetadata());
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getPathParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
InMultiPart inMP = mock(InMultiPart.class);
when(inMP.hasNext()).thenReturn(true, false);
when(inMP.next()).thenReturn(mock(InPart.class));
rim.post(mock(HttpHeaders.class), uriInfo, inMP);
verify(mockCommand).execute((InteractionContext) argThat(new CommandReceivesResourceArgumentMatcher()));
}
class CommandReceivesResourceArgumentMatcher extends ArgumentMatcher<InteractionContext> {
public boolean matches(Object o) {
if (o instanceof InteractionContext) {
InteractionContext ctx = (InteractionContext) o;
if (ctx.getResource() == null)
return false;
return true;
}
return false;
}
}
/*
* Test the contract for POST commands. A POST command should could receive
* an InteractionContext that has the new resource set; enabling the command
* to getResource.
*/
@SuppressWarnings("unchecked")
@Test
public void testPOSTCommandReceivesResource() throws InteractionException {
List<Action> actions = mockActions(new Action("POST", Action.TYPE.ENTRY));
ResourceState initialState = new ResourceState("entity", "state", actions, "/test");
initialState.addTransition(new Transition.Builder().method("POST").target(initialState).build());
// create a mock command to test the context is initialised correctly
InteractionCommand mockCommand = mock(InteractionCommand.class);
when(mockCommand.execute(any(InteractionContext.class))).thenReturn(Result.SUCCESS);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController(mockCommand), new ResourceStateMachine(
initialState), createMockMetadata());
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getPathParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
rim.post(mock(HttpHeaders.class), "id", uriInfo, new EntityResource<Object>("test resource"));
verify(mockCommand).execute((InteractionContext) argThat(new CommandReceivesResourceArgumentMatcher()));
}
@Test
public void testPOSTCommandCreate() throws Exception {
// this test incorrectly supplies a resource as a result of the command.
InteractionCommand mockCommand = new InteractionCommand() {
public Result execute(InteractionContext ctx) {
ctx.setResource(new EntityResource<>());
return Result.CREATED;
}
};
// create mock command controller
CommandController mockCommandController = mockCommandController(mockCommand);
// create a state machine with a POST interaction
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/path");
ResourceState newState = new ResourceState("entity", "new", mockActions(), "/path");
initialState.addTransition(new Transition.Builder().method("POST").target(newState).build());
ResourceStateMachine stateMachine = new ResourceStateMachine(initialState);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController, stateMachine, createMockMetadata());
Response response = rim.post(mock(HttpHeaders.class), "id", mockEmptyUriInfo(), mock(EntityResource.class));
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
}
@Test
public void testPOSTCommandCreateNoContent() throws Exception {
// this test incorrectly supplies a resource as a result of the command.
InteractionCommand mockCommand = new InteractionCommand() {
public Result execute(InteractionContext ctx) {
ctx.setResource(null);
return Result.CREATED;
}
};
// create mock command controller
CommandController mockCommandController = mockCommandController(mockCommand);
// create a state machine with a POST interaction
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/path");
ResourceState newState = new ResourceState("entity", "new", mockActions(), "/path");
initialState.addTransition(new Transition.Builder().method("POST").target(newState).build());
ResourceStateMachine stateMachine = new ResourceStateMachine(initialState);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController, stateMachine, createMockMetadata());
Response response = rim.post(mock(HttpHeaders.class), "id", mockEmptyUriInfo(), mock(EntityResource.class));
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
@Test(expected = RuntimeException.class)
public void testBootstrapInvalidCommandControllerConfigurationPUT() {
String resourcePath = "/notes/{id}";
ResourceState exists = new ResourceState("entity", "exists", mockActions(), resourcePath);
exists.addTransition(new Transition.Builder().method("PUT").target(exists).build());
CommandController cc = mock(CommandController.class);
new HTTPHypermediaRIM(cc, new ResourceStateMachine(exists), mock(Metadata.class));
}
@Test(expected = RuntimeException.class)
public void testBootstrapInvalidCommandControllerConfigurationPOST() {
String resourcePath = "/notes/{id}";
ResourceState exists = new ResourceState("entity", "exists", mockActions(), resourcePath);
exists.addTransition(new Transition.Builder().method("POST").target(exists).build());
CommandController cc = mock(CommandController.class);
new HTTPHypermediaRIM(cc, new ResourceStateMachine(exists), mock(Metadata.class));
}
@Test(expected = RuntimeException.class)
public void testBootstrapInvalidCommandControllerConfigurationDELETE() {
String resourcePath = "/notes/{id}";
ResourceState exists = new ResourceState("entity", "exists", mockActions(), resourcePath);
exists.addTransition(new Transition.Builder().method("DELETE").target(exists).build());
CommandController cc = mock(CommandController.class);
new HTTPHypermediaRIM(cc, new ResourceStateMachine(exists), mock(Metadata.class));
}
@Test(expected = RuntimeException.class)
public void testBootstrapInvalidCommandControllerConfigurationGET() {
String resourcePath = "/notes/{id}";
ResourceState exists = new ResourceState("entity", "exists", mockActions(), resourcePath);
exists.addTransition(new Transition.Builder().method("GET").target(exists).build());
CommandController cc = mock(CommandController.class);
new HTTPHypermediaRIM(cc, new ResourceStateMachine(exists), mock(Metadata.class));
}
@Test
public void testChildrenRIMsSubstate() {
String ENTITY_NAME = "DraftNote";
String resourcePath = "/notes/{id}";
ResourceState initial = new ResourceState(ENTITY_NAME, "initial", mockActions(), resourcePath);
ResourceState draft = new ResourceState(ENTITY_NAME, "draft", mockActions(), "/draft");
// create draft
initial.addTransition(new Transition.Builder().method("PUT").target(draft).build());
// updated draft
draft.addTransition(new Transition.Builder().method("PUT").target(draft).build());
// supply a transformer to check that this is copied into child resource
BeanTransformer transformer = new BeanTransformer();
ResourceStateMachine stateMachine = new ResourceStateMachine(initial, transformer);
HTTPHypermediaRIM parent = new HTTPHypermediaRIM(mockCommandController(), stateMachine, createMockMetadata());
Collection<ResourceInteractionModel> resources = parent.getChildren();
assertEquals(1, resources.size());
assertEquals(draft.getPath(), resources.iterator().next().getResourcePath());
assertEquals(transformer, ((HTTPHypermediaRIM) resources.iterator().next()).getHypermediaEngine()
.getTransformer());
}
@Test
public void testChildrenRIMsDifferentEntity() {
ResourceState initial = new ResourceState("Note", "initial", mockActions(), "/note/{id}");
ResourceState comment = new ResourceState("Comment", "draft", mockActions(), "/comments/{noteid}");
// example uri linkage uses 'id' from Note entity to transition to
// 'noteid' of comments resource
Map<String, String> uriLinkageMap = new HashMap<String, String>();
uriLinkageMap.put("noteid", "id");
// create comment for note
initial.addTransition(new Transition.Builder().method("PUT").target(comment).uriParameters(uriLinkageMap)
.build());
// update comment
comment.addTransition(new Transition.Builder().method("PUT").target(comment).build());
// supply a transformer to check that this is copied into child resource
BeanTransformer transformer = new BeanTransformer();
ResourceStateMachine stateMachine = new ResourceStateMachine(initial, transformer);
HTTPHypermediaRIM parent = new HTTPHypermediaRIM(mockCommandController(), stateMachine, createMockMetadata());
Collection<ResourceInteractionModel> resources = parent.getChildren();
assertEquals(1, resources.size());
assertEquals(comment.getPath(), resources.iterator().next().getResourcePath());
assertEquals(transformer, ((HTTPHypermediaRIM) resources.iterator().next()).getHypermediaEngine()
.getTransformer());
}
@Test
public void testPUTCommandCreate() throws Exception {
MapBasedCommandController commandController = new MapBasedCommandController();
commandController.getCommandMap().put("GET", createCommand("entity", new Entity("entity", null), Result.SUCCESS));
commandController.getCommandMap().put("PUT", createCommand("entity", new Entity("entity", null), Result.CREATED));
// create a state machine with a POST interaction
ResourceState initialState = new ResourceState("entity", "state", mockActions(new Action("GET", Action.TYPE.VIEW)), "/path");
ResourceState newState = new ResourceState("entity", "new", mockActions(new Action("PUT", Action.TYPE.ENTRY)), "/path");
initialState.addTransition(new Transition.Builder().method("PUT").target(newState).build());
ResourceStateMachine stateMachine = new ResourceStateMachine(initialState);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(commandController, stateMachine, createMockMetadata());
Response response = rim.put(mock(HttpHeaders.class), "id", mockEmptyUriInfo(), mock(EntityResource.class));
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
}
@Test
public void testPUTCommandCreateNoContent() throws Exception {
MapBasedCommandController commandController = new MapBasedCommandController();
commandController.getCommandMap().put("GET", createCommand("entity", new Entity("entity", null), Result.SUCCESS));
// put command returns no resource
commandController.getCommandMap().put("PUT", createCommand("entity", null, Result.CREATED));
// create a state machine with a POST interaction
ResourceState initialState = new ResourceState("entity", "state", mockActions(new Action("GET", Action.TYPE.VIEW)), "/path");
ResourceState newState = new ResourceState("entity", "new", mockActions(new Action("PUT", Action.TYPE.ENTRY)), "/path");
initialState.addTransition(new Transition.Builder().method("PUT").target(newState).build());
ResourceStateMachine stateMachine = new ResourceStateMachine(initialState);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(commandController, stateMachine, createMockMetadata());
Response response = rim.put(mock(HttpHeaders.class), "id", mockEmptyUriInfo(), mock(EntityResource.class));
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
@Test
public void testPUTCommandCreateNoContentAuto() throws Exception {
MapBasedCommandController commandController = new MapBasedCommandController();
// get command returns no resource
commandController.getCommandMap().put("GET", createTransitionCommand("entity", null, Result.SUCCESS));
commandController.getCommandMap().put("PUT", createCommand("entity", new Entity("entity", null), Result.CREATED));
// create a state machine with a POST interaction
ResourceState initialState = new ResourceState("entity", "state", mockActions(new Action("GET", Action.TYPE.VIEW)), "/path");
ResourceState newState = new ResourceState("entity", "new", mockActions(new Action("PUT", Action.TYPE.ENTRY)), "/path");
ResourceState entityState = new ResourceState("entity", "entity", mockActions(new Action("GET", Action.TYPE.VIEW)), "/path");
initialState.addTransition(new Transition.Builder().method("PUT").target(newState).build());
newState.addTransition(new Transition.Builder().target(entityState).flags(Transition.AUTO).build());
ResourceStateMachine stateMachine = new ResourceStateMachine(initialState);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(commandController, stateMachine, createMockMetadata());
Response response = rim.put(mock(HttpHeaders.class), "id", mockEmptyUriInfo(), mock(EntityResource.class));
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
@Test
public void testPUTCommandCreateAuto() throws Exception {
MapBasedCommandController commandController = new MapBasedCommandController();
commandController.getCommandMap().put("GET", createCommand("entity", new Entity("entity", null), Result.SUCCESS));
commandController.getCommandMap().put("PUT", createCommand("entity", new Entity("entity", null), Result.CREATED));
// create a state machine with a POST interaction
ResourceState initialState = new ResourceState("entity", "state", mockActions(new Action("GET", Action.TYPE.VIEW)), "/path");
ResourceState newState = new ResourceState("entity", "new", mockActions(new Action("PUT", Action.TYPE.ENTRY)), "/path");
ResourceState entityState = new ResourceState("entity", "entity", mockActions(new Action("GET", Action.TYPE.VIEW)), "/path");
initialState.addTransition(new Transition.Builder().method("PUT").target(newState).build());
newState.addTransition(new Transition.Builder().target(entityState).flags(Transition.AUTO).build());
ResourceStateMachine stateMachine = new ResourceStateMachine(initialState);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(commandController, stateMachine, createMockMetadata());
Response response = rim.put(mock(HttpHeaders.class), "id", mockEmptyUriInfo(), mock(EntityResource.class));
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
}
@Test
public void testPUTCommandConflict() throws Exception {
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/path");
// this test incorrectly supplies a resource as a result of the command.
InteractionCommand mockCommand = new InteractionCommand() {
public Result execute(InteractionContext ctx) {
ctx.setResource(null);
return Result.CONFLICT;
}
};
// create mock command controller
CommandController mockCommandController = mockCommandController(mockCommand);
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController, new ResourceStateMachine(initialState),
createMockMetadata());
Response response = rim.get(mock(HttpHeaders.class), "id", mockEmptyUriInfo());
assertEquals(Status.PRECONDITION_FAILED.getStatusCode(), response.getStatus());
}
@SuppressWarnings("unchecked")
@Test
public void testPutCommandWithIfMatchHeader() throws InteractionException {
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/test");
initialState.addTransition(new Transition.Builder().method("PUT").target(initialState).build());
// this test incorrectly supplies a resource as a result of the command.
InteractionCommand mockCommand = new InteractionCommand() {
public Result execute(InteractionContext ctx) {
assertNotNull(ctx.getResource());
assertNull(ctx.getResource().getEntityTag()); // Etag is a
// response header
// and should be
// null
assertNotNull(ctx.getPreconditionIfMatch());
assertEquals("ABCDEFG", ctx.getPreconditionIfMatch());
return Result.SUCCESS;
}
};
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController(mockCommand), new ResourceStateMachine(
initialState), createMockMetadata());
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getPathParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
// EntityResource without Etag
EntityResource<Object> er = new EntityResource<Object>("test resource");
// Apply If-Match header
HttpHeaders httpHeaders = mock(HttpHeaders.class);
doAnswer(new Answer<List<String>>() {
@SuppressWarnings("serial")
@Override
public List<String> answer(InvocationOnMock invocation) throws Throwable {
String headerName = (String) invocation.getArguments()[0];
if (headerName.equals(HttpHeaders.IF_MATCH)) {
return new ArrayList<String>() {
{
add("ABCDEFG");
}
};
}
return null;
}
}).when(httpHeaders).getRequestHeader(any(String.class));
// execute
rim.put(httpHeaders, "id", uriInfo, er);
}
@SuppressWarnings("unchecked")
@Test
public void testPutCommandWithEtag() throws InteractionException {
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/test");
initialState.addTransition(new Transition.Builder().method("PUT").target(initialState).build());
// this test incorrectly supplies a resource as a result of the command.
InteractionCommand mockCommand = new InteractionCommand() {
public Result execute(InteractionContext ctx) {
assertNotNull(ctx.getResource());
assertNotNull(ctx.getPreconditionIfMatch());
assertEquals("ABCDEFG", ctx.getPreconditionIfMatch());
return Result.SUCCESS;
}
};
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController(mockCommand), new ResourceStateMachine(
initialState), createMockMetadata());
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getPathParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
// EntityResource with Etag - etag is a response header and should not
// be used on requests
EntityResource<Object> er = new EntityResource<Object>("test resource");
er.setEntityTag("IJKLMNO"); // This should not override the If-Match
// header
// Apply If-Match header
HttpHeaders httpHeaders = mock(HttpHeaders.class);
doAnswer(new Answer<List<String>>() {
@SuppressWarnings("serial")
@Override
public List<String> answer(InvocationOnMock invocation) throws Throwable {
String headerName = (String) invocation.getArguments()[0];
if (headerName.equals(HttpHeaders.IF_MATCH)) {
return new ArrayList<String>() {
{
add("ABCDEFG");
}
};
}
return null;
}
}).when(httpHeaders).getRequestHeader(any(String.class));
// execute
rim.put(httpHeaders, "id", uriInfo, er);
}
@SuppressWarnings("unchecked")
@Test
public void testDeleteCommandWithIfMatchHeader() throws InteractionException {
ResourceState initialState = new ResourceState("entity", "state", mockActions(), "/test");
initialState.addTransition(new Transition.Builder().method("DELETE").target(initialState).build());
// this test incorrectly supplies a resource as a result of the command.
InteractionCommand mockCommand = new InteractionCommand() {
public Result execute(InteractionContext ctx) {
assertNotNull(ctx.getResource());
assertNotNull(ctx.getPreconditionIfMatch());
assertNull(ctx.getResource().getEntityTag());
assertEquals("ABCDEFG", ctx.getPreconditionIfMatch());
return Result.SUCCESS;
}
};
// RIM with command controller that issues commands that always return
// SUCCESS
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mockCommandController(mockCommand), new ResourceStateMachine(
initialState), createMockMetadata());
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getPathParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
// Apply If-Match header
HttpHeaders httpHeaders = mock(HttpHeaders.class);
doAnswer(new Answer<List<String>>() {
@SuppressWarnings("serial")
@Override
public List<String> answer(InvocationOnMock invocation) throws Throwable {
String headerName = (String) invocation.getArguments()[0];
if (headerName.equals(HttpHeaders.IF_MATCH)) {
return new ArrayList<String>() {
{
add("ABCDEFG");
}
};
}
return null;
}
}).when(httpHeaders).getRequestHeader(any(String.class));
// execute
rim.put(httpHeaders, "id", uriInfo, null); // resource is null
}
@Test
@SuppressWarnings({ "unchecked" })
public void testDoubleAutotransitionResolution() throws InteractionException{
//construct resource states
ResourceState initialState = new ResourceState("entity", "state",
Arrays.asList(new Action[]{new Action("GET", Action.TYPE.VIEW)}), "/test"),
nextState = new ResourceState("next", "nextState",
Arrays.asList(new Action[]{new Action("GET", Action.TYPE.VIEW)}), "/nextState"),
postState = new ResourceState("entity", "state_unsafe",
Arrays.asList(new Action[]{new Action("POST", Action.TYPE.ENTRY)}), "/test_unsafe");
//build transitions between resource states
initialState.addTransition(new Transition.Builder().flags(2).target(nextState).build());
nextState.addTransition(new Transition.Builder().flags(2).target(postState).build());
//fake an InteractionCommand that always returns SUCCESS and spy
InteractionCommand mockCommand = new InteractionCommand() {
public Result execute(InteractionContext ctx) {
ctx.setResource(new EntityResource<Object>());
return Result.SUCCESS;
}
};
mockCommand = spy(mockCommand);
//create mock command controller and return the faked InteractionCommand for every command we issue
CommandController mockCommandController = mock(CommandController.class);
when(mockCommandController.fetchCommand(anyString())).thenReturn(mockCommand);
//instantiate the class under test using the command controller that we created
ResourceStateMachine resourceStateMachine = new ResourceStateMachine(initialState);
resourceStateMachine.setWorkflowCommandBuilderProvider(new WorkflowCommandBuilderFactory(mockCommandController));
HTTPHypermediaRIM rim = spy(new HTTPHypermediaRIM(mockCommandController, resourceStateMachine, createMockMetadata()));
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getPathParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
doAnswer(new Answer<ResponseBuilder>(){
@Override
public ResponseBuilder answer(InvocationOnMock invocation) throws Throwable {
return (ResponseBuilder)invocation.getArguments()[0];
}
}).when(rim).setLocationHeader(any(ResponseBuilder.class), anyString(), any(MultivaluedMap.class));
//execute the request and verify that we have executed InteractionCommand once
Response response = rim.get(mock(HttpHeaders.class), "id", uriInfo);
assertThat(response.getStatus(), equalTo(200));
verify(mockCommand, times(1)).execute(any(InteractionContext.class));
verify(rim, times(1)).setLocationHeader(any(ResponseBuilder.class), eq("http://localhost/myservice.svc/test_unsafe"), any(MultivaluedMap.class));
}
@Test
public void testFilterParameters() {
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mock(CommandController.class), mockResourceStateMachine(), createMockMetadata());
MultivaluedMap<String, String> params = new MultivaluedMapImpl<>();
params.putSingle("k1", "v1");
params.putSingle("k2", "v2");
params.putSingle("k3", "v3");
params.putSingle("k4", "v4");
Set<String> filters = new HashSet<>();
filters.add("k2");
filters.add("k4");
MultivaluedMap<String, String> results = rim.filterParameters(params, filters);
assertEquals(2, results.size());
assertEquals(1, results.get("k2").size());
assertEquals(1, results.get("k4").size());
assertEquals("v2", results.get("k2").get(0));
assertEquals("v4", results.get("k4").get(0));
}
@Test
public void testGetStateParameters() {
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mock(CommandController.class), mockResourceStateMachine(), createMockMetadata());
ResourceStateAndParameters stateAndParams = new ResourceStateAndParameters();
stateAndParams.setParams(new ParameterAndValue[] {new ParameterAndValue("k1", "v1"), new ParameterAndValue("k2", "v2")});
MultivaluedMap<String, String> results = rim.getStateParameters(stateAndParams);
assertEquals(2, results.size());
assertEquals(1, results.get("k1").size());
assertEquals(1, results.get("k2").size());
assertEquals("v1", results.get("k1").get(0));
assertEquals("v2", results.get("k2").get(0));
}
@Test
public void testBuildPathParameters() {
Map<String, Object> transitionProperties = new HashMap<>();
transitionProperties.put("tpk1", "tpv1");
transitionProperties.put("tpk2", "tpv2");
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mock(CommandController.class), mockResourceStateMachine(transitionProperties), createMockMetadata());
MultivaluedMap<String, String> pathParameters = new MultivaluedMapImpl<>();
pathParameters.putSingle("pk1", "pv1");
InteractionContext ctxMock = mockInteractionContext(pathParameters, null);
MultivaluedMap<String, String> results = rim.buildPathParameters(mock(Transition.class), ctxMock);
assertEquals(3, results.size());
assertEquals(1, results.get("tpk1").size());
assertEquals(1, results.get("tpk2").size());
assertEquals(1, results.get("pk1").size());
assertEquals("tpv1", results.get("tpk1").get(0));
assertEquals("tpv2", results.get("tpk2").get(0));
assertEquals("pv1", results.get("pk1").get(0));
}
@Test
public void testCopyParameters() {
HTTPHypermediaRIM rim = new HTTPHypermediaRIM(mock(CommandController.class), mockResourceStateMachine(), createMockMetadata());
MultivaluedMap<String, String> pathParameters = new MultivaluedMapImpl<>();
pathParameters.putSingle("pk1", "pv1");
MultivaluedMap<String, String> results = rim.copyParameters(pathParameters);
assertEquals(1, results.size());
assertEquals(1, results.get("pk1").size());
assertEquals("pv1", results.get("pk1").get(0));
assertTrue(results != pathParameters);
}
@SuppressWarnings({ "unchecked" })
private UriInfo mockEmptyUriInfo() {
UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getPathParameters(anyBoolean())).thenReturn(mock(MultivaluedMap.class));
when(uriInfo.getQueryParameters(false)).thenReturn(mock(MultivaluedMap.class));
return uriInfo;
}
private InteractionContext mockInteractionContext(MultivaluedMap<String, String> pathParameters, MultivaluedMap<String, String> queryParameters) {
InteractionContext ctxMock = mock(InteractionContext.class);
when(ctxMock.getPathParameters()).thenReturn(pathParameters);
when(ctxMock.getQueryParameters()).thenReturn(queryParameters);
when(ctxMock.getResource()).thenReturn(new EntityResource<>());
return ctxMock;
}
private ResourceStateMachine mockResourceStateMachine() {
return mockResourceStateMachine(new HashMap<String, Object>());
}
private ResourceStateMachine mockResourceStateMachine(Map<String, Object> transitionProperties) {
ResourceStateMachine resourceStateMachineMock = mock(ResourceStateMachine.class);
when(resourceStateMachineMock.getInitial()).thenReturn(new ResourceState("entity", "state", mockActions(), "/test"));
when(resourceStateMachineMock.getTransitionProperties(any(Transition.class), any(), any(MultivaluedMap.class), any(MultivaluedMap.class))).thenReturn(transitionProperties);
return resourceStateMachineMock;
}
private Metadata createMockMetadata() {
Metadata metadata = mock(Metadata.class);
when(metadata.getEntityMetadata(any(String.class))).thenReturn(mock(EntityMetadata.class));
return metadata;
}
}