From 26a52332ae1858500e5018f74ce8548a71409862 Mon Sep 17 00:00:00 2001 From: Jon Brisbin Date: Mon, 12 Mar 2012 09:24:08 -0500 Subject: [PATCH] Added test coverage, updated the README to reflect a sample configuration. --- README.md | 66 ++++++++++- .../rest/mvc/RepositoryRestController.java | 9 +- .../spec/RepositoryRestControllerSpec.groovy | 112 ++++++++++++++++-- 3 files changed, 173 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f8691be62..a13d16471 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,69 @@ managing associations. ### Installation To use the Spring Data Rest exporter, first package your domain classes and repositories into a JAR -file. Include some Spring XML configuration files in the `META-INF/spring-data-rest` directory in -that JAR file (including an applicable EntityManager and DataSource). +file. Include some Spring XML configuration files in the `META-INF/spring-data-rest` directory (the +file name should end with "-export.xml" to be picked up by the scanner) in that JAR file that include +an applicable EntityManager and DataSource and the Repository configuration (using the special JPA +Repository namespace). You can either deploy this JAR file into your Servlet container in a "shared" configuration, or you -can add this JAR file (and any other application dependencies to the exporter WAR file's `WEB-INF/lib` +can add this JAR file (and any other application dependencies) to the exporter WAR file's `WEB-INF/lib` directory. + +Somewhere in the Spring configuration files you need to define a bean called "baseUri" that is a +`java.net.URI` and is the fully-qualified URI in which the exporter servlet has been deployed. In the +case of the sample below, the servlet is deployed to a context path of `/data`. Using the default +host and port settings, this yields a `baseUri` of `http://localhost:8080/data`. You'll want to change +this to reflect your deployment configuration. + +### Sample Configuration + +The configuration used in testing looks like this: + +##### META-INF/spring-data-rest/shared.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + +##### META-INF/spring-data-rest/repositories-export.xml + + + + + + + + + + diff --git a/rest/src/main/java/org/springframework/data/rest/mvc/RepositoryRestController.java b/rest/src/main/java/org/springframework/data/rest/mvc/RepositoryRestController.java index 75b224fd5..6fa634700 100644 --- a/rest/src/main/java/org/springframework/data/rest/mvc/RepositoryRestController.java +++ b/rest/src/main/java/org/springframework/data/rest/mvc/RepositoryRestController.java @@ -204,10 +204,6 @@ public class RepositoryRestController implements InitializingBean { URI relativeUri = baseUri.relativize(request.getURI()); final Stack uris = UriUtils.explode(baseUri, relativeUri); - if (LOG.isDebugEnabled()) { - LOG.debug("uris: " + uris); - } - final int uriCnt = uris.size(); if (uris.size() > 0) { final String repoName = uris.get(0).getPath(); @@ -448,6 +444,11 @@ public class RepositoryRestController implements InitializingBean { repo.findOne(serId) : entityMetadata.targetType().newInstance(); + if (null == entity) { + model.addAttribute(STATUS, HttpStatus.NOT_FOUND); + return; + } + entityMetadata.doWithEmbedded(new Handler() { @Override public Void handle(Attribute attribute) { String name = attribute.getName(); diff --git a/rest/src/test/groovy/org/springframework/data/rest/mvc/spec/RepositoryRestControllerSpec.groovy b/rest/src/test/groovy/org/springframework/data/rest/mvc/spec/RepositoryRestControllerSpec.groovy index 1d7c0aa5a..830245b9e 100644 --- a/rest/src/test/groovy/org/springframework/data/rest/mvc/spec/RepositoryRestControllerSpec.groovy +++ b/rest/src/test/groovy/org/springframework/data/rest/mvc/spec/RepositoryRestControllerSpec.groovy @@ -14,6 +14,7 @@ import org.springframework.mock.web.MockHttpServletRequest import org.springframework.test.context.ContextConfiguration import org.springframework.transaction.annotation.Transactional import org.springframework.ui.ExtendedModelMap +import org.springframework.ui.Model import spock.lang.Shared import spock.lang.Specification @@ -30,21 +31,47 @@ class RepositoryRestControllerSpec extends Specification { @Autowired RepositoryRestController controller - ServletServerHttpRequest createRequest(String method, String path) { - return new ServletServerHttpRequest(new MockHttpServletRequest( + MockHttpServletRequest createRequest(String method, String path) { + return new MockHttpServletRequest( serverPort: 8080, requestURI: "/data/$path", method: method - )) + ) } - Map GET(String path) { + Model GET(String path) { def request = createRequest("GET", path) def model = new ExtendedModelMap() - controller.get(request, model) + controller.get(new ServletServerHttpRequest(request), model) return model } + Model POST(String path, m) { + def request = createRequest("POST", path) + request.contentType = "application/json" + request.setContent(mapper.writeValueAsBytes(m)) + def model = new ExtendedModelMap() + controller.createOrUpdate(new ServletServerHttpRequest(request), model) + return model + } + + Model PUT(String path, m) { + def request = createRequest("PUT", path) + request.contentType = "application/json" + request.setContent(mapper.writeValueAsBytes(m)) + def model = new ExtendedModelMap() + controller.createOrUpdate(new ServletServerHttpRequest(request), model) + return model + } + + Model DELETE(String path) { + def request = createRequest("DELETE", path) + def model = new ExtendedModelMap() + controller.delete(new ServletServerHttpRequest(request), model) + return model + + } + def setupSpec() { def customSerializerFactory = new CustomSerializerFactory() customSerializerFactory.addSpecificMapping(SimpleLink, new FluentBeanSerializer(SimpleLink)) @@ -74,15 +101,86 @@ class RepositoryRestControllerSpec extends Specification { def person = GET("person/1") then: - person?.resource.name == "John Doe" + person?.resource?.name == "John Doe" when: def profiles = GET("person/1/profiles") - def profilesLinks = profiles?.resource.profiles + def profilesLinks = profiles.resource?.profiles then: profilesLinks.size() == 2 } + @Transactional + def "responds to POST with ID"() { + + when: + def created = POST("person/3", [name: "James Doe"]) + + then: + created.status == HttpStatus.CREATED + + } + + @Transactional + def "responds to PUT"() { + + given: + POST("person/3", [name: "James Doe"]) + + when: + def updated = PUT("person/3", [name: "James Doe Jr."]) + def getUpdated = GET("person/3") + + then: + updated.status == HttpStatus.NO_CONTENT + getUpdated.status == HttpStatus.OK + getUpdated.resource?.name == "James Doe Jr." + + } + + @Transactional + def "updates links"() { + + given: + POST("person/3", [name: "James Doe"]) + + when: + def link = POST("person/3/addresses", [[href: "$baseUri/address/1".toString()]]) + def getUpdated = GET("person/3/addresses") + + then: + link.status == HttpStatus.CREATED + getUpdated.status == HttpStatus.OK + getUpdated.resource?.size() == 1 + + } + + @Transactional + def "responds to DELETE"() { + + given: + POST("person/3", [name: "James Doe"]) + POST("person/3/addresses", [[href: "$baseUri/address/1".toString()]]) + + when: + def deleted = DELETE("person/3/addresses/1") + def getUpdated = GET("person/3/addresses") + + then: + deleted.status == HttpStatus.NO_CONTENT + getUpdated.status == HttpStatus.OK + getUpdated.resource?._links?.size() == 0 + + when: + def delEntity = DELETE("person/3") + def getUpdEntity = GET("person/3") + + then: + delEntity.status == HttpStatus.NO_CONTENT + getUpdEntity.status == HttpStatus.NOT_FOUND + + } + }