package com.interaction.example.hateoas.simple;
/*
* #%L
* interaction-example-hateoas-simple
* %%
* 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 org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.endsWith;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.ws.rs.core.Response;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.test.framework.JerseyTest;
import com.temenos.interaction.media.hal.MediaType;
import com.theoryinpractise.halbuilder.api.Link;
import com.theoryinpractise.halbuilder.api.ReadableRepresentation;
import com.theoryinpractise.halbuilder.api.RepresentationFactory;
import com.theoryinpractise.halbuilder.standard.StandardRepresentationFactory;
/**
* This test ensures that we can navigate from one application state to another
* using hypermedia (links).
*
* @author aphethean
*/
public class HypermediaITCase extends JerseyTest {
public HypermediaITCase() throws Exception {
super();
}
@Before
public void initTest() {
// -DTEST_ENDPOINT_URI={someurl} to test with external server
webResource = Client.create().resource(ConfigurationHelper.getTestEndpointUri(Configuration.TEST_ENDPOINT_URI));
}
@After
public void tearDown() {
}
@Test
public void testGetEntryPointLinks() {
ClientResponse response = webResource.path("/").accept(MediaType.APPLICATION_HAL_JSON)
.get(ClientResponse.class);
assertEquals(Response.Status.Family.SUCCESSFUL, Response.Status.fromStatusCode(response.getStatus())
.getFamily());
RepresentationFactory representationFactory = new StandardRepresentationFactory();
ReadableRepresentation resource = representationFactory.readRepresentation(MediaType.APPLICATION_HAL_JSON.toString(),new InputStreamReader(response
.getEntityInputStream()));
List<Link> links = resource.getLinks();
assertEquals(4, links.size());
for (Link link : links) {
if (link.getRel().equals("self")) {
assertEquals(Configuration.TEST_ENDPOINT_URI + "/", link.getHref());
} else if (link.getName().equals("HOME.home>GET>Preferences.preferences")) {
assertEquals(Configuration.TEST_ENDPOINT_URI + "/preferences", link.getHref());
} else if (link.getName().equals("HOME.home>GET>Profile.profile")) {
assertEquals(Configuration.TEST_ENDPOINT_URI + "/profile", link.getHref());
} else if (link.getName().equals("HOME.home>GET>Note.notes")) {
assertEquals(Configuration.TEST_ENDPOINT_URI + "/notes", link.getHref());
} else {
fail("unexpected link [" + link.getName() + "]");
}
}
}
@Test
public void testGetEntryPointEmbeddedPreferences() {
ClientResponse response = webResource.path("/").accept(MediaType.APPLICATION_HAL_JSON)
.get(ClientResponse.class);
assertEquals(Response.Status.Family.SUCCESSFUL, Response.Status.fromStatusCode(response.getStatus())
.getFamily());
RepresentationFactory representationFactory = new StandardRepresentationFactory();
ReadableRepresentation resource = representationFactory.readRepresentation(MediaType.APPLICATION_HAL_JSON.toString(),new InputStreamReader(response
.getEntityInputStream()));
// the embedded resources
Map<String, Collection<ReadableRepresentation>> subresources = resource.getResourceMap();
assertNotNull(subresources);
assertTrue(subresources.size() == 1);
Collection<ReadableRepresentation> preferences = subresources.get("http://relations.rimdsl.org/preferences");
assertNotNull(preferences);
assertTrue(preferences.size() == 1);
ReadableRepresentation preference = preferences.iterator().next();
assertEquals("user", preference.getValue("userID"));
assertEquals("GBP", preference.getValue("currency"));
assertEquals("en", preference.getValue("language"));
}
@Test
public void testCollectionLinks() {
ClientResponse response = webResource.path("/notes").accept(MediaType.APPLICATION_HAL_JSON)
.get(ClientResponse.class);
assertEquals(Response.Status.Family.SUCCESSFUL, Response.Status.fromStatusCode(response.getStatus())
.getFamily());
RepresentationFactory representationFactory = new StandardRepresentationFactory();
ReadableRepresentation resource = representationFactory.readRepresentation(MediaType.APPLICATION_HAL_JSON.toString(),new InputStreamReader(response
.getEntityInputStream()));
// the links from the collection
List<Link> links = resource.getLinks();
assertEquals(2, links.size());
for (Link link : links) {
if (link.getRel().equals("self")) {
assertEquals(Configuration.TEST_ENDPOINT_URI + "/notes", link.getHref());
} else if (link.getName().equals("Note.notes>POST>Note.createNote")) {
assertEquals(Configuration.TEST_ENDPOINT_URI + "/notes", link.getHref());
} else {
fail("unexpected link [" + link.getName() + "]");
}
}
// the items, and links on each item
Collection<Map.Entry<String, ReadableRepresentation>> subresources = resource.getResources();
assertNotNull(subresources);
/*
* Test that there are actually some subresource returned. If the 'self'
* link rel in the HALProvider is broken then we won't get any
* subresources here.
*/
assertTrue(subresources.size() > 0);
boolean beveragesBody = false;
boolean condimentsBody = false;
boolean confectionsBody = false;
boolean dairyProductsBody = false;
boolean grainsCerealsBody = false;
boolean meatPoultryBody = false;
boolean produceBody = false;
boolean seafoodBody = false;
boolean importantBody = false;
for (Map.Entry<String, ReadableRepresentation> entry : subresources) {
ReadableRepresentation item = entry.getValue();
List<Link> itemLinks = item.getLinks();
if (!item.getProperties().get("noteID").equals("9")) {
assertEquals(2, itemLinks.size());
}
for (Link link : itemLinks) {
if (link.getRel().contains("item")) {
assertEquals(Configuration.TEST_ENDPOINT_URI + "/notes/" + item.getProperties().get("noteID"),
link.getHref());
} else if (link.getName().contains("Note.deletedNote")) {
assertEquals(Configuration.TEST_ENDPOINT_URI + "/notes/" + item.getProperties().get("noteID"),
link.getHref());
} else {
fail("unexpected link [" + link.getName() + "]");
}
}
if(entryBodyMatches(entry.getValue(), "Beverages"))
beveragesBody = true;
if(entryBodyMatches(entry.getValue(), "Condiments"))
condimentsBody = true;
if(entryBodyMatches(entry.getValue(), "Confections"))
confectionsBody = true;
if(entryBodyMatches(entry.getValue(), "Dairy Products"))
dairyProductsBody = true;
if(entryBodyMatches(entry.getValue(), "Grains/Cereals"))
grainsCerealsBody = true;
if(entryBodyMatches(entry.getValue(), "Meat/Poultry"))
meatPoultryBody = true;
if(entryBodyMatches(entry.getValue(), "Produce"))
produceBody = true;
if(entryBodyMatches(entry.getValue(), "Seafood"))
seafoodBody = true;
if(entryBodyMatches(entry.getValue(), "IMPORTANT"))
importantBody = true;
}
assertTrue(beveragesBody);
assertTrue(condimentsBody);
assertTrue(confectionsBody);
assertTrue(dairyProductsBody);
assertTrue(grainsCerealsBody);
assertTrue(meatPoultryBody);
assertTrue(produceBody);
assertTrue(seafoodBody);
assertTrue(importantBody);
}
private boolean entryBodyMatches(ReadableRepresentation representation, String valueToMatch) {
return valueToMatch.equals(representation.getValue("body"));
}
@Test
public void testConditionalCollectionLinks() {
ClientResponse response = webResource.path("/notes").accept(MediaType.APPLICATION_HAL_JSON)
.get(ClientResponse.class);
assertEquals(Response.Status.Family.SUCCESSFUL, Response.Status.fromStatusCode(response.getStatus())
.getFamily());
RepresentationFactory representationFactory = new StandardRepresentationFactory();
ReadableRepresentation resource = representationFactory.readRepresentation(MediaType.APPLICATION_HAL_JSON.toString(),new InputStreamReader(response
.getEntityInputStream()));
// the links from the collection
List<Link> links = resource.getLinks();
assertEquals(2, links.size());
for (Link link : links) {
if (link.getRel().equals("self")) {
assertEquals(Configuration.TEST_ENDPOINT_URI + "/notes", link.getHref());
} else if (link.getName().equals("Note.notes>POST>Note.createNote")) {
assertEquals(Configuration.TEST_ENDPOINT_URI + "/notes", link.getHref());
} else {
fail("unexpected link [" + link.getName() + "]");
}
}
// the items, and links on each item
Collection<Map.Entry<String, ReadableRepresentation>> subresources = resource.getResources();
assertNotNull(subresources);
/*
* Test that there are actually some subresource returned. If the 'self'
* link rel in the HALProvider is broken then we won't get any
* subresources here.
*/
assertTrue(subresources.size() > 0);
List<Link> testLinks = null;
for (Map.Entry<String, ReadableRepresentation> entry : subresources) {
ReadableRepresentation item = entry.getValue();
List<Link> itemLinks = item.getLinks();
if (item.getProperties().get("noteID").equals("9")) {
// the links for out note number 9, i.e. the IMPORTANT links
testLinks = itemLinks;
} else {
assertEquals(2, itemLinks.size());
}
}
assertNotNull(testLinks);
assertEquals(1, testLinks.size());
for (Link link : testLinks) {
if (link.getRel().contains("item")) {
assertEquals(Configuration.TEST_ENDPOINT_URI + "/notes/9", link.getHref());
// in the test we assert we cannot delete IMPORTANT notes
// } else if (link.getName().contains("Note.deletedNote")) {
// assertEquals(Configuration.TEST_ENDPOINT_URI + "/notes/9", link.getHref());
} else {
fail("unexpected link [" + link.getName() + "]");
}
}
}
/**
* Found a small issue where a GET to a non-existent resource still
* generated the links and this resulted in a server side error (500)
*/
@Test
public void testGET404() {
ClientResponse response = webResource.path("/notes/666").accept(MediaType.APPLICATION_HAL_JSON)
.get(ClientResponse.class);
assertEquals(404, response.getStatus());
}
@Test
public void testFollowDeleteItemLink() {
ClientResponse response = webResource.path("/notes").accept(MediaType.APPLICATION_HAL_JSON)
.get(ClientResponse.class);
assertEquals(Response.Status.Family.SUCCESSFUL, Response.Status.fromStatusCode(response.getStatus())
.getFamily());
RepresentationFactory representationFactory = new StandardRepresentationFactory();
ReadableRepresentation resource = representationFactory.readRepresentation(MediaType.APPLICATION_HAL_JSON.toString(),new InputStreamReader(response
.getEntityInputStream()));
// the items in the collection
Collection<Map.Entry<String, ReadableRepresentation>> subresources = resource.getResources();
assertNotNull(subresources);
// follow the link to delete the first in the collection
if (subresources.size() == 0) {
// we might have run the integration tests more times than we have
// rows in our table
} else {
ReadableRepresentation item = subresources.iterator().next().getValue();
List<Link> itemLinks = item.getLinks();
assertEquals(2, itemLinks.size());
Link deleteLink = null;
for (Link link : itemLinks) {
if (link.getName() != null && link.getName().contains("Note.notes>DELETE>Note.deletedNote")) {
deleteLink = link;
}
}
assertNotNull(deleteLink);
// create http client
Client client = Client.create();
// do not follow the Location redirect
client.setFollowRedirects(false);
// execute delete without custom link relation, will find the only
// DELETE transition from entity
ClientResponse deleteResponse = client.resource(deleteLink.getHref())
.accept(MediaType.APPLICATION_HAL_JSON).delete(ClientResponse.class);
// 303 "See Other" instructs user agent to fetch another resource as
// specified by the 'Location' header
assertEquals(303, deleteResponse.getStatus());
assertEquals(Configuration.TEST_ENDPOINT_URI + "/notes", deleteResponse.getHeaders().getFirst("Location"));
}
}
@Test
public void testFollowDeleteItemLinkRelation() {
ClientResponse response = webResource.path("/notes").accept(MediaType.APPLICATION_HAL_JSON)
.get(ClientResponse.class);
assertEquals(Response.Status.Family.SUCCESSFUL, Response.Status.fromStatusCode(response.getStatus())
.getFamily());
RepresentationFactory representationFactory = new StandardRepresentationFactory();
ReadableRepresentation resource = representationFactory.readRepresentation(MediaType.APPLICATION_HAL_JSON.toString(),new InputStreamReader(response
.getEntityInputStream()));
// the items in the collection
Collection<Map.Entry<String, ReadableRepresentation>> subresources = resource.getResources();
assertNotNull(subresources);
// follow the link to delete the first in the collection
if (subresources.size() == 0) {
// we might have run the integration tests more times than we have
// rows in our table
} else {
ReadableRepresentation item = subresources.iterator().next().getValue();
List<Link> itemLinks = item.getLinks();
assertEquals(2, itemLinks.size());
Link deleteLink = null;
for (Link link : itemLinks) {
if (link.getName() != null && link.getName().contains("Note.notes>DELETE>Note.deletedNote")) {
deleteLink = link;
}
}
assertNotNull(deleteLink);
// execute delete with custom link relation (see rfc5988)
Client client = Client.create();
client.setFollowRedirects(false);
ClientResponse deleteResponse = client
.resource(deleteLink.getHref())
.header("Link", "<" + deleteLink.getHref() + ">; rel=\"" + deleteLink.getName() + "\"")
.accept(MediaType.APPLICATION_HAL_JSON).delete(ClientResponse.class);
// 205 "Reset Content" instructs user agent to reload the resource
// that contained this link
assertEquals(205, deleteResponse.getStatus());
}
}
@Test
public void testFollowDeleteLinkWithLinkRel() {
ClientResponse response = webResource.path("/notes").accept(MediaType.APPLICATION_HAL_JSON)
.get(ClientResponse.class);
assertEquals(Response.Status.Family.SUCCESSFUL, Response.Status.fromStatusCode(response.getStatus())
.getFamily());
RepresentationFactory representationFactory = new StandardRepresentationFactory();
ReadableRepresentation resource = representationFactory.readRepresentation(MediaType.APPLICATION_HAL_JSON.toString(),new InputStreamReader(response
.getEntityInputStream()));
// the items in the collection
Collection<Map.Entry<String, ReadableRepresentation>> subresources = resource.getResources();
assertNotNull(subresources);
// follow the link to delete the first in the collection
if (subresources.size() == 0) {
// we might have run the integration tests more times than we have
// rows in our table
} else {
ReadableRepresentation item = subresources.iterator().next().getValue();
List<Link> itemLinks = item.getLinks();
assertEquals(2, itemLinks.size());
// GET item link (Note.notes->Note.note)
Link getLink = null;
for (Link link : itemLinks) {
if (link.getRel().contains("item")) {
getLink = link;
}
}
// follow GET item link
assertNotNull(getLink);
ClientResponse getResponse = Client.create().resource(getLink.getHref())
.accept(MediaType.APPLICATION_HAL_JSON).get(ClientResponse.class);
assertEquals(Response.Status.Family.SUCCESSFUL, Response.Status.fromStatusCode(getResponse.getStatus())
.getFamily());
// the item
ReadableRepresentation itemResource = representationFactory.readRepresentation(MediaType.APPLICATION_HAL_JSON.toString(),new InputStreamReader(
getResponse.getEntityInputStream()));
List<Link> links = itemResource.getLinks();
assertNotNull(links);
/*
* 2 links. One to 'self' One to 'item' which contains DELETE in
* href (this should be changed to 'edit' rel)
*/
assertEquals(2, links.size());
// DELETE item link (Note.note>Note.deletedNote, Note.deletedNote is
// an auto transition to Note.notes)
Link deleteLink = null;
for (Link link : links) {
if (link.getName() != null && link.getName().contains("Note.note>DELETE>Note.deletedNote")) {
deleteLink = link;
}
}
assertNotNull(deleteLink);
String uri = deleteLink.getHref();
// create http client
Client client = Client.create();
// do not follow the Location redirect
client.setFollowRedirects(false);
// execute delete with custom link relation (see rfc5988)
ClientResponse deleteResponse = client.resource(uri)
.header("Link", "<" + uri + ">; rel=\"" + deleteLink.getName() + "\"")
.accept(MediaType.APPLICATION_HAL_JSON).delete(ClientResponse.class);
// 303 "See Other" instructs user agent to fetch another resource as
// specified by the 'Location' header
assertEquals(303, deleteResponse.getStatus());
assertEquals(Configuration.TEST_ENDPOINT_URI + "/notes", deleteResponse.getHeaders().getFirst("Location"));
}
}
/**
* Attempt a DELETE to the notes resource (a collection resource)
*/
@Test
public void deletePersonMethodNotAllowed() throws Exception {
// attempt to delete the Person root, rather than an individual
ClientResponse response = webResource.path("/notes").delete(ClientResponse.class);
assertEquals(405, response.getStatus());
assertEquals(4, response.getAllow().size());
assertTrue(response.getAllow().contains("GET"));
assertTrue(response.getAllow().contains("POST"));
assertTrue(response.getAllow().contains("OPTIONS"));
assertTrue(response.getAllow().contains("HEAD"));
}
/**
* Attempt a PUT to the notes collection resource (method not allowed)
*/
@Test
public void putNoteToCollection() throws Exception {
String halRequest = "{}";
// attempt to put to the notes collection, rather than an individual
ClientResponse response = webResource.path("/notes").type(MediaType.APPLICATION_HAL_JSON)
.put(ClientResponse.class, halRequest);
assertEquals(405, response.getStatus());
}
/**
* Attempt a POST an invalid notes resource (a collection resource)
*/
@Test
public void postNoteBadRequest() throws Exception {
String halRequest = "{{";
// attempt to put to the notes collection, rather than an individual
ClientResponse response = webResource.path("/notes").type(MediaType.APPLICATION_HAL_JSON)
.post(ClientResponse.class, halRequest);
assertEquals(400, response.getStatus());
}
/**
* Attempt a PUT an invalid notes resource (a collection resource)
*/
@Test
public void putNoteBadRequest() throws Exception {
String halRequest = "{{";
// attempt to put to the notes collection, rather than an individual
ClientResponse response = webResource.path("/notes").type(MediaType.APPLICATION_HAL_JSON)
.put(ClientResponse.class, halRequest);
assertEquals(400, response.getStatus());
}
@Test
public void testGetProfile() throws Exception {
ClientResponse response = webResource.path("/profiles/test%2BUser+1%5C2%2F3%274").accept(MediaType.APPLICATION_HAL_JSON)
.get(ClientResponse.class);
assertEquals(200, response.getStatus());
RepresentationFactory representationFactory = new StandardRepresentationFactory();
ReadableRepresentation resource = representationFactory.readRepresentation(
MediaType.APPLICATION_HAL_JSON.toString(), new InputStreamReader(response.getEntityInputStream()));
Map<String, Object> properties = resource.getProperties();
assertNotNull(resource.getLinkByRel("self"));
assertThat(properties.get("userID"), equalTo("test+User 1\\2/3'4"));
}
@SuppressWarnings("unchecked")
@Test
public void testGetProfiles() throws Exception {
ClientResponse response = webResource.path("/profiles").accept(MediaType.APPLICATION_HAL_JSON)
.get(ClientResponse.class);
assertEquals(200, response.getStatus());
RepresentationFactory representationFactory = new StandardRepresentationFactory();
ReadableRepresentation resource = representationFactory.readRepresentation(
MediaType.APPLICATION_HAL_JSON.toString(), new InputStreamReader(response.getEntityInputStream()));
assertNotNull(resource.getLinkByRel("self"));
Iterator<Entry<String, ReadableRepresentation>> propsIter = resource.getResources().iterator();
while (propsIter.hasNext()) {
Entry<String, ReadableRepresentation> entry = propsIter.next();
Map<String, Object> properties = entry.getValue().getProperties();
Link link = entry.getValue().getLinkByRel("item");// Rel self??
assertNotNull(link);
String href = link.getHref();
assertTrue(href.endsWith("test%2BUser+1%5C2%2F3%274") || href.endsWith("aphethean"));
assertThat(properties.get("userID"), anyOf(equalTo("test+User 1\\2/3'4"), equalTo("aphethean")));
}
}
}