org.springsource.restbucks.payment.web.PaymentProcessIntegrationTest.java Source code

Java tutorial

Introduction

Here is the source code for org.springsource.restbucks.payment.web.PaymentProcessIntegrationTest.java

Source

/*
 * Copyright 2012-2015 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
 *
 *      http://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.springsource.restbucks.payment.web;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;

import java.nio.file.Files;

import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.ResultActions;
import org.springsource.restbucks.AbstractWebIntegrationTest;
import org.springsource.restbucks.Restbucks;
import org.springsource.restbucks.order.Order;

import com.jayway.jsonpath.JsonPath;

/**
 * Integration tests modeling the hypermedia-driven interaction flow against the server implementation. Uses the Spring
 * MVC integration test facilities introduced in 3.2. Implements the order process modeled in my presentation on
 * Hypermedia design with Spring.
 * 
 * @see http://bit.ly/UIcDvq
 * @author Oliver Gierke
 */
@Slf4j
public class PaymentProcessIntegrationTest extends AbstractWebIntegrationTest {

    private static final String ORDERS_REL = Restbucks.CURIE_NAMESPACE + ":orders";
    private static final String ORDER_REL = Restbucks.CURIE_NAMESPACE + ":order";
    private static final String RECEIPT_REL = Restbucks.CURIE_NAMESPACE + ":receipt";
    private static final String CANCEL_REL = Restbucks.CURIE_NAMESPACE + ":cancel";
    private static final String UPDATE_REL = Restbucks.CURIE_NAMESPACE + ":update";
    private static final String PAYMENT_REL = Restbucks.CURIE_NAMESPACE + ":payment";

    private static final String FIRST_ORDER_EXPRESSION = String.format("$._embedded.%s[0]", ORDERS_REL);

    /**
     * Processes the first existing {@link Order} found.
     */
    @Test
    public void processExistingOrder() throws Exception {

        MockHttpServletResponse response = accessRootResource();

        response = discoverOrdersResource(response);
        response = accessFirstOrder(response);
        response = triggerPayment(response);
        response = pollUntilOrderHasReceiptLink(response);
        response = takeReceipt(response);

        verifyOrderTaken(response);
    }

    /**
     * Creates a new {@link Order} and processes that one.
     */
    @Test
    public void processNewOrder() throws Exception {

        MockHttpServletResponse response = accessRootResource();

        response = createNewOrder(response);
        response = triggerPayment(response);
        response = pollUntilOrderHasReceiptLink(response);
        response = takeReceipt(response);

        verifyOrderTaken(response);
    }

    /**
     * Creates a new {@link Order} and cancels it right away.
     */
    @Test
    public void cancelOrderBeforePayment() throws Exception {

        MockHttpServletResponse response = accessRootResource();

        response = createNewOrder(response);
        cancelOrder(response);
    }

    /**
     * Access the root resource by referencing the well-known URI. Verifies the orders resource being present.
     * 
     * @return the response for the orders resource
     * @throws Exception
     */
    private MockHttpServletResponse accessRootResource() throws Exception {

        LOG.info("Accessing root resource");

        MockHttpServletResponse response = mvc.perform(get("/")). //
                andExpect(status().isOk()). //
                andExpect(linkWithRelIsPresent(ORDERS_REL)). //
                andReturn().getResponse();

        return response;
    }

    /**
     * Creates a new {@link Order} by looking up the orders link from the source and posting the content of
     * {@code orders.json} to it. Verifies we get receive a {@code 201 Created} and a {@code Location} header. Follows the
     * location header to retrieve the {@link Order} just created.
     * 
     * @param source
     * @return
     * @throws Exception
     */
    private MockHttpServletResponse createNewOrder(MockHttpServletResponse source) throws Exception {

        String content = source.getContentAsString();

        Link ordersLink = getDiscovererFor(source).findLinkWithRel(ORDERS_REL, content);

        ClassPathResource resource = new ClassPathResource("order.json");
        byte[] data = Files.readAllBytes(resource.getFile().toPath());

        MockHttpServletResponse result = mvc
                .perform(post(ordersLink.expand().getHref()).contentType(MediaType.APPLICATION_JSON).content(data)). //
                andExpect(status().isCreated()). //
                andExpect(header().string("Location", is(notNullValue()))). //
                andReturn().getResponse();

        return mvc.perform(get(result.getHeader("Location"))).andReturn().getResponse();
    }

    /**
     * Follows the {@code orders} link returns the {@link Order}s found.
     * 
     * @param source
     * @return
     * @throws Exception
     */
    private MockHttpServletResponse discoverOrdersResource(MockHttpServletResponse source) throws Exception {

        String content = source.getContentAsString();
        Link ordersLink = getDiscovererFor(source).findLinkWithRel(ORDERS_REL, content);

        LOG.info("Root resource returned: " + content);
        LOG.info(String.format("Found orders link pointing to %s Following", ordersLink));

        MockHttpServletResponse response = mvc.perform(get(ordersLink.expand().getHref())). //
                andExpect(status().isOk()). //
                andReturn().getResponse();

        LOG.info("Found orders: " + response.getContentAsString());
        return response;
    }

    /**
     * Looks up the first {@link Order} from the orders representation using a JSONPath expression of
     * {@value #FIRST_ORDER_EXPRESSION}. Looks up the {@value Link#REL_SELF} link from the nested object and follows it to
     * lookup the representation. Verifies the {@code self}, {@code cancel}, and {@code update} link to be present.
     * 
     * @param source
     * @return
     * @throws Exception
     */
    private MockHttpServletResponse accessFirstOrder(MockHttpServletResponse source) throws Exception {

        String content = source.getContentAsString();
        String order = JsonPath.parse(content).read(JsonPath.compile(FIRST_ORDER_EXPRESSION), JSONObject.class)
                .toString();
        Link orderLink = getDiscovererFor(source).findLinkWithRel("self", order).expand();

        LOG.info(String.format("Picking first order using JSONPath expression %s", FIRST_ORDER_EXPRESSION));
        LOG.info(String.format("Discovered self link pointing to %s Following", orderLink));

        return mvc.perform(get(orderLink.getHref())). //
                andExpect(linkWithRelIsPresent(Link.REL_SELF)). //
                andExpect(linkWithRelIsPresent(CANCEL_REL)). //
                andExpect(linkWithRelIsPresent(UPDATE_REL)). //
                andExpect(linkWithRelIsPresent(PAYMENT_REL)).//
                andReturn().getResponse();
    }

    /**
     * Triggers the payment of an {@link Order} by following the {@code payment} link and submitting a credit card number
     * to it. Verifies that we get a {@code 201 Created} and the response contains an {@code order} link. After the
     * payment has been triggered we fake a cancellation to make sure it is rejected with a {@code 404 Not found}.
     * 
     * @param response
     * @return
     * @throws Exception
     */
    private MockHttpServletResponse triggerPayment(MockHttpServletResponse response) throws Exception {

        String content = response.getContentAsString();
        LinkDiscoverer discoverer = getDiscovererFor(response);
        Link paymentLink = discoverer.findLinkWithRel(PAYMENT_REL, content);

        LOG.info(String.format("Discovered payment link pointing to %s", paymentLink));

        assertThat(paymentLink, not(nullValue()));

        LOG.info("Triggering payment");

        ResultActions action = mvc.perform(put(paymentLink.getHref()).//
                content("\"1234123412341234\"").//
                contentType(MediaType.APPLICATION_JSON).//
                accept(MediaTypes.HAL_JSON));

        MockHttpServletResponse result = action.andExpect(status().isCreated()). //
                andExpect(linkWithRelIsPresent(ORDER_REL)). //
                andReturn().getResponse();

        LOG.info("Payment triggered");

        // Make sure we cannot cheat and cancel the order after it has been payed
        LOG.info("Faking a cancel request to make sure it's forbidden");
        Link selfLink = discoverer.findLinkWithRel(Link.REL_SELF, content).expand();
        mvc.perform(delete(selfLink.getHref())).andExpect(status().isMethodNotAllowed());

        return result;
    }

    /**
     * Polls the order resource every 2 seconds and uses an {@code If-None-Match} header alongside the {@code ETag} of the
     * first response to avoid sending the representation over and over again.
     * 
     * @param response
     * @return
     * @throws Exception
     */
    private MockHttpServletResponse pollUntilOrderHasReceiptLink(MockHttpServletResponse response)
            throws Exception {

        // Grab
        String content = response.getContentAsString();
        LinkDiscoverer discoverer = getDiscovererFor(response);
        Link orderLink = discoverer.findLinkWithRel(ORDER_REL, content);

        // Poll order until receipt link is set
        Link receiptLink = null;
        String etag = null;
        MockHttpServletResponse pollResponse;

        do {

            HttpHeaders headers = new HttpHeaders();
            if (etag != null) {
                headers.setIfNoneMatch(etag);
            }

            LOG.info("Poll state of order until receipt is ready");

            ResultActions action = mvc.perform(get(orderLink.expand().getHref()).headers(headers));
            pollResponse = action.andReturn().getResponse();

            int status = pollResponse.getStatus();
            etag = pollResponse.getHeader("ETag");

            LOG.info(String.format("Received %s with ETag of %s", status, etag));

            if (status == HttpStatus.OK.value()) {

                action.andExpect(linkWithRelIsPresent(Link.REL_SELF)). //
                        andExpect(linkWithRelIsNotPresent(UPDATE_REL)). //
                        andExpect(linkWithRelIsNotPresent(CANCEL_REL));

                receiptLink = discoverer.findLinkWithRel(RECEIPT_REL, pollResponse.getContentAsString());

            } else if (status == HttpStatus.NO_CONTENT.value()) {
                action.andExpect(content().string(isEmptyOrNullString()));
            }

            if (receiptLink == null) {
                Thread.sleep(2000);
            }

        } while (receiptLink == null);

        return pollResponse;
    }

    /**
     * Concludes the {@link Order} by looking up the {@code receipt} link from the response and follows it. Triggers a
     * {@code DELETE} request susequently.
     * 
     * @param response
     * @return
     * @throws Exception
     */
    private MockHttpServletResponse takeReceipt(MockHttpServletResponse response) throws Exception {

        Link receiptLink = getDiscovererFor(response).findLinkWithRel(RECEIPT_REL, response.getContentAsString());

        MockHttpServletResponse receiptResponse = mvc.perform(get(receiptLink.getHref())). //
                andExpect(status().isOk()). //
                andReturn().getResponse();

        LOG.info("Accessing receipt, got:" + receiptResponse.getContentAsString());
        LOG.info("Taking receipt");

        return mvc.perform( //
                delete(receiptLink.getHref()).//
                        accept(MediaTypes.HAL_JSON))
                . //
                andExpect(status().isOk()). //
                andReturn().getResponse();
    }

    /**
     * Follows the {@code order} link and asserts only the self link being present so that no further navigation is
     * possible anymore.
     * 
     * @param response
     * @throws Exception
     */
    private void verifyOrderTaken(MockHttpServletResponse response) throws Exception {

        Link orderLink = getDiscovererFor(response).findLinkWithRel(ORDER_REL, response.getContentAsString());
        MockHttpServletResponse orderResponse = mvc.perform(get(orderLink.expand().getHref())). //
                andExpect(status().isOk()). // //
                andExpect(linkWithRelIsPresent(Link.REL_SELF)). //
                andExpect(linkWithRelIsNotPresent(UPDATE_REL)). //
                andExpect(linkWithRelIsNotPresent(CANCEL_REL)). //
                andExpect(linkWithRelIsNotPresent(PAYMENT_REL)). //
                andExpect(jsonPath("$.status", is("Delivered"))). //
                andReturn().getResponse();

        LOG.info("Final order state: " + orderResponse.getContentAsString());
    }

    /**
     * Cancels the order by issuing a delete request. Verifies the resource being inavailable after that.
     * 
     * @param response the response that retrieved an order resource
     * @throws Exception
     */
    private void cancelOrder(MockHttpServletResponse response) throws Exception {

        String content = response.getContentAsString();

        LinkDiscoverer discoverer = getDiscovererFor(response);
        Link selfLink = discoverer.findLinkWithRel(Link.REL_SELF, content).expand();
        Link cancellationLink = discoverer.findLinkWithRel(CANCEL_REL, content);

        mvc.perform(delete(cancellationLink.getHref())).andExpect(status().isNoContent());
        mvc.perform(get(selfLink.getHref())).andExpect(status().isNotFound());
    }
}