Skip to content

Commit

Permalink
Add samples for multiple responses per resource
Browse files Browse the repository at this point in the history
  • Loading branch information
Mathias Düsterhöft committed Apr 23, 2018
1 parent d3e71d5 commit 462e497
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/docs/asciidoc/products.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,13 @@ operation::product-get[snippets='http-request,http-response,response-fields']
=== Retrieve products

operation::products-get[snippets='http-request,request-parameters,http-response,response-fields,links']

=== Update a product

A patch request can be used to partially update a product.

The update can be expressed in `application/json` or `application/json-patch+json`

operation::product-patch[snippets='http-request']

operation::product-patch-json-patch[snippets='http-request']
5 changes: 5 additions & 0 deletions src/main/java/com/epages/sample/Product.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
import javax.persistence.Id;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;

import org.springframework.hateoas.Identifiable;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
Expand All @@ -31,8 +33,11 @@ public Product(String name, BigDecimal price) {
private Long id;

@NotEmpty
@Setter
private String name;

@NotNull
@Setter
@Positive
private BigDecimal price;
}
24 changes: 24 additions & 0 deletions src/main/java/com/epages/sample/SampleApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,35 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@SpringBootApplication
public class SampleApplication {

public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}

@Configuration
static class ValidationConfiguration extends RepositoryRestConfigurerAdapter {

@Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator());
validatingListener.addValidator("beforeSave", validator());
}

@Bean
@Primary
public Validator validator() {
return new LocalValidatorFactoryBean();
}

}
}
Empty file.
4 changes: 4 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
spring:
output:
ansi:
enabled: 'ALWAYS'
91 changes: 91 additions & 0 deletions src/test/java/com/epages/sample/ProductRestIntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import static lombok.AccessLevel.PRIVATE;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.data.rest.webmvc.RestMediaTypes.JSON_PATCH_JSON;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
import static com.epages.restdocs.raml.RamlDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
Expand All @@ -19,11 +22,17 @@

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.epages.restdocs.raml.ConstrainedFields;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import lombok.SneakyThrows;
import lombok.experimental.FieldDefaults;

Expand All @@ -34,6 +43,10 @@
@AutoConfigureRestDocs
public class ProductRestIntegrationTest extends BaseIntegrationTest {

@Autowired
private ObjectMapper objectMapper;

private ConstrainedFields fields = new ConstrainedFields(Product.class);
@Test
@SneakyThrows
public void should_get_products() {
Expand Down Expand Up @@ -106,11 +119,89 @@ public void should_create_product() {
;
}

@Test
@SneakyThrows
public void should_update_product() {
givenProduct();
givenProductPayload("Updated name", "12.12");

whenProductIsPatched();

resultActions
.andExpect(status().isOk())
.andDo(document("product-patch", requestFields(
fields.withPath("name").description("The name of the product."),
fields.withPath("price").description("The price of the product.")
)))
;
}

@Test
@SneakyThrows
public void should_fail_to_update_product_with_negative_price() {
givenProduct();
givenProductPayload("Updated name", "-12.12");

whenProductIsPatched();

resultActions
.andExpect(status().isBadRequest())
.andDo(document("product-patch-constraint-violation"))
;
}

@Test
@SneakyThrows
public void should_partially_update_product() {
givenProduct();
givenPatchPayload();

whenProductIsPatchedJsonPatch();

resultActions
.andExpect(status().isOk())
.andDo(document("product-patch-json-patch", requestFields(
fields.withPath("[].op").description("Patch operation."),
fields.withPath("[].path").description("The path of the field."),
fields.withPath("[].value").description("The value to assign.")
)))
;
}

@SneakyThrows
private void givenPatchPayload() {
json = objectMapper.writeValueAsString(
ImmutableList.of(
ImmutableMap.of(
"op", "replace",
"path", "/name",
"value", "Fancy socks"
)
)
);
}

@SneakyThrows
private void whenProductIsRetrieved() {
resultActions = mockMvc.perform(get("/products/{id}", productId));
}

@SneakyThrows
private void whenProductIsPatched() {
resultActions = mockMvc.perform(patch("/products/{id}", productId)
.contentType(APPLICATION_JSON)
.accept(APPLICATION_JSON)
.content(json));
}

@SneakyThrows
private void whenProductIsPatchedJsonPatch() {
resultActions = mockMvc.perform(patch("/products/{id}", productId)
.contentType(JSON_PATCH_JSON)
.accept(APPLICATION_JSON)
.content(json));
}

@SneakyThrows
private void whenProductsAreRetrieved() {
resultActions = mockMvc.perform(get("/products")
Expand Down

0 comments on commit 462e497

Please sign in to comment.