├── .travis.yml ├── .settings ├── org.eclipse.m2e.core.prefs ├── org.eclipse.wst.common.project.facet.core.xml ├── org.eclipse.core.resources.prefs └── org.eclipse.jdt.core.prefs ├── src ├── main │ └── java │ │ └── com │ │ └── dzone │ │ └── albanoj2 │ │ └── example │ │ └── rest │ │ ├── domain │ │ ├── Identifiable.java │ │ └── Order.java │ │ ├── resource │ │ ├── ResourceAssembler.java │ │ ├── OrderResource.java │ │ └── OrderResourceAssembler.java │ │ ├── repository │ │ ├── OrderRepository.java │ │ ├── IdGenerator.java │ │ └── InMemoryRepository.java │ │ ├── Application.java │ │ └── controller │ │ └── OrderController.java └── test │ ├── java │ └── com │ │ └── dzone │ │ └── albanoj2 │ │ └── example │ │ └── rest │ │ └── test │ │ ├── acceptance │ │ ├── OrderAcceptanceTests.java │ │ ├── step │ │ │ └── OrderSteps.java │ │ └── util │ │ │ └── AbstractSteps.java │ │ ├── integration │ │ └── controller │ │ │ ├── util │ │ │ ├── CompositeResultMatcher.java │ │ │ ├── ControllerTestUtils.java │ │ │ └── OrderControllerTestUtils.java │ │ │ ├── ControllerIntegrationTest.java │ │ │ └── OrderControllerTest.java │ │ ├── util │ │ └── OrderTestUtils.java │ │ └── unit │ │ └── repository │ │ ├── IdGeneratorTest.java │ │ └── OrderRepositoryTest.java │ └── resources │ └── acceptance │ └── order.feature ├── .gitignore ├── .project ├── .classpath ├── pom.xml └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/main/resources=UTF-8 4 | encoding//src/test/java=UTF-8 5 | encoding//src/test/resources=UTF-8 6 | encoding/=UTF-8 7 | -------------------------------------------------------------------------------- /src/main/java/com/dzone/albanoj2/example/rest/domain/Identifiable.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.domain; 2 | 3 | public interface Identifiable extends org.springframework.hateoas.Identifiable { 4 | public void setId(Long id); 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | 11 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 12 | !/.mvn/wrapper/maven-wrapper.jar 13 | -------------------------------------------------------------------------------- /src/test/java/com/dzone/albanoj2/example/rest/test/acceptance/OrderAcceptanceTests.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.test.acceptance; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.springframework.test.context.web.WebAppConfiguration; 5 | 6 | import cucumber.api.CucumberOptions; 7 | import cucumber.api.junit.Cucumber; 8 | 9 | @RunWith(Cucumber.class) 10 | @CucumberOptions(features = "src/test/resources/acceptance") 11 | @WebAppConfiguration 12 | public class OrderAcceptanceTests { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/dzone/albanoj2/example/rest/resource/ResourceAssembler.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.resource; 2 | 3 | import java.util.Collection; 4 | import java.util.stream.Collectors; 5 | 6 | public abstract class ResourceAssembler { 7 | 8 | public abstract ResourceType toResource(DomainType domainObject); 9 | 10 | public Collection toResourceCollection(Collection domainObjects) { 11 | return domainObjects.stream().map(o -> toResource(o)).collect(Collectors.toList()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/dzone/albanoj2/example/rest/repository/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.repository; 2 | 3 | import org.springframework.stereotype.Repository; 4 | 5 | import com.dzone.albanoj2.example.rest.domain.Order; 6 | 7 | @Repository 8 | public class OrderRepository extends InMemoryRepository { 9 | 10 | protected void updateIfExists(Order original, Order updated) { 11 | original.setDescription(updated.getDescription()); 12 | original.setCostInCents(updated.getCostInCents()); 13 | original.setComplete(updated.isComplete()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/dzone/albanoj2/example/rest/repository/IdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.repository; 2 | 3 | import java.util.concurrent.atomic.AtomicLong; 4 | 5 | import org.springframework.beans.factory.config.BeanDefinition; 6 | import org.springframework.context.annotation.Scope; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | @Scope(BeanDefinition.SCOPE_PROTOTYPE) 11 | public class IdGenerator { 12 | 13 | private AtomicLong nextId = new AtomicLong(1); 14 | 15 | public long getNextId() { 16 | return nextId.getAndIncrement(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.8 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 12 | org.eclipse.jdt.core.compiler.source=1.8 13 | -------------------------------------------------------------------------------- /src/main/java/com/dzone/albanoj2/example/rest/Application.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.hateoas.config.EnableEntityLinks; 6 | import org.springframework.hateoas.config.EnableHypermediaSupport; 7 | import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; 8 | 9 | @EnableEntityLinks 10 | @EnableHypermediaSupport(type = HypermediaType.HAL) 11 | @SpringBootApplication 12 | public class Application { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(Application.class, args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/dzone/albanoj2/example/rest/test/integration/controller/util/CompositeResultMatcher.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.test.integration.controller.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.springframework.test.web.servlet.MvcResult; 7 | import org.springframework.test.web.servlet.ResultMatcher; 8 | 9 | public class CompositeResultMatcher implements ResultMatcher { 10 | 11 | private List matchers = new ArrayList<>(); 12 | 13 | @Override 14 | public void match(MvcResult result) throws Exception { 15 | 16 | for (ResultMatcher matcher: matchers) { 17 | matcher.match(result); 18 | } 19 | } 20 | 21 | public CompositeResultMatcher addMatcher(ResultMatcher matcher) { 22 | matchers.add(matcher); 23 | return this; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/resources/acceptance/order.feature: -------------------------------------------------------------------------------- 1 | Feature: User can successfully get, create, delete, and update orders 2 | 3 | Scenario: User gets a created order 4 | When the user creates an order 5 | And the order is successfully created 6 | And the user gets the created order 7 | Then the user receives status code of 200 8 | And the retrieved order is correct 9 | 10 | Scenario: User gets an existing order 11 | Given an order exists 12 | When the user gets the created order 13 | Then the user receives status code of 200 14 | And the retrieved order is correct 15 | 16 | Scenario: User deletes a created order 17 | Given an order exists 18 | And the user deletes the created order 19 | And the user receives status code of 204 20 | When the user gets the created order 21 | Then the user receives status code of 404 22 | -------------------------------------------------------------------------------- /src/main/java/com/dzone/albanoj2/example/rest/resource/OrderResource.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.resource; 2 | 3 | import org.springframework.hateoas.ResourceSupport; 4 | 5 | import com.dzone.albanoj2.example.rest.domain.Order; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | 8 | public class OrderResource extends ResourceSupport { 9 | 10 | private final long id; 11 | private final String description; 12 | private final long costInCents; 13 | private final boolean isComplete; 14 | 15 | public OrderResource(Order order) { 16 | id = order.getId(); 17 | description = order.getDescription(); 18 | costInCents = order.getCostInCents(); 19 | isComplete = order.isComplete(); 20 | } 21 | 22 | @JsonProperty("id") 23 | public Long getResourceId() { 24 | return id; 25 | } 26 | 27 | public String getDescription() { 28 | return description; 29 | } 30 | 31 | public long getCostInCents() { 32 | return costInCents; 33 | } 34 | 35 | public boolean isComplete() { 36 | return isComplete; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/dzone/albanoj2/example/rest/resource/OrderResourceAssembler.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.resource; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.hateoas.EntityLinks; 5 | import org.springframework.hateoas.Link; 6 | import org.springframework.stereotype.Component; 7 | 8 | import com.dzone.albanoj2.example.rest.domain.Order; 9 | 10 | @Component 11 | public class OrderResourceAssembler extends ResourceAssembler { 12 | 13 | @Autowired 14 | protected EntityLinks entityLinks; 15 | 16 | private static final String UPDATE_REL = "update"; 17 | private static final String DELETE_REL = "delete"; 18 | 19 | @Override 20 | public OrderResource toResource(Order order) { 21 | 22 | OrderResource resource = new OrderResource(order); 23 | 24 | final Link selfLink = entityLinks.linkToSingleResource(order); 25 | 26 | resource.add(selfLink.withSelfRel()); 27 | resource.add(selfLink.withRel(UPDATE_REL)); 28 | resource.add(selfLink.withRel(DELETE_REL)); 29 | 30 | return resource; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/dzone/albanoj2/example/rest/domain/Order.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.domain; 2 | 3 | public class Order implements Identifiable { 4 | 5 | private Long id; 6 | private String description; 7 | private long costInCents; 8 | private boolean isComplete; 9 | 10 | @Override 11 | public Long getId() { 12 | return id; 13 | } 14 | 15 | @Override 16 | public void setId(Long id) { 17 | this.id = id; 18 | } 19 | 20 | public String getDescription() { 21 | return description; 22 | } 23 | 24 | public void setDescription(String description) { 25 | this.description = description; 26 | } 27 | 28 | public void setCostInCents(long cost) { 29 | costInCents = cost; 30 | } 31 | 32 | public long getCostInCents() { 33 | return costInCents; 34 | } 35 | 36 | public void setComplete(boolean isComplete) { 37 | this.isComplete = isComplete; 38 | } 39 | 40 | public void markComplete() { 41 | setComplete(true); 42 | } 43 | 44 | public void markIncomplete() { 45 | setComplete(false); 46 | } 47 | 48 | public boolean isComplete() { 49 | return isComplete; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/dzone/albanoj2/example/rest/test/util/OrderTestUtils.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.test.util; 2 | 3 | import org.junit.Assert; 4 | 5 | import com.dzone.albanoj2.example.rest.domain.Order; 6 | 7 | public class OrderTestUtils { 8 | 9 | public static void assertAllButIdsMatchBetweenOrders(Order expected, Order actual) { 10 | Assert.assertEquals(expected.getDescription(), actual.getDescription()); 11 | Assert.assertEquals(expected.getCostInCents(), actual.getCostInCents()); 12 | Assert.assertEquals(expected.isComplete(), actual.isComplete()); 13 | } 14 | 15 | public static Order generateTestOrder() { 16 | Order order = new Order(); 17 | order.setDescription("test description"); 18 | order.setCostInCents(150L); 19 | order.markIncomplete(); 20 | return order; 21 | } 22 | 23 | public static Order generateUpdatedOrder(Order original) { 24 | Order updated = new Order(); 25 | updated.setDescription(original.getDescription() + " updated"); 26 | updated.setCostInCents(original.getCostInCents() + 100); 27 | updated.markComplete(); 28 | return updated; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/dzone/albanoj2/example/rest/test/unit/repository/IdGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.test.unit.repository; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.annotation.DirtiesContext; 9 | import org.springframework.test.annotation.DirtiesContext.ClassMode; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | 12 | import com.dzone.albanoj2.example.rest.repository.IdGenerator; 13 | 14 | 15 | @RunWith(SpringRunner.class) 16 | @SpringBootTest 17 | @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) 18 | public class IdGeneratorTest { 19 | 20 | @Autowired 21 | private IdGenerator generator1; 22 | 23 | @Autowired 24 | private IdGenerator generator2; 25 | 26 | @Test 27 | public void testMultipleGeneratorsEnsureGeneratorsDoNotInterfere() throws Exception { 28 | Assert.assertEquals(1, generator1.getNextId()); 29 | Assert.assertEquals(2, generator1.getNextId()); 30 | Assert.assertEquals(1, generator2.getNextId()); 31 | Assert.assertEquals(2, generator2.getNextId()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | order-rest-backend 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.common.project.facet.core.builder 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.springframework.ide.eclipse.core.springbuilder 20 | 21 | 22 | 23 | 24 | org.springframework.ide.eclipse.boot.validation.springbootbuilder 25 | 26 | 27 | 28 | 29 | org.eclipse.m2e.core.maven2Builder 30 | 31 | 32 | 33 | 34 | 35 | org.springframework.ide.eclipse.core.springnature 36 | org.eclipse.jdt.core.javanature 37 | org.eclipse.m2e.core.maven2Nature 38 | org.eclipse.wst.common.project.facet.core.nature 39 | 40 | 41 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/java/com/dzone/albanoj2/example/rest/repository/InMemoryRepository.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.repository; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.Optional; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | 10 | import com.dzone.albanoj2.example.rest.domain.Identifiable; 11 | 12 | 13 | public abstract class InMemoryRepository { 14 | 15 | @Autowired 16 | private IdGenerator idGenerator; 17 | 18 | private List elements = Collections.synchronizedList(new ArrayList<>()); 19 | 20 | public T create(T element) { 21 | elements.add(element); 22 | element.setId(idGenerator.getNextId()); 23 | return element; 24 | } 25 | 26 | public boolean delete(Long id) { 27 | return elements.removeIf(element -> element.getId().equals(id)); 28 | } 29 | 30 | public List findAll() { 31 | return elements; 32 | } 33 | 34 | public Optional findById(Long id) { 35 | return elements.stream().filter(e -> e.getId().equals(id)).findFirst(); 36 | } 37 | 38 | public int getCount() { 39 | return elements.size(); 40 | } 41 | 42 | public void clear() { 43 | elements.clear(); 44 | } 45 | 46 | public boolean update(Long id, T updated) { 47 | 48 | if (updated == null) { 49 | return false; 50 | } 51 | else { 52 | Optional element = findById(id); 53 | element.ifPresent(original -> updateIfExists(original, updated)); 54 | return element.isPresent(); 55 | } 56 | } 57 | 58 | protected abstract void updateIfExists(T original, T desired); 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/dzone/albanoj2/example/rest/test/integration/controller/util/ControllerTestUtils.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.test.integration.controller.util; 2 | 3 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 4 | 5 | import org.springframework.test.web.servlet.ResultMatcher; 6 | 7 | public class ControllerTestUtils { 8 | 9 | private static final String SELF_REL = "self"; 10 | private static final String UPDATE_REL = "update"; 11 | private static final String DELETE_REL = "delete"; 12 | 13 | public static ResultMatcher selfLinkAtIndexIs(int index, String expected) { 14 | return linkAtIndexIs(index, SELF_REL, expected); 15 | } 16 | 17 | private static ResultMatcher linkAtIndexIs(int index, String linkKey, String expected) { 18 | return jsonPath("$.[" + index + "]._links." + linkKey + ".href").value(expected); 19 | } 20 | 21 | public static ResultMatcher selfLinkIs(String expected) { 22 | return linkIs(SELF_REL, expected); 23 | } 24 | 25 | public static ResultMatcher linkIs(String linkKey, String expected) { 26 | return jsonPath("$._links." + linkKey + ".href").value(expected); 27 | } 28 | 29 | public static ResultMatcher updateLinkAtIndexIs(int index, String expected) { 30 | return linkAtIndexIs(index, UPDATE_REL, expected); 31 | } 32 | 33 | public static ResultMatcher updateLinkIs(String expected) { 34 | return linkIs(UPDATE_REL, expected); 35 | } 36 | 37 | public static ResultMatcher deleteLinkAtIndexIs(int index, String expected) { 38 | return linkAtIndexIs(index, DELETE_REL, expected); 39 | } 40 | 41 | public static ResultMatcher deleteLinkIs(String expected) { 42 | return linkIs(DELETE_REL, expected); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/dzone/albanoj2/example/rest/test/integration/controller/ControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.test.integration.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.test.web.servlet.MockMvc; 7 | import org.springframework.test.web.servlet.ResultActions; 8 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 9 | 10 | import com.fasterxml.jackson.core.JsonProcessingException; 11 | import com.fasterxml.jackson.databind.ObjectMapper; 12 | 13 | 14 | @AutoConfigureMockMvc 15 | public class ControllerIntegrationTest { 16 | 17 | @Autowired 18 | private MockMvc mvc; 19 | 20 | protected ResultActions get(String url, Object... urlVariables) throws Exception { 21 | return mvc.perform(MockMvcRequestBuilders.get(url, urlVariables).accept(MediaType.APPLICATION_JSON)); 22 | } 23 | 24 | protected ResultActions post(String url, String content, Object... urlVariables) throws Exception { 25 | return mvc.perform(MockMvcRequestBuilders.post(url, urlVariables) 26 | .accept(MediaType.APPLICATION_JSON) 27 | .contentType(MediaType.APPLICATION_JSON) 28 | .content(content) 29 | ); 30 | } 31 | 32 | protected ResultActions delete(String url, Object... urlVariables) throws Exception { 33 | return mvc.perform(MockMvcRequestBuilders.delete(url, urlVariables).accept(MediaType.APPLICATION_JSON)); 34 | } 35 | 36 | protected ResultActions put(String url, Object content, Object... urlVariables) throws Exception { 37 | return mvc.perform(MockMvcRequestBuilders.put(url, urlVariables) 38 | .accept(MediaType.APPLICATION_JSON) 39 | .contentType(MediaType.APPLICATION_JSON) 40 | .content(toJsonString(content)) 41 | ); 42 | } 43 | 44 | protected static String toJsonString(final Object obj) throws JsonProcessingException { 45 | ObjectMapper mapper = new ObjectMapper(); 46 | return mapper.writeValueAsString(obj); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.dzone.albanoj2.example.rest 7 | order-rest-backend 8 | 0.0.1-SNAPSHOT 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 1.4.7.RELEASE 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-web 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-test 24 | test 25 | 26 | 27 | com.jayway.jsonpath 28 | json-path 29 | test 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-hateoas 34 | 35 | 36 | junit 37 | junit 38 | test 39 | 40 | 41 | info.cukes 42 | cucumber-junit 43 | 1.2.5 44 | test 45 | 46 | 47 | info.cukes 48 | cucumber-spring 49 | 1.2.5 50 | test 51 | 52 | 53 | commons-io 54 | commons-io 55 | 2.5 56 | 57 | 58 | 59 | 60 | 61 | 1.8 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-maven-plugin 70 | 71 | 72 | 73 | 74 | 75 | 76 | spring-releases 77 | https://repo.spring.io/libs-release 78 | 79 | 80 | 81 | 82 | spring-releases 83 | https://repo.spring.io/libs-release 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/test/java/com/dzone/albanoj2/example/rest/test/integration/controller/util/OrderControllerTestUtils.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.test.integration.controller.util; 2 | 3 | import static com.dzone.albanoj2.example.rest.test.integration.controller.util.ControllerTestUtils.*; 4 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 5 | 6 | import org.springframework.hateoas.EntityLinks; 7 | import org.springframework.test.web.servlet.ResultMatcher; 8 | 9 | import com.dzone.albanoj2.example.rest.domain.Order; 10 | 11 | public class OrderControllerTestUtils { 12 | 13 | public static ResultMatcher orderAtIndexIsCorrect(int index, Order expected) { 14 | return new CompositeResultMatcher() 15 | .addMatcher(jsonPath("$.[" + index + "].id").value(expected.getId())) 16 | .addMatcher(jsonPath("$.[" + index + "].description").value(expected.getDescription())) 17 | .addMatcher(jsonPath("$.[" + index + "].costInCents").value(expected.getCostInCents())) 18 | .addMatcher(jsonPath("$.[" + index + "].complete").value(expected.isComplete())); 19 | } 20 | 21 | public static ResultMatcher orderIsCorrect(Order expected) { 22 | return orderIsCorrect(expected.getId(), expected); 23 | } 24 | 25 | private static ResultMatcher orderIsCorrect(Long expectedId, Order expected) { 26 | return new CompositeResultMatcher().addMatcher(jsonPath("$.id").value(expectedId)) 27 | .addMatcher(jsonPath("$.description").value(expected.getDescription())) 28 | .addMatcher(jsonPath("$.costInCents").value(expected.getCostInCents())) 29 | .addMatcher(jsonPath("$.complete").value(expected.isComplete())); 30 | } 31 | 32 | public static ResultMatcher updatedOrderIsCorrect(Long originalId, Order expected) { 33 | return orderIsCorrect(originalId, expected); 34 | } 35 | 36 | public static ResultMatcher orderLinksAtIndexAreCorrect(int index, Order expected, EntityLinks entityLinks) { 37 | final String selfReference = entityLinks.linkForSingleResource(expected).toString(); 38 | 39 | return new CompositeResultMatcher() 40 | .addMatcher(selfLinkAtIndexIs(index, selfReference)) 41 | .addMatcher(updateLinkAtIndexIs(index, selfReference)) 42 | .addMatcher(deleteLinkAtIndexIs(index, selfReference)); 43 | } 44 | 45 | public static ResultMatcher orderLinksAreCorrect(Order expected, EntityLinks entityLinks) { 46 | final String selfReference = entityLinks.linkForSingleResource(expected).toString(); 47 | 48 | return new CompositeResultMatcher() 49 | .addMatcher(selfLinkIs(selfReference)) 50 | .addMatcher(updateLinkIs(selfReference)) 51 | .addMatcher(deleteLinkIs(selfReference)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Order Management REST API ![CI Status](https://travis-ci.org/albanoj2/order-rest-backend.svg?branch=master) 2 | 3 | Demonstrates a simple RESTful web service using Spring MVC and Java. This web service provides an in-memory order management service, with the capability to get a single order, get all orders, create an order, delete an order, and update an order. After exploring this project, the reader will understand the three common layers of a RESTful web service (namely domain, data source, and presentation), the common design decisions used when creating a web service, and the functionality provided by the Spring framework for creating a web service (as well as supplemental functionality, such as creating [HATEOAS](http://projects.spring.io/spring-hateoas/) links). In particular, the reader will learn 4 | 5 | - How to create a domain model 6 | - How to store domain objects in a persistence layer 7 | - How to wrap domain objects within resource objects 8 | - How to add HATEOAS links to a resource 9 | - How to serve up resources to a client over HTTP 10 | - How to provide RESTful Create, Read, Update, and Delete (CRUD) operations to change domain objects 11 | - How to create unit, integration, and acceptance tests that exercise a REST API 12 | 13 | ## Starting the Order Management System 14 | To start this web service, install [Maven](https://maven.apache.org/install.html) and execute the following command 15 | 16 | mvn spring-boot:run 17 | 18 | Once the web service is started, it can be reached at 19 | 20 | http://localhost:8080/order 21 | 22 | ## REST Endpoints 23 | The following REST endpoints are available upon deployment of the order management system: 24 | 25 | | HTTP Verb | URL | Description | Status Codes | 26 | | ------------- |-------------|:-----| ----| 27 | | `GET` | `http://localhost:8080/order` | Obtains a list of all existing orders |
  • `200 OK`
| 28 | | `GET` | `http://localhost:8080/order/{id}` | Obtains the order corresponding to the supplied order ID |
  • `200 OK` if order exists
  • `404 Not Found` if order does not exist
| 29 | | `POST` | `http://localhost:8080/order` | Creates a new order based on the payload contained in the request body |
  • `201 Created` if order successfully created
| 30 | | `PUT` | `http://localhost:8080/order/{id}` | Updated an existing order with the data contained in the request body |
  • `200 OK` if order succesfully updated
  • `404 Not Found` if order does not exist
| 31 | | `DELETE` | `http://localhost:8080/order/{id}` | Deletes an existing order that corresponds to the supplied order ID |
  • `204 No Content` if order succesfully deleted
  • `404 Not Found` if order does not exist
| 32 | -------------------------------------------------------------------------------- /src/test/java/com/dzone/albanoj2/example/rest/test/acceptance/step/OrderSteps.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.test.acceptance.step; 2 | 3 | import java.util.Map; 4 | 5 | import org.junit.Assert; 6 | 7 | import com.dzone.albanoj2.example.rest.test.acceptance.util.AbstractSteps; 8 | import com.fasterxml.jackson.core.type.TypeReference; 9 | 10 | import cucumber.api.java.en.And; 11 | import cucumber.api.java.en.Given; 12 | import cucumber.api.java.en.Then; 13 | import cucumber.api.java.en.When; 14 | 15 | public class OrderSteps extends AbstractSteps { 16 | 17 | private static final String TEST_ORDER = "{\"description\": \"some test order\", \"lineItems\": [{\"name\": \"test item 1\", \"description\": \"some test item 1\", \"costInCents\": 100}, {\"name\": \"test item 2\", \"description\": \"some test item 2\", \"costInCents\": 200}]}"; 18 | private static final TypeReference> RESOURCE_TYPE = new TypeReference>() {}; 19 | 20 | @Given("^an order exists$") 21 | public void anOrderExists() throws Throwable { 22 | createOrder(); 23 | } 24 | 25 | private void createOrder() throws Exception { 26 | post("/order", TEST_ORDER); 27 | } 28 | 29 | @When("^the user creates an order$") 30 | public void theUserCallsGetOrders() throws Throwable { 31 | createOrder(); 32 | } 33 | 34 | @When("^the user deletes the created order$") 35 | public void theUserDeletesTheCreatedOrder() throws Throwable { 36 | delete("/order/{id}", getCreatedId()); 37 | } 38 | 39 | private Object getCreatedId() throws Exception { 40 | return getLastPostContentAs(RESOURCE_TYPE).get("id"); 41 | } 42 | 43 | @And("^the order is successfully created$") 44 | public void theOrderIsSuccessfullyCreated() { 45 | Assert.assertEquals(201, getLastPostResponse().getStatus()); 46 | } 47 | 48 | @And("^the user gets the created order$") 49 | public void theUserRetrievesTheOrder() throws Throwable { 50 | get("/order/{id}", getCreatedId()); 51 | } 52 | 53 | @Then("^the user receives status code of (\\d+)$") 54 | public void theUserReceivesStatusCodeOf(int statusCode) throws Throwable { 55 | Assert.assertEquals(statusCode, getLastStatusCode()); 56 | } 57 | 58 | @And("^the retrieved order is correct$") 59 | public void theRetrievedOrderIsCorrect() throws Throwable { 60 | assertOrderResourcesMatch(getLastPostContentAs(RESOURCE_TYPE), getLastGetContentAs(RESOURCE_TYPE)); 61 | } 62 | 63 | private static void assertOrderResourcesMatch(Map expected, Map actual) { 64 | Assert.assertEquals(expected.size(), actual.size()); 65 | 66 | for (String key: expected.keySet()) { 67 | Assert.assertEquals(expected.get(key), actual.get(key)); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/dzone/albanoj2/example/rest/controller/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.controller; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.hateoas.ExposesResourceFor; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.CrossOrigin; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestMethod; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | import com.dzone.albanoj2.example.rest.domain.Order; 19 | import com.dzone.albanoj2.example.rest.repository.OrderRepository; 20 | import com.dzone.albanoj2.example.rest.resource.OrderResource; 21 | import com.dzone.albanoj2.example.rest.resource.OrderResourceAssembler; 22 | 23 | @CrossOrigin(origins = "*") 24 | @RestController 25 | @ExposesResourceFor(Order.class) 26 | @RequestMapping(value = "/order", produces = "application/json") 27 | public class OrderController { 28 | 29 | @Autowired 30 | private OrderRepository repository; 31 | 32 | @Autowired 33 | private OrderResourceAssembler assembler; 34 | 35 | @RequestMapping(method = RequestMethod.GET) 36 | public ResponseEntity> findAllOrders() { 37 | List orders = repository.findAll(); 38 | return new ResponseEntity<>(assembler.toResourceCollection(orders), HttpStatus.OK); 39 | } 40 | 41 | @RequestMapping(method = RequestMethod.POST, consumes = "application/json") 42 | public ResponseEntity createOrder(@RequestBody Order order) { 43 | Order createdOrder = repository.create(order); 44 | return new ResponseEntity<>(assembler.toResource(createdOrder), HttpStatus.CREATED); 45 | } 46 | 47 | @RequestMapping(value = "/{id}", method = RequestMethod.GET) 48 | public ResponseEntity findOrderById(@PathVariable Long id) { 49 | Optional order = repository.findById(id); 50 | 51 | if (order.isPresent()) { 52 | return new ResponseEntity<>(assembler.toResource(order.get()), HttpStatus.OK); 53 | } 54 | else { 55 | return new ResponseEntity<>(HttpStatus.NOT_FOUND); 56 | } 57 | } 58 | 59 | @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) 60 | public ResponseEntity deleteOrder(@PathVariable Long id) { 61 | boolean wasDeleted = repository.delete(id); 62 | HttpStatus responseStatus = wasDeleted ? HttpStatus.NO_CONTENT : HttpStatus.NOT_FOUND; 63 | return new ResponseEntity<>(responseStatus); 64 | } 65 | 66 | @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = "application/json") 67 | public ResponseEntity updateOrder(@PathVariable Long id, @RequestBody Order updatedOrder) { 68 | boolean wasUpdated = repository.update(id, updatedOrder); 69 | 70 | if (wasUpdated) { 71 | return findOrderById(id); 72 | } 73 | else { 74 | return new ResponseEntity<>(HttpStatus.NOT_FOUND); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/com/dzone/albanoj2/example/rest/test/acceptance/util/AbstractSteps.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.test.acceptance.util; 2 | 3 | import java.io.IOException; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.mock.web.MockHttpServletResponse; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.web.WebAppConfiguration; 11 | import org.springframework.test.web.servlet.MockMvc; 12 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 13 | 14 | import com.dzone.albanoj2.example.rest.Application; 15 | import com.fasterxml.jackson.core.JsonParseException; 16 | import com.fasterxml.jackson.core.type.TypeReference; 17 | import com.fasterxml.jackson.databind.JsonMappingException; 18 | import com.fasterxml.jackson.databind.ObjectMapper; 19 | 20 | @WebAppConfiguration 21 | @ContextConfiguration(classes = Application.class) 22 | @AutoConfigureMockMvc 23 | public abstract class AbstractSteps { 24 | 25 | private static ObjectMapper mapper = new ObjectMapper(); 26 | 27 | @Autowired 28 | private MockMvc mvc; 29 | 30 | private MockHttpServletResponse lastGetResponse; 31 | private MockHttpServletResponse lastPostResponse; 32 | private MockHttpServletResponse lastPutResponse; 33 | private MockHttpServletResponse lastDeleteResponse; 34 | private int lastStatusCode; 35 | 36 | protected void get(String url, Object... urlVariable) throws Exception { 37 | mvc.perform(MockMvcRequestBuilders.get(url, urlVariable) 38 | .accept(MediaType.APPLICATION_JSON) 39 | ) 40 | .andDo(result -> { 41 | lastGetResponse = result.getResponse(); 42 | lastStatusCode = lastGetResponse.getStatus(); 43 | }); 44 | } 45 | 46 | protected void post(String url, String body, Object... urlVariables) throws Exception { 47 | mvc.perform(MockMvcRequestBuilders.post(url, urlVariables) 48 | .accept(MediaType.APPLICATION_JSON) 49 | .contentType(MediaType.APPLICATION_JSON) 50 | .content(body)) 51 | .andDo(result -> { 52 | lastPostResponse = result.getResponse(); 53 | lastStatusCode = lastPostResponse.getStatus(); 54 | }); 55 | } 56 | 57 | protected void put(String url, String body) throws Exception { 58 | mvc.perform(MockMvcRequestBuilders.put(url) 59 | .accept(MediaType.APPLICATION_JSON) 60 | .contentType(MediaType.APPLICATION_JSON) 61 | .content(body)) 62 | .andDo(result -> { 63 | lastPutResponse = result.getResponse(); 64 | lastStatusCode = lastPutResponse.getStatus(); 65 | }); 66 | } 67 | 68 | protected void delete(String url, Object... urlVariables) throws Exception { 69 | mvc.perform(MockMvcRequestBuilders.delete(url, urlVariables) 70 | .accept(MediaType.APPLICATION_JSON)) 71 | .andDo(result -> { 72 | lastDeleteResponse = result.getResponse(); 73 | lastStatusCode = lastDeleteResponse.getStatus(); 74 | }); 75 | } 76 | 77 | protected MockHttpServletResponse getLastGetResponse() { 78 | return lastGetResponse; 79 | } 80 | 81 | protected T getLastGetContentAs(TypeReference type) throws Exception { 82 | return deserializeResponse(getLastGetResponse(), type); 83 | } 84 | 85 | protected T getLastPostContentAs(TypeReference type) throws Exception { 86 | return deserializeResponse(getLastPostResponse(), type); 87 | } 88 | 89 | private static T deserializeResponse(MockHttpServletResponse response, TypeReference type) throws Exception { 90 | return deserialize(response.getContentAsString(), type); 91 | } 92 | 93 | protected MockHttpServletResponse getLastPostResponse() { 94 | return lastPostResponse; 95 | } 96 | 97 | protected MockHttpServletResponse getLastPutResponse() { 98 | return lastPutResponse; 99 | } 100 | 101 | protected MockHttpServletResponse getLastDeleteResponse() { 102 | return lastDeleteResponse; 103 | } 104 | 105 | protected static T deserialize(String json, Class type) throws JsonParseException, JsonMappingException, IOException { 106 | return mapper.readValue(json, type); 107 | } 108 | 109 | protected static T deserialize(String json, TypeReference type) throws JsonParseException, JsonMappingException, IOException { 110 | return mapper.readValue(json, type); 111 | } 112 | 113 | public int getLastStatusCode() { 114 | return lastStatusCode; 115 | } 116 | } -------------------------------------------------------------------------------- /src/test/java/com/dzone/albanoj2/example/rest/test/unit/repository/OrderRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.test.unit.repository; 2 | 3 | import static com.dzone.albanoj2.example.rest.test.util.OrderTestUtils.*; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | import org.junit.Assert; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | 17 | import com.dzone.albanoj2.example.rest.domain.Order; 18 | import com.dzone.albanoj2.example.rest.repository.OrderRepository; 19 | 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest 22 | public class OrderRepositoryTest { 23 | 24 | private static final long NONEXISTENT_ID = 1000; 25 | 26 | @Autowired 27 | private OrderRepository repository; 28 | 29 | @Before 30 | public void setUp() { 31 | repository.clear(); 32 | } 33 | 34 | @Test 35 | public void testFindNonexistentOrderEnsureOptionalIsNotPresent() throws Exception { 36 | assertNoExistingOrders(); 37 | Optional order = repository.findById(NONEXISTENT_ID); 38 | Assert.assertFalse(order.isPresent()); 39 | } 40 | 41 | private void assertNoExistingOrders() { 42 | assertExistingOrderCountIs(0); 43 | } 44 | 45 | private void assertExistingOrderCountIs(int count) { 46 | Assert.assertEquals(count, repository.getCount()); 47 | } 48 | 49 | @Test 50 | public void testFindExistingOrderEnsureOptionalIsPresent() throws Exception { 51 | Order injectedOrder = injectOrder(); 52 | Optional foundOrder = repository.findById(injectedOrder.getId()); 53 | Assert.assertTrue(foundOrder.isPresent()); 54 | } 55 | 56 | private Order injectOrder() { 57 | Order createdOrder = repository.create(generateTestOrder()); 58 | return createdOrder; 59 | } 60 | 61 | @Test 62 | public void testFindExistingOrderEnsureCorrectOrderValues() throws Exception { 63 | Order injectedOrder = injectOrder(); 64 | Optional foundOrder = repository.findById(injectedOrder.getId()); 65 | assertOrdersMatch(injectedOrder, foundOrder.get()); 66 | } 67 | 68 | private static void assertOrdersMatch(Order expected, Order actual) { 69 | Assert.assertEquals(expected.getId(), actual.getId()); 70 | assertAllButIdsMatchBetweenOrders(expected, actual); 71 | } 72 | 73 | @Test 74 | public void testFindAllWithNoExistingOrdersEnsureNoOrdersFound() throws Exception { 75 | assertFindAllIsCorrectWithOrderCount(0); 76 | } 77 | 78 | private void assertFindAllIsCorrectWithOrderCount(int count) { 79 | injectGivenNumberOfOrders(count); 80 | assertExistingOrderCountIs(count); 81 | List ordersFound = repository.findAll(); 82 | Assert.assertEquals(count, ordersFound.size()); 83 | } 84 | 85 | private List injectGivenNumberOfOrders(int count) { 86 | 87 | List injectedOrders = new ArrayList<>(); 88 | 89 | for (int i = 0; i < count; i++) { 90 | injectedOrders.add(injectOrder()); 91 | } 92 | 93 | return injectedOrders; 94 | } 95 | 96 | @Test 97 | public void testFindAllWithOneExistingOrdersEnsureOneOrdersFound() throws Exception { 98 | assertFindAllIsCorrectWithOrderCount(1); 99 | } 100 | 101 | @Test 102 | public void testFindAllWithTwoExistingOrdersEnsureTwoOrdersFound() throws Exception { 103 | assertFindAllIsCorrectWithOrderCount(2); 104 | } 105 | 106 | @Test 107 | public void testFindAllWithTwoExistingOrderEnsureFirstOrderIsCorrect() throws Exception { 108 | List injectedOrders = injectGivenNumberOfOrders(2); 109 | List ordersFound = repository.findAll(); 110 | assertOrdersMatch(injectedOrders.get(0), ordersFound.get(0)); 111 | } 112 | 113 | @Test 114 | public void testFindAllWithTwoExistingOrderEnsureSecondOrderIsCorrect() throws Exception { 115 | List injectedOrders = injectGivenNumberOfOrders(2); 116 | List ordersFound = repository.findAll(); 117 | assertOrdersMatch(injectedOrders.get(1), ordersFound.get(1)); 118 | } 119 | 120 | @Test 121 | public void testDeleteNonexistentOrderEnsureNoOrderDeleted() throws Exception { 122 | assertNoExistingOrders(); 123 | boolean wasDeleted = repository.delete(NONEXISTENT_ID); 124 | Assert.assertFalse(wasDeleted); 125 | } 126 | 127 | @Test 128 | public void testDeleteExistingOrderEnsureOrderDeleted() throws Exception { 129 | Order injectedOrder = injectOrder(); 130 | assertExistingOrderCountIs(1); 131 | boolean wasDeleted = repository.delete(injectedOrder.getId()); 132 | Assert.assertTrue(wasDeleted); 133 | assertNoExistingOrders(); 134 | } 135 | 136 | @Test 137 | public void testUpdateNonexistentOrderEnsureNoUpdateMade() throws Exception { 138 | assertNoExistingOrders(); 139 | boolean wasUpdated = repository.update(NONEXISTENT_ID, new Order()); 140 | Assert.assertFalse(wasUpdated); 141 | } 142 | 143 | @Test 144 | public void testUpdateExistingOrderEnsureUpdateMade() throws Exception { 145 | Order injectedOrder = injectOrder(); 146 | boolean wasUpdated = repository.update(injectedOrder.getId(), new Order()); 147 | Assert.assertTrue(wasUpdated); 148 | } 149 | 150 | @Test 151 | public void testUpdateExistingOrderEnsureOriginalOrderIsUpdated() throws Exception { 152 | Order originalOrder = injectOrder(); 153 | Order updatedOrder = generateUpdatedOrder(originalOrder); 154 | repository.update(originalOrder.getId(), updatedOrder); 155 | assertAllButIdsMatchBetweenOrders(updatedOrder, originalOrder); 156 | } 157 | 158 | @Test 159 | public void testUpdateExistingOrderWithNullUpdatedOrderEnsureNoUpdateMade() throws Exception { 160 | Order injectedOrder = injectOrder(); 161 | boolean wasUpdated = repository.update(injectedOrder.getId(), null); 162 | Assert.assertFalse(wasUpdated); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/test/java/com/dzone/albanoj2/example/rest/test/integration/controller/OrderControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.dzone.albanoj2.example.rest.test.integration.controller; 2 | 3 | import static com.dzone.albanoj2.example.rest.test.integration.controller.util.OrderControllerTestUtils.*; 4 | import static com.dzone.albanoj2.example.rest.test.util.OrderTestUtils.*; 5 | import static org.hamcrest.CoreMatchers.equalTo; 6 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 7 | 8 | import java.util.List; 9 | 10 | import org.junit.Assert; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.hateoas.EntityLinks; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | import org.springframework.test.web.servlet.ResultActions; 19 | 20 | import com.dzone.albanoj2.example.rest.domain.Order; 21 | import com.dzone.albanoj2.example.rest.repository.OrderRepository; 22 | 23 | @RunWith(SpringRunner.class) 24 | @SpringBootTest 25 | public class OrderControllerTest extends ControllerIntegrationTest { 26 | 27 | private static final String INVALID_TEST_ORDER = ""; 28 | private static final String TEST_ORDER = "{\"description\": \"Some test description\", \"costInCents\": 200}"; 29 | private static final String TEST_ORDER_MISSING_ORDER_DATA = "{\"foo\": \"bar\"}"; 30 | 31 | @Autowired 32 | private OrderRepository repository; 33 | 34 | @Autowired 35 | private EntityLinks entityLinks; 36 | 37 | @Before 38 | public void setUp() { 39 | repository.clear(); 40 | } 41 | 42 | @Test 43 | public void testGetAllEmptyListEnsureCorrectResponse() throws Exception { 44 | assertNoOrders(); 45 | getOrder() 46 | .andExpect(status().isOk()) 47 | .andExpect(content().string(equalTo("[]"))); 48 | } 49 | 50 | private ResultActions getOrder() throws Exception { 51 | return get("/order"); 52 | } 53 | 54 | private void assertNoOrders() { 55 | assertOrderCountIs(0); 56 | } 57 | 58 | private void assertOrderCountIs(int count) { 59 | Assert.assertEquals(count, repository.getCount()); 60 | } 61 | 62 | @Test 63 | public void testGetAllOneOrderEnsureCorrectResponse() throws Exception { 64 | Order injectedOrder = injectOrder(); 65 | assertOrderCountIs(1); 66 | getOrder() 67 | .andExpect(status().isOk()) 68 | .andExpect(orderAtIndexIsCorrect(0, injectedOrder)); 69 | } 70 | 71 | @Test 72 | public void testGetAllOneOrderEnsureCorrectLinks() throws Exception { 73 | Order injectedOrder = injectOrder(); 74 | assertOrderCountIs(1); 75 | getOrder() 76 | .andExpect(status().isOk()) 77 | .andExpect(orderLinksAtIndexAreCorrect(0, injectedOrder, entityLinks)); 78 | } 79 | 80 | private Order injectOrder() { 81 | Order order = new Order(); 82 | order.setDescription("Test description"); 83 | 84 | return repository.create(order); 85 | } 86 | 87 | @Test 88 | public void testGetAllTwoOrderEnsureCorrectResponse() throws Exception { 89 | Order injectedOrder1 = injectOrder(); 90 | Order injectedOrder2 = injectOrder(); 91 | assertOrderCountIs(2); 92 | getOrder() 93 | .andExpect(status().isOk()) 94 | .andExpect(orderAtIndexIsCorrect(0, injectedOrder1)) 95 | .andExpect(orderAtIndexIsCorrect(1, injectedOrder2)); 96 | } 97 | 98 | @Test 99 | public void testGetAllTwoOrderEnsureCorrectLinks() throws Exception { 100 | Order injectedOrder1 = injectOrder(); 101 | Order injectedOrder2 = injectOrder(); 102 | assertOrderCountIs(2); 103 | getOrder() 104 | .andExpect(status().isOk()) 105 | .andExpect(orderLinksAtIndexAreCorrect(0, injectedOrder1, entityLinks)) 106 | .andExpect(orderLinksAtIndexAreCorrect(1, injectedOrder2, entityLinks)); 107 | } 108 | 109 | @Test 110 | public void testGetNonexistentOrderEnsureNotFoundResponse() throws Exception { 111 | assertNoOrders(); 112 | getOrder(1) 113 | .andExpect(status().isNotFound()); 114 | } 115 | 116 | private ResultActions getOrder(long id) throws Exception { 117 | return get("/order/{id}", id); 118 | } 119 | 120 | @Test 121 | public void testGetExistingOrderEnsureCorrectResponse() throws Exception { 122 | Order injectedOrder = injectOrder(); 123 | assertOrderCountIs(1); 124 | getOrder(injectedOrder.getId()) 125 | .andExpect(status().isOk()) 126 | .andExpect(orderIsCorrect(injectedOrder)); 127 | } 128 | 129 | @Test 130 | public void testGetExistingOrderEnsureCorrectLinks() throws Exception { 131 | Order injectedOrder = injectOrder(); 132 | assertOrderCountIs(1); 133 | getOrder(injectedOrder.getId()) 134 | .andExpect(status().isOk()) 135 | .andExpect(orderLinksAreCorrect(injectedOrder, entityLinks)); 136 | } 137 | 138 | @Test 139 | public void testCreateNewOrderEnsureOrderCreated() throws Exception { 140 | assertNoOrders(); 141 | Order desiredOrder = generateTestOrder(); 142 | createOrder(toJsonString(desiredOrder)); 143 | assertOrderCountIs(1); 144 | assertAllButIdsMatchBetweenOrders(desiredOrder, getCreatedOrder()); 145 | } 146 | 147 | private ResultActions createOrder(String payload) throws Exception { 148 | return post("/order", payload); 149 | } 150 | 151 | private Order getCreatedOrder() { 152 | List orders = repository.findAll(); 153 | return orders.get(orders.size() - 1); 154 | } 155 | 156 | @Test 157 | public void testCreateNewOrderEnsureCorrectResponse() throws Exception { 158 | assertNoOrders(); 159 | createOrder(TEST_ORDER) 160 | .andExpect(status().isCreated()) 161 | .andExpect(orderIsCorrect(getCreatedOrder())); 162 | } 163 | 164 | @Test 165 | public void testCreateNewOrderEnsureCorrectLinks() throws Exception { 166 | assertNoOrders(); 167 | createOrder(TEST_ORDER) 168 | .andExpect(status().isCreated()) 169 | .andExpect(orderLinksAreCorrect(getCreatedOrder(), entityLinks)); 170 | } 171 | 172 | @Test 173 | public void testCreateNewOrderMissingDataEnsureCorrectResponse() throws Exception { 174 | assertNoOrders(); 175 | createOrder(TEST_ORDER_MISSING_ORDER_DATA) 176 | .andExpect(status().isCreated()) 177 | .andExpect(orderIsCorrect(getCreatedOrder())); 178 | } 179 | 180 | @Test 181 | public void testCreateInvalidNewOrderEnsureCorrectResponse() throws Exception { 182 | assertNoOrders(); 183 | createOrder(INVALID_TEST_ORDER) 184 | .andExpect(status().isBadRequest()); 185 | } 186 | 187 | @Test 188 | public void testDeleteNonexistentOrderEnsureCorrectResponse() throws Exception { 189 | assertNoOrders(); 190 | deleteOrder(1) 191 | .andExpect(status().isNotFound()); 192 | } 193 | 194 | private ResultActions deleteOrder(long id) throws Exception { 195 | return delete("/order/{id}", id); 196 | } 197 | 198 | @Test 199 | public void testDeleteExistingOrderEnsureCorrectResponse() throws Exception { 200 | Order injectedOrder = injectOrder(); 201 | assertOrderCountIs(1); 202 | deleteOrder(injectedOrder.getId()) 203 | .andExpect(status().isNoContent()); 204 | } 205 | 206 | @Test 207 | public void testDeleteExistingOrderEnsureOrderDeleted() throws Exception { 208 | Order injectedOrder = injectOrder(); 209 | assertOrderCountIs(1); 210 | deleteOrder(injectedOrder.getId()); 211 | assertNoOrders(); 212 | } 213 | 214 | @Test 215 | public void testUpdateNonexistentOrderEnsureCorrectResponse() throws Exception { 216 | assertNoOrders(); 217 | updateOrder(1, new Order()) 218 | .andExpect(status().isNotFound()); 219 | } 220 | 221 | private ResultActions updateOrder(long id, Order updatedOrder) throws Exception { 222 | return put("/order/{id}", updatedOrder, String.valueOf(id)); 223 | } 224 | 225 | @Test 226 | public void testUpdateExistingOrderEnsureOrderUpdated() throws Exception { 227 | Order originalOrder = injectOrder(); 228 | assertOrderCountIs(1); 229 | Order updatedOrder = generateUpdatedOrder(originalOrder); 230 | updateOrder(originalOrder.getId(), updatedOrder); 231 | assertAllButIdsMatchBetweenOrders(updatedOrder, originalOrder); 232 | } 233 | 234 | @Test 235 | public void testUpdateExistingOrderEnsureCorrectResponse() throws Exception { 236 | Order originalOrder = injectOrder(); 237 | assertOrderCountIs(1); 238 | Order updatedOrder = generateUpdatedOrder(originalOrder); 239 | updateOrder(originalOrder.getId(), updatedOrder) 240 | .andExpect(status().isOk()) 241 | .andExpect(updatedOrderIsCorrect(originalOrder.getId(), updatedOrder)); 242 | } 243 | } 244 | --------------------------------------------------------------------------------