Introduce Spring Data REST + Spring HATEOAS example.
Show how to integrate custom controller operations with Spring Data REST-provided ones.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.hateoas.examples;
|
||||
|
||||
import static org.springframework.hateoas.examples.OrderStatus.*;
|
||||
|
||||
import org.springframework.data.rest.webmvc.BasePathAwareController;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@BasePathAwareController
|
||||
public class CustomOrderController {
|
||||
|
||||
private final OrderRepository repository;
|
||||
|
||||
public CustomOrderController(OrderRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@PostMapping("/orders/{id}/pay")
|
||||
ResponseEntity<?> pay(@PathVariable Long id) {
|
||||
|
||||
Order order = this.repository.findById(id).orElseThrow(() -> new OrderNotFoundException(id));
|
||||
|
||||
if (valid(order.getOrderStatus(), OrderStatus.PAID_FOR)) {
|
||||
|
||||
order.setOrderStatus(OrderStatus.PAID_FOR);
|
||||
return ResponseEntity.ok(repository.save(order));
|
||||
}
|
||||
|
||||
return ResponseEntity.badRequest()
|
||||
.body("Transitioning from " + order.getOrderStatus() + " to " + OrderStatus.PAID_FOR + " is not valid.");
|
||||
}
|
||||
|
||||
@PostMapping("/orders/{id}/cancel")
|
||||
ResponseEntity<?> cancel(@PathVariable Long id) {
|
||||
|
||||
Order order = this.repository.findById(id).orElseThrow(() -> new OrderNotFoundException(id));
|
||||
|
||||
if (valid(order.getOrderStatus(), OrderStatus.CANCELLED)) {
|
||||
|
||||
order.setOrderStatus(OrderStatus.CANCELLED);
|
||||
return ResponseEntity.ok(repository.save(order));
|
||||
}
|
||||
|
||||
return ResponseEntity.badRequest()
|
||||
.body("Transitioning from " + order.getOrderStatus() + " to " + OrderStatus.CANCELLED + " is not valid.");
|
||||
}
|
||||
|
||||
@PostMapping("/orders/{id}/fulfill")
|
||||
ResponseEntity<?> fulfill(@PathVariable Long id) {
|
||||
|
||||
Order order = this.repository.findById(id).orElseThrow(() -> new OrderNotFoundException(id));
|
||||
|
||||
if (valid(order.getOrderStatus(), OrderStatus.FULFILLED)) {
|
||||
|
||||
order.setOrderStatus(OrderStatus.FULFILLED);
|
||||
return ResponseEntity.ok(repository.save(order));
|
||||
}
|
||||
|
||||
return ResponseEntity.badRequest()
|
||||
.body("Transitioning from " + order.getOrderStatus() + " to " + OrderStatus.FULFILLED + " is not valid.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.hateoas.examples;
|
||||
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@Component
|
||||
public class DatabaseLoader {
|
||||
|
||||
@Bean
|
||||
CommandLineRunner init(OrderRepository repository) {
|
||||
|
||||
return args -> {
|
||||
repository.save(new Order("grande mocha"));
|
||||
repository.save(new Order("venti hazelnut machiatto"));
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.hateoas.examples;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "ORDERS")
|
||||
class Order {
|
||||
|
||||
@Id @GeneratedValue
|
||||
private Long id;
|
||||
|
||||
private OrderStatus orderStatus;
|
||||
|
||||
private String description;
|
||||
|
||||
private Order() {
|
||||
this.id = null;
|
||||
this.orderStatus = OrderStatus.BEING_CREATED;
|
||||
this.description = "";
|
||||
}
|
||||
|
||||
public Order(String description) {
|
||||
this();
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public OrderStatus getOrderStatus() {
|
||||
return orderStatus;
|
||||
}
|
||||
|
||||
public void setOrderStatus(OrderStatus orderStatus) {
|
||||
this.orderStatus = orderStatus;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Order order = (Order) o;
|
||||
return Objects.equals(id, order.id) &&
|
||||
orderStatus == order.orderStatus &&
|
||||
Objects.equals(description, order.description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, orderStatus, description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Order{" +
|
||||
"id=" + id +
|
||||
", orderStatus=" + orderStatus +
|
||||
", description='" + description + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas.examples;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
class OrderNotFoundException extends RuntimeException {
|
||||
|
||||
public OrderNotFoundException(Long id) {
|
||||
super("Order " + id + " not found!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.hateoas.examples;
|
||||
|
||||
import static org.springframework.hateoas.examples.OrderStatus.*;
|
||||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.hateoas.EntityModel;
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.server.RepresentationModelProcessor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A {@link RepresentationModelProcessor} that takes an {@link Order} that has been wrapped by Spring Data REST into an
|
||||
* {@link EntityModel} and applies custom Spring HATEAOS-based {@link Link}s based on the state.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@Component
|
||||
public class OrderProcessor implements RepresentationModelProcessor<EntityModel<Order>> {
|
||||
|
||||
private final RepositoryRestConfiguration configuration;
|
||||
|
||||
public OrderProcessor(RepositoryRestConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityModel<Order> process(EntityModel<Order> model) {
|
||||
|
||||
CustomOrderController controller = methodOn(CustomOrderController.class);
|
||||
String basePath = configuration.getBasePath().toString();
|
||||
|
||||
// If PAID_FOR is valid, add a link to the `pay()` method
|
||||
if (valid(model.getContent().getOrderStatus(), OrderStatus.PAID_FOR)) {
|
||||
model.add(applyBasePath( //
|
||||
linkTo(controller.pay(model.getContent().getId())) //
|
||||
.withRel(IanaLinkRelations.PAYMENT), //
|
||||
basePath));
|
||||
}
|
||||
|
||||
// If CANCELLED is valid, add a link to the `cancel()` method
|
||||
if (valid(model.getContent().getOrderStatus(), OrderStatus.CANCELLED)) {
|
||||
model.add(applyBasePath( //
|
||||
linkTo(controller.cancel(model.getContent().getId())) //
|
||||
.withRel(LinkRelation.of("cancel")), //
|
||||
basePath));
|
||||
}
|
||||
|
||||
// If FULFILLED is valid, add a link to the `fulfill()` method
|
||||
if (valid(model.getContent().getOrderStatus(), OrderStatus.FULFILLED)) {
|
||||
model.add(applyBasePath( //
|
||||
linkTo(controller.fulfill(model.getContent().getId())) //
|
||||
.withRel(LinkRelation.of("fulfill")), //
|
||||
basePath));
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the {@link Link} such that it starts at {@literal basePath}.
|
||||
*
|
||||
* @param link - link presumably supplied via Spring HATEOAS
|
||||
* @param basePath - base path provided by Spring Data REST
|
||||
* @return new {@link Link} with these two values melded together
|
||||
*/
|
||||
private static Link applyBasePath(Link link, String basePath) {
|
||||
|
||||
URI uri = link.toUri();
|
||||
|
||||
URI newUri = null;
|
||||
try {
|
||||
newUri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), //
|
||||
uri.getPort(), basePath + uri.getPath(), uri.getQuery(), uri.getFragment());
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return new Link(newUri.toString(), link.getRel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.hateoas.examples;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public interface OrderRepository extends CrudRepository<Order, Long> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.hateoas.examples;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public enum OrderStatus {
|
||||
|
||||
BEING_CREATED, PAID_FOR, FULFILLED, CANCELLED;
|
||||
|
||||
/**
|
||||
* Verify the transition between {@link OrderStatus} is valid. NOTE: This is where any/all rules for state transitions
|
||||
* should be kept and enforced.
|
||||
*/
|
||||
static boolean valid(OrderStatus currentStatus, OrderStatus newStatus) {
|
||||
|
||||
if (currentStatus == BEING_CREATED) {
|
||||
return newStatus == PAID_FOR || newStatus == CANCELLED;
|
||||
} else if (currentStatus == PAID_FOR) {
|
||||
return newStatus == FULFILLED;
|
||||
} else if (currentStatus == FULFILLED) {
|
||||
return false;
|
||||
} else if (currentStatus == CANCELLED) {
|
||||
return false;
|
||||
} else {
|
||||
throw new RuntimeException("Unrecognized situation.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.hateoas.examples;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SpringHateoasSpringDataRestApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringHateoasSpringDataRestApplication.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
spring:
|
||||
data:
|
||||
rest:
|
||||
base-path: /api
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.hateoas.examples;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@SpringBootTest()
|
||||
@AutoConfigureMockMvc
|
||||
public class OrderIntegrationTest {
|
||||
|
||||
@Autowired MockMvc mvc;
|
||||
|
||||
@Test
|
||||
void basics() throws Exception {
|
||||
|
||||
// Core operations provided by Spring Data REST
|
||||
|
||||
this.mvc.perform(get("/api")) //
|
||||
.andDo(print()) //
|
||||
.andExpect(status().isOk()) //
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON)) //
|
||||
.andExpect(jsonPath("$._links.orders.href", is("http://localhost/api/orders")))
|
||||
.andExpect(jsonPath("$._links.profile.href", is("http://localhost/api/profile")));
|
||||
|
||||
this.mvc.perform(get("/api/orders")).andDo(print()) //
|
||||
.andExpect(status().isOk()) //
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON)) //
|
||||
.andExpect(jsonPath("$._embedded.orders[0].orderStatus", is("BEING_CREATED")))
|
||||
.andExpect(jsonPath("$._embedded.orders[0].description", is("grande mocha")))
|
||||
.andExpect(jsonPath("$._embedded.orders[0]._links.self.href", is("http://localhost/api/orders/1")))
|
||||
.andExpect(jsonPath("$._embedded.orders[0]._links.order.href", is("http://localhost/api/orders/1")))
|
||||
.andExpect(jsonPath("$._embedded.orders[0]._links.payment.href", is("http://localhost/api/orders/1/pay")))
|
||||
.andExpect(jsonPath("$._embedded.orders[0]._links.cancel.href", is("http://localhost/api/orders/1/cancel")))
|
||||
.andExpect(jsonPath("$._embedded.orders[1].orderStatus", is("BEING_CREATED")))
|
||||
.andExpect(jsonPath("$._embedded.orders[1].description", is("venti hazelnut machiatto")))
|
||||
.andExpect(jsonPath("$._embedded.orders[1]._links.self.href", is("http://localhost/api/orders/2")))
|
||||
.andExpect(jsonPath("$._embedded.orders[1]._links.order.href", is("http://localhost/api/orders/2")))
|
||||
.andExpect(jsonPath("$._embedded.orders[1]._links.payment.href", is("http://localhost/api/orders/2/pay")))
|
||||
.andExpect(jsonPath("$._embedded.orders[1]._links.cancel.href", is("http://localhost/api/orders/2/cancel")))
|
||||
.andExpect(jsonPath("$._links.self.href", is("http://localhost/api/orders")))
|
||||
.andExpect(jsonPath("$._links.profile.href", is("http://localhost/api/profile/orders")));
|
||||
|
||||
// Fulfilling an unpaid-for order should fail.
|
||||
|
||||
this.mvc.perform(post("/api/orders/1/fulfill")) //
|
||||
.andDo(print()) //
|
||||
.andExpect(status().is4xxClientError()) //
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) //
|
||||
.andExpect(content().string("\"Transitioning from BEING_CREATED to FULFILLED is not valid.\""));
|
||||
|
||||
// Pay for the order.
|
||||
|
||||
this.mvc.perform(post("/api/orders/1/pay")) //
|
||||
.andDo(print()) //
|
||||
.andExpect(status().isOk()) //
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) //
|
||||
.andExpect(jsonPath("$.id", is(1))) //
|
||||
.andExpect(jsonPath("$.orderStatus", is("PAID_FOR")));
|
||||
|
||||
// Paying for an already paid-for order should fail.
|
||||
|
||||
this.mvc.perform(post("/api/orders/1/pay")) //
|
||||
.andDo(print()) //
|
||||
.andExpect(status().is4xxClientError()) //
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) //
|
||||
.andExpect(content().string("\"Transitioning from PAID_FOR to PAID_FOR is not valid.\""));
|
||||
|
||||
// Cancelling a paid-for order should fail.
|
||||
|
||||
this.mvc.perform(post("/api/orders/1/cancel")) //
|
||||
.andDo(print()) //
|
||||
.andExpect(status().is4xxClientError()) //
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) //
|
||||
.andExpect(content().string("\"Transitioning from PAID_FOR to CANCELLED is not valid.\""));
|
||||
|
||||
// Verify a paid-for order now shows links to fulfill.
|
||||
|
||||
this.mvc.perform(get("/api/orders/1")) //
|
||||
.andDo(print()) //
|
||||
.andExpect(status().isOk()) //
|
||||
.andExpect(content().contentType(MediaTypes.HAL_JSON)) //
|
||||
.andExpect(jsonPath("$.orderStatus", is("PAID_FOR"))) //
|
||||
.andExpect(jsonPath("$.description", is("grande mocha"))) //
|
||||
.andExpect(jsonPath("$._links.self.href", is("http://localhost/api/orders/1")))
|
||||
.andExpect(jsonPath("$._links.order.href", is("http://localhost/api/orders/1")))
|
||||
.andExpect(jsonPath("$._links.fulfill.href", is("http://localhost/api/orders/1/fulfill")));
|
||||
|
||||
// Fulfill the order.
|
||||
|
||||
this.mvc.perform(post("/api/orders/1/fulfill")) //
|
||||
.andDo(print()) //
|
||||
.andExpect(status().isOk()) //
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) //
|
||||
.andExpect(jsonPath("$.orderStatus", is("FULFILLED"))) //
|
||||
.andExpect(jsonPath("$.description", is("grande mocha")));
|
||||
|
||||
// Cancelling a fulfilled order should fail.
|
||||
|
||||
this.mvc.perform(post("/api/orders/1/cancel")) //
|
||||
.andDo(print()) //
|
||||
.andExpect(status().is4xxClientError()) //
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) //
|
||||
.andExpect(content().string("\"Transitioning from FULFILLED to CANCELLED is not valid.\""));
|
||||
|
||||
// Cancel an order.
|
||||
|
||||
this.mvc.perform(post("/api/orders/2/cancel")) //
|
||||
.andDo(print()) //
|
||||
.andExpect(status().isOk()) //
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) //
|
||||
.andExpect(jsonPath("$.orderStatus", is("CANCELLED"))) //
|
||||
.andExpect(jsonPath("$.description", is("venti hazelnut machiatto")));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user