diff --git a/spring-rest-docs/pom.xml b/spring-rest-docs/pom.xml index decdd3a5df..04ee11d0de 100644 --- a/spring-rest-docs/pom.xml +++ b/spring-rest-docs/pom.xml @@ -21,6 +21,7 @@ UTF-8 1.8 + ${project.build.directory}/generated-snippets @@ -44,16 +45,53 @@ 1.0.1.RELEASE test + + com.jayway.jsonpath + json-path + 2.0.0 + - - - - org.springframework.boot - spring-boot-maven-plugin - - - + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Documentation.java + + + + + org.asciidoctor + asciidoctor-maven-plugin + 1.5.2 + + + generate-docs + package + + process-asciidoc + + + html + book + + ${snippetsDirectory} + + src/docs/asciidocs + target/generated-docs + + + + + + diff --git a/spring-rest-docs/src/docs/asciidocs/api-guide.adoc b/spring-rest-docs/src/docs/asciidocs/api-guide.adoc new file mode 100644 index 0000000000..9fbe74c072 --- /dev/null +++ b/spring-rest-docs/src/docs/asciidocs/api-guide.adoc @@ -0,0 +1,203 @@ += RESTful Notes API Guide +Baeldung; +:doctype: book +:icons: font +:source-highlighter: highlightjs +:toc: left +:toclevels: 4 +:sectlinks: + +[[overview]] += Overview + +[[overview-http-verbs]] +== HTTP verbs + +RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its +use of HTTP verbs. + +|=== +| Verb | Usage + +| `GET` +| Used to retrieve a resource + +| `POST` +| Used to create a new resource + +| `PATCH` +| Used to update an existing resource, including partial updates + +| `DELETE` +| Used to delete an existing resource +|=== + +RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its +use of HTTP status codes. + +|=== +| Status code | Usage + +| `200 OK` +| The request completed successfully + +| `201 Created` +| A new resource has been created successfully. The resource's URI is available from the response's +`Location` header + +| `204 No Content` +| An update to an existing resource has been applied successfully + +| `400 Bad Request` +| The request was malformed. The response body will include an error providing further information + +| `404 Not Found` +| The requested resource did not exist +|=== + +[[overview-headers]] +== Headers + +Every response has the following header(s): + +include::{snippets}/headers-example/response-headers.adoc[] + +[[overview-hypermedia]] +== Hypermedia + +RESTful Notes uses hypermedia and resources include links to other resources in their +responses. Responses are in http://stateless.co/hal_specification.html[Hypertext Application +from resource to resource. +Language (HAL)] format. Links can be found beneath the `_links` key. Users of the API should +not create URIs themselves, instead they should use the above-described links to navigate + +[[resources]] += Resources + + + +[[resources-index]] +== Index + +The index provides the entry point into the service. + +[[resources-index-access]] +=== Accessing the index + +A `GET` request is used to access the index + +==== Response structure + +include::{snippets}/index-example/http-response.adoc[] + +==== Example response + +include::{snippets}/index-example/http-response.adoc[] + +==== Example request + +include::{snippets}/index-example/http-request.adoc[] + +==== CURL request + +include::{snippets}/index-example/curl-request.adoc[] + +[[resources-index-links]] +==== Links + +include::{snippets}/index-example/links.adoc[] + + +[[resources-CRUD]] +== CRUD REST Service + +The CRUD provides the entry point into the service. + +[[resources-crud-access]] +=== Accessing the crud GET + +A `GET` request is used to access the CRUD read + +==== Response structure + +include::{snippets}/crud-get-example/http-request.adoc[] + +==== Example response + +include::{snippets}/crud-get-example/http-response.adoc[] + +==== CURL request + +include::{snippets}/crud-get-example/curl-request.adoc[] + +[[resources-crud-access]] +=== Accessing the crud POST + +A `POST` request is used to access the CRUD create + +==== Response structure + +include::{snippets}/crud-create-example/http-request.adoc[] + +==== Example response + +include::{snippets}/crud-create-example/http-response.adoc[] + +==== CURL request + +include::{snippets}/crud-create-example/curl-request.adoc[] + +[[resources-crud-access]] +=== Accessing the crud DELETE + +A `DELETE` request is used to access the CRUD create + +==== Response structure + +include::{snippets}/crud-delete-example/http-request.adoc[] + +==== Example response + +include::{snippets}/crud-delete-example/http-response.adoc[] + +==== CURL request + +include::{snippets}/crud-delete-example/curl-request.adoc[] + +[[resources-crud-access]] +=== Accessing the crud PATCH + +A `PATCH` request is used to access the CRUD create + +==== Response structure + +include::{snippets}/crud-patch-example/http-request.adoc[] + +==== Example response + +include::{snippets}/crud-patch-example/http-response.adoc[] + +==== CURL request + +include::{snippets}/crud-patch-example/curl-request.adoc[] + +[[resources-crud-access]] +=== Accessing the crud PUT + +A `PUT` request is used to access the CRUD create + +==== Response structure + +include::{snippets}/crud-put-example/http-request.adoc[] + +==== Example response + +include::{snippets}/crud-put-example/http-response.adoc[] + +==== CURL request + +include::{snippets}/crud-put-example/curl-request.adoc[] + + + + diff --git a/spring-rest-docs/src/main/java/com/example/CRUDController.java b/spring-rest-docs/src/main/java/com/example/CRUDController.java new file mode 100644 index 0000000000..818b29d3a6 --- /dev/null +++ b/spring-rest-docs/src/main/java/com/example/CRUDController.java @@ -0,0 +1,55 @@ +package com.example; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/crud") +public class CRUDController { + + @RequestMapping(method=RequestMethod.GET) + @ResponseStatus(HttpStatus.OK) + public List read(@RequestBody CrudInput crudInput) { + List returnList=new ArrayList(); + returnList.add(crudInput); + return returnList; + } + + @ResponseStatus(HttpStatus.CREATED) + @RequestMapping(method=RequestMethod.POST) + public HttpHeaders save(@RequestBody CrudInput crudInput) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setLocation(linkTo(CRUDController.class).slash(crudInput.getTitle()).toUri()); + return httpHeaders; + } + + @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) + @ResponseStatus(HttpStatus.OK) + HttpHeaders delete(@RequestBody CrudInput crudInput) { + HttpHeaders httpHeaders = new HttpHeaders(); + return httpHeaders; + } + + @RequestMapping(value = "/{id}", method = RequestMethod.PUT) + @ResponseStatus(HttpStatus.ACCEPTED) + void put(@PathVariable("id") long id, @RequestBody CrudInput crudInput) { + + } + + @RequestMapping(value = "/{id}", method = RequestMethod.PATCH) + @ResponseStatus(HttpStatus.NO_CONTENT) + void patch(@PathVariable("id") long id, @RequestBody CrudInput crudInput) { + + } +} diff --git a/spring-rest-docs/src/main/java/com/example/CrudInput.java b/spring-rest-docs/src/main/java/com/example/CrudInput.java new file mode 100644 index 0000000000..3d91b7d45a --- /dev/null +++ b/spring-rest-docs/src/main/java/com/example/CrudInput.java @@ -0,0 +1,42 @@ +package com.example; + +import java.net.URI; +import java.util.Collections; +import java.util.List; + +import org.hibernate.validator.constraints.NotBlank; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CrudInput { + + //@NotBlank + private final String title; + + private final String body; + + private final List tagUris; + + @JsonCreator + public CrudInput(@JsonProperty("title") String title, + @JsonProperty("body") String body, @JsonProperty("tags") List tagUris) { + this.title = title; + this.body = body; + this.tagUris = tagUris == null ? Collections.emptyList() : tagUris; + } + + public String getTitle() { + return title; + } + + public String getBody() { + return body; + } + + @JsonProperty("tags") + public List getTagUris() { + return this.tagUris; + } + +} \ No newline at end of file diff --git a/spring-rest-docs/src/main/java/com/example/IndexController.java b/spring-rest-docs/src/main/java/com/example/IndexController.java index 92d987f05d..a6b4537c43 100644 --- a/spring-rest-docs/src/main/java/com/example/IndexController.java +++ b/spring-rest-docs/src/main/java/com/example/IndexController.java @@ -1,23 +1,22 @@ package com.example; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + import org.springframework.hateoas.ResourceSupport; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; -import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; - @RestController -@RequestMapping("/api") +@RequestMapping("/") public class IndexController { - @RequestMapping(method = RequestMethod.GET) - public ResourceSupport index() { - ResourceSupport index = new ResourceSupport(); - index.add(linkTo(MyRestController.class).withRel("notes")); - index.add(linkTo(MyRestController.class).withRel("tags")); - return index; - } + @RequestMapping(method=RequestMethod.GET) + public ResourceSupport index() { + ResourceSupport index = new ResourceSupport(); + index.add(linkTo(CRUDController.class).withRel("crud")); + return index; + } } \ No newline at end of file diff --git a/spring-rest-docs/src/main/java/com/example/MyRestController.java b/spring-rest-docs/src/main/java/com/example/MyRestController.java deleted file mode 100644 index 896b82abfb..0000000000 --- a/spring-rest-docs/src/main/java/com/example/MyRestController.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/rest/api") -public class MyRestController { - - @RequestMapping(method = RequestMethod.GET) - public String index() { - return "Hello"; - } - -} diff --git a/spring-rest-docs/src/main/java/com/example/SpringRestDocsApplication.java b/spring-rest-docs/src/main/java/com/example/SpringRestDocsApplication.java index da09f9accc..dd20ef324e 100644 --- a/spring-rest-docs/src/main/java/com/example/SpringRestDocsApplication.java +++ b/spring-rest-docs/src/main/java/com/example/SpringRestDocsApplication.java @@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringRestDocsApplication { - public static void main(String[] args) { - SpringApplication.run(SpringRestDocsApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(SpringRestDocsApplication.class, args); + } } diff --git a/spring-rest-docs/src/test/java/com/example/ApiDocumentation.java b/spring-rest-docs/src/test/java/com/example/ApiDocumentation.java index 5490e90ff5..96ecbe158a 100644 --- a/spring-rest-docs/src/test/java/com/example/ApiDocumentation.java +++ b/spring-rest-docs/src/test/java/com/example/ApiDocumentation.java @@ -1,66 +1,184 @@ package com.example; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.hateoas.MediaTypes; import org.springframework.restdocs.RestDocumentation; +import org.springframework.restdocs.constraints.ConstraintDescriptions; import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.util.StringUtils; import org.springframework.web.context.WebApplicationContext; -import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; -import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.fasterxml.jackson.databind.ObjectMapper; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = SpringRestDocsApplication.class) @WebAppConfiguration public class ApiDocumentation { - @Rule - public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets"); + @Rule + public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets"); + + @Autowired + private WebApplicationContext context; + + @Autowired + private ObjectMapper objectMapper; + + private RestDocumentationResultHandler document; + + private MockMvc mockMvc; + + @Before + public void setUp() { + this.document = document("{method-name}",preprocessRequest(prettyPrint()),preprocessResponse(prettyPrint())); + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).apply(documentationConfiguration(this.restDocumentation)).alwaysDo(this.document).build(); + } + - @Autowired - private WebApplicationContext context; + @Test + public void headersExample() throws Exception { + this.document.snippets(responseHeaders(headerWithName("Content-Type").description("The Content-Type of the payload, e.g. `application/hal+json`"))); + this.mockMvc.perform(get("/")).andExpect(status().isOk()); + } + + @Test + public void indexExample() throws Exception { + this.document.snippets(links(linkWithRel("crud").description("The <>")),responseFields(fieldWithPath("_links").description("<> to other resources"))); + this.mockMvc.perform(get("/")).andExpect(status().isOk()); + } + + + @Test + public void crudGetExample() throws Exception { + + Map tag = new HashMap(); + tag.put("name", "GET"); - private RestDocumentationResultHandler document; + String tagLocation =this.mockMvc.perform(get("/crud").contentType(MediaTypes.HAL_JSON).content(this.objectMapper.writeValueAsString(tag))).andExpect(status().isOk()).andReturn().getResponse().getHeader("Location"); - private MockMvc mockMvc; + Map crud = new HashMap(); + crud.put("title", "Sample Model"); + crud.put("body", "http://www.baeldung.com/"); + crud.put("tags", Arrays.asList(tagLocation)); + + this.mockMvc.perform(get("/crud").contentType(MediaTypes.HAL_JSON).content(this.objectMapper.writeValueAsString(crud))).andExpect(status().isOk()); + } + + @Test + public void crudCreateExample() throws Exception { + Map tag = new HashMap(); + tag.put("name", "CREATE"); - @Before - public void setUp() { - this.document = document("{method-name}", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())); + String tagLocation =this.mockMvc.perform(post("/crud").contentType(MediaTypes.HAL_JSON).content(this.objectMapper.writeValueAsString(tag))).andExpect(status().isCreated()).andReturn().getResponse().getHeader("Location"); - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation)) - .alwaysDo(this.document).build(); - } + Map crud = new HashMap(); + crud.put("title", "Sample Model"); + crud.put("body", "http://www.baeldung.com/"); + crud.put("tags", Arrays.asList(tagLocation)); - @Test - public void indexExample() throws Exception { - this.document.snippets( - links( - linkWithRel("notes").description("The <>"), - linkWithRel("tags").description("The <>") - ), - responseFields(fieldWithPath("_links").description("<> to other resources"))); + ConstrainedFields fields = new ConstrainedFields(CrudInput.class); + this.document.snippets(requestFields(fields.withPath("title").description("The title of the note"),fields.withPath("body").description("The body of the note"),fields.withPath("tags").description("An array of tag resource URIs"))); + this.mockMvc.perform(post("/crud").contentType(MediaTypes.HAL_JSON).content(this.objectMapper.writeValueAsString(crud))).andExpect(status().isCreated()); + + + } + + @Test + public void crudDeleteExample() throws Exception { + + Map tag = new HashMap(); + tag.put("name", "DELETE"); + + String tagLocation =this.mockMvc.perform(delete("/crud/10").contentType(MediaTypes.HAL_JSON).content(this.objectMapper.writeValueAsString(tag))).andExpect(status().isOk()).andReturn().getResponse().getHeader("Location"); + Map crud = new HashMap(); + crud.put("title", "Sample Model"); + crud.put("body", "http://www.baeldung.com/"); + crud.put("tags", Arrays.asList(tagLocation)); + + this.mockMvc.perform(delete("/crud/10").contentType(MediaTypes.HAL_JSON).content(this.objectMapper.writeValueAsString(crud))).andExpect(status().isOk()); + } + + @Test + public void crudPatchExample() throws Exception { + + Map tag = new HashMap(); + tag.put("name", "PATCH"); + + String tagLocation =this.mockMvc.perform(patch("/crud/10").contentType(MediaTypes.HAL_JSON).content(this.objectMapper.writeValueAsString(tag))).andExpect(status().isNoContent()).andReturn().getResponse().getHeader("Location"); + Map crud = new HashMap(); + crud.put("title", "Sample Model"); + crud.put("body", "http://www.baeldung.com/"); + crud.put("tags", Arrays.asList(tagLocation)); + + this.mockMvc.perform(patch("/crud/10").contentType(MediaTypes.HAL_JSON).content(this.objectMapper.writeValueAsString(crud))).andExpect(status().isNoContent()); + } + + + @Test + public void crudPutExample() throws Exception { + Map tag = new HashMap(); + tag.put("name", "PUT"); + + String tagLocation =this.mockMvc.perform(put("/crud/10").contentType(MediaTypes.HAL_JSON).content(this.objectMapper.writeValueAsString(tag))).andExpect(status().isAccepted()).andReturn().getResponse().getHeader("Location"); + Map crud = new HashMap(); + crud.put("title", "Sample Model"); + crud.put("body", "http://www.baeldung.com/"); + crud.put("tags", Arrays.asList(tagLocation)); + + this.mockMvc.perform(put("/crud/10").contentType(MediaTypes.HAL_JSON).content(this.objectMapper.writeValueAsString(crud))).andExpect(status().isAccepted()); + } + + + @Test + public void contextLoads() { + } + + private static class ConstrainedFields { - this.mockMvc.perform(get("/api")).andExpect(status().isOk()); - } + private final ConstraintDescriptions constraintDescriptions; - @Test - public void contextLoads() { - } -} \ No newline at end of file + ConstrainedFields(Class input) { + this.constraintDescriptions = new ConstraintDescriptions(input); + } + + private FieldDescriptor withPath(String path) { + return fieldWithPath(path).attributes(key("constraints").value(StringUtils.collectionToDelimitedString(this.constraintDescriptions.descriptionsForProperty(path), ". "))); + } + } + + +} diff --git a/spring-rest-docs/src/test/java/com/example/GettingStartedDocumentation.java b/spring-rest-docs/src/test/java/com/example/GettingStartedDocumentation.java new file mode 100644 index 0000000000..e9fb105438 --- /dev/null +++ b/spring-rest-docs/src/test/java/com/example/GettingStartedDocumentation.java @@ -0,0 +1,148 @@ +package com.example; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.hateoas.MediaTypes; +import org.springframework.restdocs.RestDocumentation; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonpath.JsonPath; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SpringRestDocsApplication.class) +@WebAppConfiguration +public class GettingStartedDocumentation { + + @Rule + public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets"); + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private WebApplicationContext context; + + private MockMvc mockMvc; + + + @Before + public void setUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(documentationConfiguration(this.restDocumentation)).alwaysDo(document("{method-name}/{step}/", + preprocessRequest(prettyPrint()),preprocessResponse(prettyPrint()))).build(); + } + + @Test + public void index() throws Exception { + this.mockMvc.perform(get("/").accept(MediaTypes.HAL_JSON)).andExpect(status().isOk()).andExpect(jsonPath("_links.crud", is(notNullValue()))).andExpect(jsonPath("_links.crud", is(notNullValue()))); + } + + + //@Test + public void creatingANote() throws JsonProcessingException, Exception { + String noteLocation = createNote(); + MvcResult note = getNote(noteLocation); + String tagLocation = createTag(); + getTag(tagLocation); + String taggedNoteLocation = createTaggedNote(tagLocation); + MvcResult taggedNote = getNote(taggedNoteLocation); + getTags(getLink(taggedNote, "note-tags")); + tagExistingNote(noteLocation, tagLocation); + getTags(getLink(note, "note-tags")); + } + + String createNote() throws Exception { + Map note = new HashMap(); + note.put("title", "Note creation with cURL"); + note.put("body", "An example of how to create a note using cURL"); + String noteLocation = this.mockMvc.perform(post("/crud").contentType(MediaTypes.HAL_JSON).content(objectMapper.writeValueAsString(note))).andExpect(status().isCreated()).andExpect(header().string("Location", notNullValue())).andReturn().getResponse().getHeader("Location"); + return noteLocation; + } + + MvcResult getNote(String noteLocation) throws Exception { + return this.mockMvc.perform(get(noteLocation)).andExpect(status().isOk()).andExpect(jsonPath("title", is(notNullValue()))).andExpect(jsonPath("body", is(notNullValue()))).andExpect(jsonPath("_links.crud", is(notNullValue()))).andReturn(); + } + + + String createTag() throws Exception, JsonProcessingException { + Map tag = new HashMap(); + tag.put("name", "getting-started"); + String tagLocation = this.mockMvc.perform(post("/crud").contentType(MediaTypes.HAL_JSON).content(objectMapper.writeValueAsString(tag))).andExpect(status().isCreated()).andExpect(header().string("Location", notNullValue())).andReturn().getResponse().getHeader("Location"); + return tagLocation; + } + + void getTag(String tagLocation) throws Exception { + this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk()) + .andExpect(jsonPath("name", is(notNullValue()))) + .andExpect(jsonPath("_links.tagged-notes", is(notNullValue()))); + } + + String createTaggedNote(String tag) throws Exception { + Map note = new HashMap(); + note.put("title", "Tagged note creation with cURL"); + note.put("body", "An example of how to create a tagged note using cURL"); + note.put("tags", Arrays.asList(tag)); + + String noteLocation = this.mockMvc.perform(post("/notes").contentType(MediaTypes.HAL_JSON).content(objectMapper.writeValueAsString(note))) + .andExpect(status().isCreated()).andExpect(header().string("Location", notNullValue())).andReturn().getResponse().getHeader("Location"); + return noteLocation; + } + + void getTags(String noteTagsLocation) throws Exception { + this.mockMvc.perform(get(noteTagsLocation)) + .andExpect(status().isOk()) + .andExpect(jsonPath("_embedded.tags", hasSize(1))); + } + + void tagExistingNote(String noteLocation, String tagLocation) throws Exception { + Map update = new HashMap(); + update.put("tags", Arrays.asList(tagLocation)); + this.mockMvc.perform(patch(noteLocation).contentType(MediaTypes.HAL_JSON).content(objectMapper.writeValueAsString(update))).andExpect(status().isNoContent()); + } + + MvcResult getTaggedExistingNote(String noteLocation) throws Exception { + return this.mockMvc.perform(get(noteLocation)).andExpect(status().isOk()).andReturn(); + } + + void getTagsForExistingNote(String noteTagsLocation) throws Exception { + this.mockMvc.perform(get(noteTagsLocation)) + .andExpect(status().isOk()).andExpect(jsonPath("_embedded.tags", hasSize(1))); + } + + private String getLink(MvcResult result, String rel) + throws UnsupportedEncodingException { + return JsonPath.parse(result.getResponse().getContentAsString()).read("_links." + rel + ".href"); + } + +}