com.spotify.apollo.entity.EntityMiddlewareTest.java Source code

Java tutorial

Introduction

Here is the source code for com.spotify.apollo.entity.EntityMiddlewareTest.java

Source

/*
 * -\-\-
 * Spotify Apollo Entity Middleware
 * --
 * Copyright (C) 2013 - 2016 Spotify AB
 * --
 * 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 com.spotify.apollo.entity;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.spotify.apollo.Environment;
import com.spotify.apollo.Response;
import com.spotify.apollo.Status;
import com.spotify.apollo.route.AsyncHandler;
import com.spotify.apollo.route.Middleware;
import com.spotify.apollo.route.Route;
import com.spotify.apollo.test.ServiceHelper;

import org.hamcrest.Matcher;
import org.junit.ClassRule;
import org.junit.Test;

import java.util.List;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;

import io.norberg.automatter.AutoMatter;
import io.norberg.automatter.jackson.AutoMatterModule;
import okio.ByteString;

import static com.spotify.apollo.entity.JsonMatchers.asStr;
import static com.spotify.apollo.entity.JsonMatchers.hasJsonPath;
import static com.spotify.apollo.test.unit.ResponseMatchers.doesNotHaveHeader;
import static com.spotify.apollo.test.unit.ResponseMatchers.hasHeader;
import static com.spotify.apollo.test.unit.ResponseMatchers.hasNoPayload;
import static com.spotify.apollo.test.unit.ResponseMatchers.hasPayload;
import static com.spotify.apollo.test.unit.ResponseMatchers.hasStatus;
import static com.spotify.apollo.test.unit.StatusTypeMatchers.withCode;
import static com.spotify.apollo.test.unit.StatusTypeMatchers.withReasonPhrase;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;

public final class EntityMiddlewareTest {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new AutoMatterModule());

    private static final ByteString PERSON_JSON = ByteString.encodeUtf8("{\"name\":\"rouz\"}");

    private static final ByteString BAD_JSON = ByteString.encodeUtf8("I am not Jayson");

    private static final String DIRECT = "/direct";
    private static final String DIRECT_O = "/direct-o";
    private static final String RESPONSE = "/response";
    private static final String RESPONSE_O = "/response-o";
    private static final String RESPONSE_E = "/response-e";
    private static final String GET_DIRECT = "/person";
    private static final String GET_RESPONSE = "/person-resp";
    private static final String UNSERIALIZABLE = "/unserializable";

    @ClassRule
    public static ServiceHelper serviceHelper = ServiceHelper.create(EntityMiddlewareTest::init, "entity-test");

    @Test
    public void testDirectPerson() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", DIRECT, PERSON_JSON));

        assertThat(resp, hasStatus(withCode(Status.OK)));
        assertThat(resp, hasHeader("Content-Type", equalTo("application/json")));
        assertThat(resp, hasPayload(asStr(hasJsonPath("name", equalTo("hello rouz")))));
    }

    @Test
    public void testDirectOther() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", DIRECT_O, PERSON_JSON));

        assertThat(resp, hasStatus(withCode(Status.OK)));
        assertThat(resp, hasHeader("Content-Type", equalTo("application/json")));
        assertThat(resp, hasPayload(asStr(hasJsonPath("people[0].name", equalTo("rouz")))));
    }

    @Test
    public void testResponse() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", RESPONSE, PERSON_JSON));

        assertThat(resp, hasStatus(withCode(Status.ACCEPTED)));
        assertThat(resp, hasHeader("Content-Type", equalTo("application/json")));
        assertThat(resp, hasHeader("Name-Length", equalTo("4")));
        assertThat(resp, hasPayload(asStr(hasJsonPath("name", equalTo("rouz-resp")))));
    }

    @Test
    public void testResponseOther() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", RESPONSE_O, PERSON_JSON));

        assertThat(resp, hasStatus(withCode(Status.ACCEPTED)));
        assertThat(resp, hasStatus(withReasonPhrase(equalTo("was rouz"))));
        assertThat(resp, hasHeader("Content-Type", equalTo("application/json")));
        assertThat(resp, hasPayload(asStr(hasJsonPath("people[0].name", equalTo("rouz")))));
    }

    @Test
    public void testResponseEmpty() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", RESPONSE_E, PERSON_JSON));

        assertThat(resp, hasStatus(withCode(Status.ACCEPTED)));
        assertThat(resp, hasStatus(withReasonPhrase(equalTo("was rouz"))));
        assertThat(resp, doesNotHaveHeader("Content-Type"));
        assertThat(resp, hasNoPayload());
    }

    @Test
    public void testGetPerson() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("GET", GET_DIRECT));

        assertThat(resp, hasStatus(withCode(Status.OK)));
        assertThat(resp, hasHeader("Content-Type", equalTo("application/json")));
        assertThat(resp, hasPayload(asStr(hasJsonPath("name", equalTo("static")))));
    }

    @Test
    public void testGetPersonResponse() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("GET", GET_RESPONSE));

        assertThat(resp, hasStatus(withCode(Status.OK)));
        assertThat(resp, hasHeader("Content-Type", equalTo("application/json")));
        assertThat(resp, hasHeader("Reflection", equalTo("in-use")));
        assertThat(resp, hasPayload(asStr(hasJsonPath("name", equalTo("static-resp")))));
    }

    @Test
    public void testDirectAsync() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", DIRECT + "-async", PERSON_JSON));

        assertThat(resp, hasStatus(withCode(Status.OK)));
        assertThat(resp, hasHeader("Content-Type", equalTo("application/json")));
        assertThat(resp, hasPayload(asStr(hasJsonPath("name", equalTo("hello rouz")))));
    }

    @Test
    public void testDirectAsyncOther() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", DIRECT_O + "-async", PERSON_JSON));

        assertThat(resp, hasStatus(withCode(Status.OK)));
        assertThat(resp, hasHeader("Content-Type", equalTo("application/json")));
        assertThat(resp, hasPayload(asStr(hasJsonPath("people[0].name", equalTo("rouz")))));
    }

    @Test
    public void testResponseAsync() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", RESPONSE + "-async", PERSON_JSON));

        assertThat(resp, hasStatus(withCode(Status.ACCEPTED)));
        assertThat(resp, hasHeader("Content-Type", equalTo("application/json")));
        assertThat(resp, hasHeader("Async", equalTo("true")));
        assertThat(resp, hasPayload(asStr(hasJsonPath("name", equalTo("hello rouz")))));
    }

    @Test
    public void testResponseAsyncOther() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", RESPONSE_O + "-async", PERSON_JSON));

        assertThat(resp, hasStatus(withCode(Status.ACCEPTED)));
        assertThat(resp, hasHeader("Async", equalTo("true")));
        assertThat(resp, hasHeader("Content-Type", equalTo("application/json")));
        assertThat(resp, hasPayload(asStr(hasJsonPath("people[0].name", equalTo("rouz")))));
    }

    @Test
    public void testResponseAsyncEmpty() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", RESPONSE_E + "-async", PERSON_JSON));

        assertThat(resp, hasStatus(withCode(Status.ACCEPTED)));
        assertThat(resp, hasHeader("Async", equalTo("true")));
        assertThat(resp, doesNotHaveHeader("Content-Type"));
        assertThat(resp, hasNoPayload());
    }

    @Test
    public void testGetAsyncPerson() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("GET", GET_DIRECT + "-async"));

        assertThat(resp, hasStatus(withCode(Status.OK)));
        assertThat(resp, hasHeader("Content-Type", equalTo("application/json")));
        assertThat(resp, hasPayload(asStr(hasJsonPath("name", equalTo("static-async")))));
    }

    @Test
    public void testGetAsyncPersonResponse() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("GET", GET_RESPONSE + "-async"));

        assertThat(resp, hasStatus(withCode(Status.OK)));
        assertThat(resp, hasHeader("Content-Type", equalTo("application/json")));
        assertThat(resp, hasHeader("Async", equalTo("true")));
        assertThat(resp, hasHeader("Reflection", equalTo("in-use")));
        assertThat(resp, hasPayload(asStr(hasJsonPath("name", equalTo("static-resp-async")))));
    }

    @Test
    public void testMissingPayloadDirect() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", DIRECT));
        assertBadRequest(resp, equalTo("Missing payload"));
    }

    @Test
    public void testMissingPayloadDirectAsync() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", DIRECT + "-async"));
        assertBadRequest(resp, equalTo("Missing payload"));
    }

    @Test
    public void testMissingPayloadResponse() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", RESPONSE));
        assertBadRequest(resp, equalTo("Missing payload"));
    }

    @Test
    public void testMissingPayloadResponseAsync() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", RESPONSE + "-async"));
        assertBadRequest(resp, equalTo("Missing payload"));
    }

    @Test
    public void testBadPayloadDirect() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", DIRECT, BAD_JSON));
        assertBadRequest(resp, startsWith("Payload parsing failed"));
    }

    @Test
    public void testBadPayloadDirectAsync() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", DIRECT + "-async", BAD_JSON));
        assertBadRequest(resp, startsWith("Payload parsing failed"));
    }

    @Test
    public void testBadPayloadResponse() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", RESPONSE, BAD_JSON));
        assertBadRequest(resp, startsWith("Payload parsing failed"));
    }

    @Test
    public void testBadPayloadResponseAsync() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("POST", RESPONSE + "-async", BAD_JSON));
        assertBadRequest(resp, startsWith("Payload parsing failed"));
    }

    void assertBadRequest(Response<ByteString> resp, Matcher<String> reasonPhraseMatcher) {
        assertThat(resp, hasStatus(withCode(Status.BAD_REQUEST)));
        assertThat(resp, hasStatus(withReasonPhrase(reasonPhraseMatcher)));
        assertThat(resp, doesNotHaveHeader("Content-Type"));
        assertThat(resp, hasNoPayload());
    }

    @Test
    public void testBadResponse() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("GET", UNSERIALIZABLE));

        assertThat(resp, hasStatus(withCode(Status.INTERNAL_SERVER_ERROR)));
        assertThat(resp, hasStatus(withReasonPhrase(startsWith("Payload serialization failed"))));
        assertThat(resp, doesNotHaveHeader("Content-Type"));
        assertThat(resp, hasNoPayload());
    }

    @Test
    public void testBadResponseAsync() throws Exception {
        Response<ByteString> resp = await(serviceHelper.request("GET", UNSERIALIZABLE + "-async"));

        assertThat(resp, hasStatus(withCode(Status.INTERNAL_SERVER_ERROR)));
        assertThat(resp, hasStatus(withReasonPhrase(startsWith("Payload serialization failed"))));
        assertThat(resp, doesNotHaveHeader("Content-Type"));
        assertThat(resp, hasNoPayload());
    }

    /**
     * Entity based API used in tests
     */
    static void init(Environment environment) {
        EntityMiddlewareTest app = new EntityMiddlewareTest();
        EntityMiddleware e = EntityMiddleware.forCodec(JacksonEntityCodec.forMapper(OBJECT_MAPPER));

        Stream<Route<AsyncHandler<Response<ByteString>>>> syncRoutes = Stream
                .of(Route.with(e.direct(Person.class), "POST", DIRECT, rc -> app::personDirect),
                        Route.with(e.direct(Person.class, Group.class), "POST", DIRECT_O, rc -> app::personOther),
                        Route.with(e.response(Person.class), "POST", RESPONSE, rc -> app::personResponse),
                        Route.with(e.response(Person.class, Group.class), "POST", RESPONSE_O,
                                rc -> app::personOtherResponse),
                        Route.with(e.response(Person.class, Void.class), "POST", RESPONSE_E,
                                rc -> app::personEmptyResponse),
                        Route.with(e.serializerDirect(Person.class), "GET", GET_DIRECT, rc -> app.getPerson()),
                        Route.with(e.serializerResponse(Person.class), "GET", GET_RESPONSE,
                                rc -> app.getPersonResponse()),
                        Route.with(e.serializerDirect(Unserializable.class), "GET", UNSERIALIZABLE,
                                rc -> app.getUnserializable()))
                .map(r -> r.withMiddleware(Middleware::syncToAsync));

        Stream<Route<AsyncHandler<Response<ByteString>>>> asyncRoutes = Stream.of(
                Route.with(e.asyncDirect(Person.class), "POST", DIRECT + "-async", rc -> app::personAsync),
                Route.with(e.asyncDirect(Person.class, Group.class), "POST", DIRECT_O + "-async",
                        rc -> app::personAsyncOther),
                Route.with(e.asyncResponse(Person.class), "POST", RESPONSE + "-async",
                        rc -> app::personAsyncResponse),
                Route.with(e.asyncResponse(Person.class, Group.class), "POST", RESPONSE_O + "-async",
                        rc -> app::personAsyncOtherResponse),
                Route.with(e.asyncResponse(Person.class, Void.class), "POST", RESPONSE_E + "-async",
                        rc -> app::personAsyncEmptyResponse),
                Route.with(e.asyncSerializerDirect(Person.class), "GET", GET_DIRECT + "-async",
                        rc -> app.getPersonAsync()),
                Route.with(e.asyncSerializerResponse(Person.class), "GET", GET_RESPONSE + "-async",
                        rc -> app.getPersonResponseAsync()),
                Route.with(e.asyncSerializerDirect(Unserializable.class), "GET", UNSERIALIZABLE + "-async",
                        rc -> app.getUnserializableAsync()));

        environment.routingEngine().registerRoutes(syncRoutes).registerRoutes(asyncRoutes);
    }

    @AutoMatter
    public interface Person {
        String name();
    }

    @AutoMatter
    public interface Group {
        List<Person> people();
    }

    /**
     * Handler returning the entity type directly
     */
    Person personDirect(Person p) {
        return new PersonBuilder().name("hello " + p.name()).build();
    }

    /**
     * Handler returning a some other entity directly
     */
    Group personOther(Person p) {
        return new GroupBuilder().people(p).build();
    }

    /**
     * Handler returning a response containing the entity type
     */
    Response<Person> personResponse(Person p) {
        Person p2 = PersonBuilder.from(p).name(p.name() + "-resp").build();

        return Response.of(Status.ACCEPTED, p2).withHeader("Name-Length", Integer.toString(p.name().length()));
    }

    /**
     * Handler returning a response containing some other entity
     */
    Response<Group> personOtherResponse(Person p) {
        return Response.forStatus(Status.ACCEPTED.withReasonPhrase("was " + p.name()))
                .withPayload(new GroupBuilder().people(p).build());
    }

    /**
     * Handler returning a response with no entity
     */
    Response<Void> personEmptyResponse(Person p) {
        return Response.forStatus(Status.ACCEPTED.withReasonPhrase("was " + p.name()));
    }

    /**
     * Handler that only returns an entity
     */
    Person getPerson() {
        return new PersonBuilder().name("static").build();
    }

    /**
     * Handler that returns an entity in a response
     */
    Response<Person> getPersonResponse() {
        return Response.forPayload(new PersonBuilder().name("static-resp").build()).withHeader("Reflection",
                "in-use");
    }

    /**
     * Async handler returning an entity in a response
     */
    CompletionStage<Person> personAsync(Person p) {
        return completedFuture(personDirect(p));
    }

    /**
     * Async handler returning another entity in the response
     */
    CompletionStage<Group> personAsyncOther(Person p) {
        return completedFuture(personOther(p));
    }

    /**
     * Async handler returning an entity in a response
     */
    CompletionStage<Response<Person>> personAsyncResponse(Person p) {
        return completedFuture(Response.of(Status.ACCEPTED, personDirect(p)).withHeader("Async", "true"));
    }

    /**
     * Async handler returning a response with other entity
     */
    CompletionStage<Response<Group>> personAsyncOtherResponse(Person p) {
        return completedFuture(Response.of(Status.ACCEPTED, personOther(p)).withHeader("Async", "true"));
    }

    /**
     * Async handler returning a response with no entity
     */
    CompletionStage<Response<Void>> personAsyncEmptyResponse(Person p) {
        return completedFuture(Response.<Void>forStatus(Status.ACCEPTED).withHeader("Async", "true"));
    }

    /**
     * Async handler that only returns an entity
     */
    CompletionStage<Person> getPersonAsync() {
        return completedFuture(new PersonBuilder().name("static-async").build());
    }

    /**
     * Async handler that returns an entity in a response
     */
    CompletionStage<Response<Person>> getPersonResponseAsync() {
        return completedFuture(Response.forPayload(new PersonBuilder().name("static-resp-async").build())
                .withHeader("Async", "true").withHeader("Reflection", "in-use"));
    }

    /**
     * Handler returning an unserializable entity
     */
    Unserializable getUnserializable() {
        return new Unserializable() {
        };
    }

    /**
     * Async handler returning an unserializable entity
     */
    CompletionStage<Unserializable> getUnserializableAsync() {
        return completedFuture(new Unserializable() {
        });
    }

    static Response<ByteString> await(CompletionStage<Response<ByteString>> resp)
            throws InterruptedException, ExecutionException, TimeoutException {
        return resp.toCompletableFuture().get(5, TimeUnit.SECONDS);
    }

    interface Unserializable {
    }
}