com.example.notes.ApiDocumentation.java Source code

Java tutorial

Introduction

Here is the source code for com.example.notes.ApiDocumentation.java

Source

/*
 * Copyright 2014-2016 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 com.example.notes;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.RequestDispatcher;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.hateoas.MediaTypes;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.constraints.ConstraintDescriptions;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;

import com.fasterxml.jackson.databind.ObjectMapper;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = RestNotesSpringHateoas.class)
@WebAppConfiguration
public class ApiDocumentation {

    @Rule
    public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("build/generated-snippets");

    private RestDocumentationResultHandler documentationHandler;

    @Autowired
    private NoteRepository noteRepository;

    @Autowired
    private TagRepository tagRepository;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        this.documentationHandler = document("{method-name}", preprocessRequest(prettyPrint()),
                preprocessResponse(prettyPrint()));

        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
                .apply(documentationConfiguration(this.restDocumentation)).alwaysDo(this.documentationHandler)
                .build();
    }

    @Test
    public void headersExample() throws Exception {
        this.mockMvc.perform(get("/")).andExpect(status().isOk())
                .andDo(this.documentationHandler.document(responseHeaders(headerWithName("Content-Type")
                        .description("The Content-Type of the payload, e.g. `application/hal+json`"))));
    }

    @Test
    public void errorExample() throws Exception {
        this.mockMvc
                .perform(get("/error").requestAttr(RequestDispatcher.ERROR_STATUS_CODE, 400)
                        .requestAttr(RequestDispatcher.ERROR_REQUEST_URI, "/notes")
                        .requestAttr(RequestDispatcher.ERROR_MESSAGE,
                                "The tag 'http://localhost:8080/tags/123' does not exist"))
                .andExpect(status().isBadRequest()).andExpect(jsonPath("error", is("Bad Request")))
                .andExpect(jsonPath("timestamp", is(notNullValue()))).andExpect(jsonPath("status", is(400)))
                .andExpect(jsonPath("path", is(notNullValue())))
                .andDo(this.documentationHandler.document(responseFields(
                        fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`"),
                        fieldWithPath("message").description("A description of the cause of the error"),
                        fieldWithPath("path").description("The path to which the request was made"),
                        fieldWithPath("status").description("The HTTP status code, e.g. `400`"),
                        fieldWithPath("timestamp")
                                .description("The time, in milliseconds, at which the error occurred"))));
    }

    @Test
    public void indexExample() throws Exception {
        this.mockMvc.perform(get("/")).andExpect(status().isOk())
                .andDo(this.documentationHandler.document(
                        links(linkWithRel("notes").description("The <<resources-notes,Notes resource>>"),
                                linkWithRel("tags").description("The <<resources-tags,Tags resource>>")),
                        responseFields(fieldWithPath("_links")
                                .description("<<resources-index-links,Links>> to other resources"))));
    }

    @Test
    public void notesListExample() throws Exception {
        this.noteRepository.deleteAll();

        createNote("REST maturity model", "http://martinfowler.com/articles/richardsonMaturityModel.html");
        createNote("Hypertext Application Language (HAL)", "http://stateless.co/hal_specification.html");
        createNote("Application-Level Profile Semantics (ALPS)", "http://alps.io/spec/");

        this.mockMvc.perform(get("/notes")).andExpect(status().isOk())
                .andDo(this.documentationHandler.document(responseFields(fieldWithPath("_embedded.notes")
                        .description("An array of <<resources-note, Note resources>>"))));
    }

    @Test
    public void notesCreateExample() throws Exception {
        Map<String, String> tag = new HashMap<String, String>();
        tag.put("name", "REST");

        String tagLocation = this.mockMvc
                .perform(post("/tags").contentType(MediaTypes.HAL_JSON)
                        .content(this.objectMapper.writeValueAsString(tag)))
                .andExpect(status().isCreated()).andReturn().getResponse().getHeader("Location");

        Map<String, Object> note = new HashMap<String, Object>();
        note.put("title", "REST maturity model");
        note.put("body", "http://martinfowler.com/articles/richardsonMaturityModel.html");
        note.put("tags", Arrays.asList(tagLocation));

        ConstrainedFields fields = new ConstrainedFields(NoteInput.class);

        this.mockMvc
                .perform(post("/notes")
                        .contentType(MediaTypes.HAL_JSON).content(this.objectMapper.writeValueAsString(note)))
                .andExpect(status().isCreated())
                .andDo(this.documentationHandler
                        .document(requestFields(fields.withPath("title").description("The title of the note"),
                                fields.withPath("body").description("The body of the note"),
                                fields.withPath("tags").description("An array of tag resource URIs"))));
    }

    @Test
    public void noteGetExample() throws Exception {
        Map<String, String> tag = new HashMap<String, String>();
        tag.put("name", "REST");

        String tagLocation = this.mockMvc
                .perform(post("/tags").contentType(MediaTypes.HAL_JSON)
                        .content(this.objectMapper.writeValueAsString(tag)))
                .andExpect(status().isCreated()).andReturn().getResponse().getHeader("Location");

        Map<String, Object> note = new HashMap<String, Object>();
        note.put("title", "REST maturity model");
        note.put("body", "http://martinfowler.com/articles/richardsonMaturityModel.html");
        note.put("tags", Arrays.asList(tagLocation));

        String noteLocation = this.mockMvc
                .perform(post("/notes").contentType(MediaTypes.HAL_JSON)
                        .content(this.objectMapper.writeValueAsString(note)))
                .andExpect(status().isCreated()).andReturn().getResponse().getHeader("Location");

        this.mockMvc.perform(get(noteLocation)).andExpect(status().isOk())
                .andExpect(jsonPath("title", is(note.get("title"))))
                .andExpect(jsonPath("body", is(note.get("body"))))
                .andExpect(jsonPath("_links.self.href", is(noteLocation)))
                .andExpect(jsonPath("_links.note-tags", is(notNullValue())))
                .andDo(this.documentationHandler.document(
                        links(linkWithRel("self").description("This <<resources-note,note>>"),
                                linkWithRel("note-tags").description("This note's <<resources-note-tags,tags>>")),
                        responseFields(fieldWithPath("title").description("The title of the note"),
                                fieldWithPath("body").description("The body of the note"), fieldWithPath("_links")
                                        .description("<<resources-note-links,Links>> to other resources"))));

    }

    @Test
    public void tagsListExample() throws Exception {
        this.noteRepository.deleteAll();
        this.tagRepository.deleteAll();

        createTag("REST");
        createTag("Hypermedia");
        createTag("HTTP");

        this.mockMvc.perform(get("/tags")).andExpect(status().isOk())
                .andDo(this.documentationHandler.document(responseFields(fieldWithPath("_embedded.tags")
                        .description("An array of <<resources-tag,Tag resources>>"))));
    }

    @Test
    public void tagsCreateExample() throws Exception {
        Map<String, String> tag = new HashMap<String, String>();
        tag.put("name", "REST");

        ConstrainedFields fields = new ConstrainedFields(TagInput.class);

        this.mockMvc
                .perform(post("/tags").contentType(MediaTypes.HAL_JSON)
                        .content(this.objectMapper.writeValueAsString(tag)))
                .andExpect(status().isCreated()).andDo(this.documentationHandler
                        .document(requestFields(fields.withPath("name").description("The name of the tag"))));
    }

    @Test
    public void noteUpdateExample() throws Exception {
        Map<String, Object> note = new HashMap<String, Object>();
        note.put("title", "REST maturity model");
        note.put("body", "http://martinfowler.com/articles/richardsonMaturityModel.html");

        String noteLocation = this.mockMvc
                .perform(post("/notes").contentType(MediaTypes.HAL_JSON)
                        .content(this.objectMapper.writeValueAsString(note)))
                .andExpect(status().isCreated()).andReturn().getResponse().getHeader("Location");

        this.mockMvc.perform(get(noteLocation)).andExpect(status().isOk())
                .andExpect(jsonPath("title", is(note.get("title"))))
                .andExpect(jsonPath("body", is(note.get("body"))))
                .andExpect(jsonPath("_links.self.href", is(noteLocation)))
                .andExpect(jsonPath("_links.note-tags", is(notNullValue())));

        Map<String, String> tag = new HashMap<String, String>();
        tag.put("name", "REST");

        String tagLocation = this.mockMvc
                .perform(post("/tags").contentType(MediaTypes.HAL_JSON)
                        .content(this.objectMapper.writeValueAsString(tag)))
                .andExpect(status().isCreated()).andReturn().getResponse().getHeader("Location");

        Map<String, Object> noteUpdate = new HashMap<String, Object>();
        noteUpdate.put("tags", Arrays.asList(tagLocation));

        ConstrainedFields fields = new ConstrainedFields(NotePatchInput.class);

        this.mockMvc
                .perform(patch(noteLocation).contentType(MediaTypes.HAL_JSON)
                        .content(this.objectMapper.writeValueAsString(noteUpdate)))
                .andExpect(status().isNoContent())
                .andDo(this.documentationHandler.document(requestFields(
                        fields.withPath("title").description("The title of the note").type(JsonFieldType.STRING)
                                .optional(),
                        fields.withPath("body").description("The body of the note").type(JsonFieldType.STRING)
                                .optional(),
                        fields.withPath("tags").description("An array of tag resource URIs"))));
    }

    @Test
    public void tagGetExample() throws Exception {
        Map<String, String> tag = new HashMap<String, String>();
        tag.put("name", "REST");

        String tagLocation = this.mockMvc
                .perform(post("/tags").contentType(MediaTypes.HAL_JSON)
                        .content(this.objectMapper.writeValueAsString(tag)))
                .andExpect(status().isCreated()).andReturn().getResponse().getHeader("Location");

        this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk())
                .andExpect(jsonPath("name", is(tag.get("name"))))
                .andDo(this.documentationHandler.document(
                        links(linkWithRel("self").description("This <<resources-tag,tag>>"),
                                linkWithRel("tagged-notes")
                                        .description("The <<resources-tagged-notes,notes>> that have this tag")),
                        responseFields(fieldWithPath("name").description("The name of the tag"),
                                fieldWithPath("_links")
                                        .description("<<resources-tag-links,Links>> to other resources"))));
    }

    @Test
    public void tagUpdateExample() throws Exception {
        Map<String, String> tag = new HashMap<String, String>();
        tag.put("name", "REST");

        String tagLocation = this.mockMvc
                .perform(post("/tags").contentType(MediaTypes.HAL_JSON)
                        .content(this.objectMapper.writeValueAsString(tag)))
                .andExpect(status().isCreated()).andReturn().getResponse().getHeader("Location");

        Map<String, Object> tagUpdate = new HashMap<String, Object>();
        tagUpdate.put("name", "RESTful");

        ConstrainedFields fields = new ConstrainedFields(TagPatchInput.class);

        this.mockMvc
                .perform(patch(tagLocation).contentType(MediaTypes.HAL_JSON)
                        .content(this.objectMapper.writeValueAsString(tagUpdate)))
                .andExpect(status().isNoContent()).andDo(this.documentationHandler
                        .document(requestFields(fields.withPath("name").description("The name of the tag"))));
    }

    private void createNote(String title, String body) {
        Note note = new Note();
        note.setTitle(title);
        note.setBody(body);

        this.noteRepository.save(note);
    }

    private void createTag(String name) {
        Tag tag = new Tag();
        tag.setName(name);
        this.tagRepository.save(tag);
    }

    private static class ConstrainedFields {

        private final ConstraintDescriptions constraintDescriptions;

        ConstrainedFields(Class<?> input) {
            this.constraintDescriptions = new ConstraintDescriptions(input);
        }

        private FieldDescriptor withPath(String path) {
            return fieldWithPath(path).attributes(key("constraints").value(StringUtils
                    .collectionToDelimitedString(this.constraintDescriptions.descriptionsForProperty(path), ". ")));
        }
    }

}