com.netflix.genie.web.controllers.CommandRestControllerIntegrationTests.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.genie.web.controllers.CommandRestControllerIntegrationTests.java

Source

/*
 *
 *  Copyright 2015 Netflix, Inc.
 *
 *     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.netflix.genie.web.controllers;

import com.github.fge.jsonpatch.JsonPatch;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.netflix.genie.common.dto.Application;
import com.netflix.genie.common.dto.ApplicationStatus;
import com.netflix.genie.common.dto.Cluster;
import com.netflix.genie.common.dto.ClusterStatus;
import com.netflix.genie.common.dto.Command;
import com.netflix.genie.common.dto.CommandStatus;
import com.netflix.genie.core.jpa.repositories.JpaApplicationRepository;
import com.netflix.genie.core.jpa.repositories.JpaClusterRepository;
import com.netflix.genie.core.jpa.repositories.JpaCommandRepository;
import com.netflix.genie.web.hateoas.resources.ClusterResource;
import com.netflix.genie.web.hateoas.resources.CommandResource;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.restdocs.operation.preprocess.Preprocessors;
import org.springframework.restdocs.payload.PayloadDocumentation;
import org.springframework.restdocs.request.RequestDocumentation;
import org.springframework.restdocs.snippet.Attributes;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.UUID;

/**
 * Integration tests for the Commands REST API.
 *
 * @author tgianos
 * @since 3.0.0
 */
//TODO: Add tests for error conditions
public class CommandRestControllerIntegrationTests extends RestControllerIntegrationTestsBase {

    private static final String ID = UUID.randomUUID().toString();
    private static final String NAME = "hive";
    private static final String USER = "genie";
    private static final String VERSION = "1.0.0";
    private static final String EXECUTABLE = "/apps/hive/bin/hive";
    private static final long CHECK_DELAY = 10000L;
    private static final String DESCRIPTION = "Hive command v" + VERSION;
    private static final int MEMORY = 1024;
    private static final String CONFIG_1 = "s3:///path/to/config-foo";
    private static final String CONFIG_2 = "s3:///path/to/config-bar";
    private static final Set<String> CONFIGS = Sets.newHashSet(CONFIG_1, CONFIG_2);
    private static final String DEP_1 = "/path/to/file/foo";
    private static final String DEP_2 = "/path/to/file/bar";
    private static final Set<String> DEPENDENCIES = Sets.newHashSet(DEP_1, DEP_2);
    private static final String TAG_1 = "tag:foo";
    private static final String TAG_2 = "tag:bar";
    private static final Set<String> TAGS = Sets.newHashSet(TAG_1, TAG_2);

    private static final String EXECUTABLE_PATH = "$.executable";
    private static final String CHECK_DELAY_PATH = "$.checkDelay";
    private static final String MEMORY_PATH = "$.memory";
    private static final String COMMANDS_LIST_PATH = EMBEDDED_PATH + ".commandList";
    private static final String COMMAND_APPS_LINK_PATH = "$._links.applications.href";
    private static final String COMMANDS_APPS_LINK_PATH = "$.._links.applications.href";
    private static final String COMMAND_CLUSTERS_LINK_PATH = "$._links.clusters.href";
    private static final String COMMANDS_CLUSTERS_LINK_PATH = "$.._links.clusters.href";
    private static final String COMMANDS_ID_LIST_PATH = EMBEDDED_PATH + ".commandList..id";

    @Autowired
    private JpaApplicationRepository jpaApplicationRepository;

    @Autowired
    private JpaClusterRepository jpaClusterRepository;

    @Autowired
    private JpaCommandRepository jpaCommandRepository;

    /**
     * Cleanup after tests.
     */
    @After
    public void cleanup() {
        this.jpaClusterRepository.deleteAll();
        this.jpaCommandRepository.deleteAll();
        this.jpaApplicationRepository.deleteAll();
    }

    /**
     * Test creating a command without an ID.
     *
     * @throws Exception on configuration issue
     */
    @Test
    public void canCreateCommandWithoutId() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));

        final RestDocumentationResultHandler creationResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.CONTENT_TYPE_HEADER, // Request headers
                Snippets.getCommandRequestPayload(), // Request fields
                Snippets.LOCATION_HEADER // Response headers
        );

        final String id = this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY)
                        .withDescription(DESCRIPTION).withMemory(MEMORY).withConfigs(CONFIGS)
                        .withDependencies(DEPENDENCIES).withTags(TAGS).build(),
                creationResultHandler);

        final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // path parameters
                Snippets.HAL_CONTENT_TYPE_HEADER, // response headers
                Snippets.getCommandResponsePayload(), // response payload
                Snippets.COMMAND_LINKS // response links
        );

        this.mvc.perform(RestDocumentationRequestBuilders.get(COMMANDS_API + "/{id}", id))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath(ID_PATH, Matchers.is(id)))
                .andExpect(MockMvcResultMatchers.jsonPath(CREATED_PATH, Matchers.notNullValue()))
                .andExpect(MockMvcResultMatchers.jsonPath(UPDATED_PATH, Matchers.notNullValue()))
                .andExpect(MockMvcResultMatchers.jsonPath(NAME_PATH, Matchers.is(NAME)))
                .andExpect(MockMvcResultMatchers.jsonPath(USER_PATH, Matchers.is(USER)))
                .andExpect(MockMvcResultMatchers.jsonPath(VERSION_PATH, Matchers.is(VERSION)))
                .andExpect(
                        MockMvcResultMatchers.jsonPath(STATUS_PATH, Matchers.is(CommandStatus.ACTIVE.toString())))
                .andExpect(MockMvcResultMatchers.jsonPath(EXECUTABLE_PATH, Matchers.is(EXECUTABLE)))
                .andExpect(MockMvcResultMatchers.jsonPath(CHECK_DELAY_PATH, Matchers.is((int) CHECK_DELAY)))
                .andExpect(MockMvcResultMatchers.jsonPath(DESCRIPTION_PATH, Matchers.is(DESCRIPTION)))
                .andExpect(MockMvcResultMatchers.jsonPath(MEMORY_PATH, Matchers.is(MEMORY)))
                .andExpect(MockMvcResultMatchers.jsonPath(CONFIGS_PATH, Matchers.hasSize(2)))
                .andExpect(MockMvcResultMatchers.jsonPath(CONFIGS_PATH, Matchers.hasItems(CONFIG_1, CONFIG_2)))
                .andExpect(MockMvcResultMatchers.jsonPath(DEPENDENCIES_PATH, Matchers.hasSize(2)))
                .andExpect(MockMvcResultMatchers.jsonPath(DEPENDENCIES_PATH, Matchers.hasItems(DEP_1, DEP_2)))
                .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasSize(4)))
                .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItem("genie.id:" + id)))
                .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItem("genie.name:" + NAME)))
                .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItems(TAG_1, TAG_2)))
                .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH + ".*", Matchers.hasSize(3)))
                .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey(SELF_LINK_KEY)))
                .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey(CLUSTERS_LINK_KEY)))
                .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey(APPLICATIONS_LINK_KEY)))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMAND_CLUSTERS_LINK_PATH,
                        EntityLinkMatcher.matchUri(COMMANDS_API, CLUSTERS_LINK_KEY,
                                CLUSTERS_OPTIONAL_HAL_LINK_PARAMETERS, id)))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMAND_APPS_LINK_PATH,
                        EntityLinkMatcher.matchUri(COMMANDS_API, APPLICATIONS_LINK_KEY, null, id)))
                .andDo(getResultHandler);

        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(1L));
    }

    /**
     * Test creating a Command with an ID.
     *
     * @throws Exception When issue in creation
     */
    @Test
    public void canCreateCommandWithId() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .withDescription(DESCRIPTION).withMemory(MEMORY).withConfigs(CONFIGS)
                        .withDependencies(DEPENDENCIES).withTags(TAGS).build(),
                null);

        this.mvc.perform(MockMvcRequestBuilders.get(COMMANDS_API + "/" + ID))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath(ID_PATH, Matchers.is(ID)))
                .andExpect(MockMvcResultMatchers.jsonPath(CREATED_PATH, Matchers.notNullValue()))
                .andExpect(MockMvcResultMatchers.jsonPath(UPDATED_PATH, Matchers.notNullValue()))
                .andExpect(MockMvcResultMatchers.jsonPath(NAME_PATH, Matchers.is(NAME)))
                .andExpect(MockMvcResultMatchers.jsonPath(USER_PATH, Matchers.is(USER)))
                .andExpect(MockMvcResultMatchers.jsonPath(VERSION_PATH, Matchers.is(VERSION)))
                .andExpect(
                        MockMvcResultMatchers.jsonPath(STATUS_PATH, Matchers.is(CommandStatus.ACTIVE.toString())))
                .andExpect(MockMvcResultMatchers.jsonPath(EXECUTABLE_PATH, Matchers.is(EXECUTABLE)))
                .andExpect(MockMvcResultMatchers.jsonPath(CHECK_DELAY_PATH, Matchers.is((int) CHECK_DELAY)))
                .andExpect(MockMvcResultMatchers.jsonPath(DESCRIPTION_PATH, Matchers.is(DESCRIPTION)))
                .andExpect(MockMvcResultMatchers.jsonPath(MEMORY_PATH, Matchers.is(MEMORY)))
                .andExpect(MockMvcResultMatchers.jsonPath(CONFIGS_PATH, Matchers.hasSize(2)))
                .andExpect(MockMvcResultMatchers.jsonPath(CONFIGS_PATH, Matchers.hasItems(CONFIG_1, CONFIG_2)))
                .andExpect(MockMvcResultMatchers.jsonPath(DEPENDENCIES_PATH, Matchers.hasSize(2)))
                .andExpect(MockMvcResultMatchers.jsonPath(DEPENDENCIES_PATH, Matchers.hasItems(DEP_1, DEP_2)))
                .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasSize(4)))
                .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItem("genie.id:" + ID)))
                .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItem("genie.name:" + NAME)))
                .andExpect(MockMvcResultMatchers.jsonPath(TAGS_PATH, Matchers.hasItems(TAG_1, TAG_2)))
                .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH + ".*", Matchers.hasSize(3)))
                .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey(SELF_LINK_KEY)))
                .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey(CLUSTERS_LINK_KEY)))
                .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey(APPLICATIONS_LINK_KEY)))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMAND_CLUSTERS_LINK_PATH,
                        EntityLinkMatcher.matchUri(COMMANDS_API, CLUSTERS_LINK_KEY,
                                CLUSTERS_OPTIONAL_HAL_LINK_PARAMETERS, ID)))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMAND_APPS_LINK_PATH,
                        EntityLinkMatcher.matchUri(COMMANDS_API, APPLICATIONS_LINK_KEY, null, ID)));

        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(1L));
    }

    /**
     * Test to make sure the post API can handle bad input.
     *
     * @throws Exception on issue
     */
    @Test
    public void canHandleBadInputToCreateCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        final Command cluster = new Command.Builder(null, null, null, null, null, -1L).build();
        this.mvc.perform(MockMvcRequestBuilders.post(COMMANDS_API).contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsBytes(cluster)))
                .andExpect(MockMvcResultMatchers.status().isPreconditionFailed());
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
    }

    /**
     * Test to make sure that you can search for commands by various parameters.
     *
     * @throws Exception on configuration error
     */
    @Test
    public void canFindCommands() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        final String id1 = UUID.randomUUID().toString();
        final String id2 = UUID.randomUUID().toString();
        final String id3 = UUID.randomUUID().toString();
        final String name1 = UUID.randomUUID().toString();
        final String name2 = UUID.randomUUID().toString();
        final String name3 = UUID.randomUUID().toString();
        final String user1 = UUID.randomUUID().toString();
        final String user2 = UUID.randomUUID().toString();
        final String user3 = UUID.randomUUID().toString();
        final String version1 = UUID.randomUUID().toString();
        final String version2 = UUID.randomUUID().toString();
        final String version3 = UUID.randomUUID().toString();
        final String executable1 = UUID.randomUUID().toString();
        final String executable2 = UUID.randomUUID().toString();
        final String executable3 = UUID.randomUUID().toString();

        this.createConfigResource(
                new Command.Builder(name1, user1, version1, CommandStatus.ACTIVE, executable1, CHECK_DELAY)
                        .withId(id1).build(),
                null);
        Thread.sleep(1000);
        this.createConfigResource(
                new Command.Builder(name2, user2, version2, CommandStatus.DEPRECATED, executable2, CHECK_DELAY)
                        .withId(id2).build(),
                null);
        Thread.sleep(1000);
        this.createConfigResource(
                new Command.Builder(name3, user3, version3, CommandStatus.INACTIVE, executable3, CHECK_DELAY)
                        .withId(id3).build(),
                null);

        final RestDocumentationResultHandler findResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()),
                Snippets.COMMAND_SEARCH_QUERY_PARAMETERS, // Request query parameters
                Snippets.HAL_CONTENT_TYPE_HEADER, // Response headers
                Snippets.COMMAND_SEARCH_RESULT_FIELDS, // Result fields
                Snippets.SEARCH_LINKS // HAL Links
        );

        // Test finding all commands
        this.mvc.perform(MockMvcRequestBuilders.get(COMMANDS_API)).andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_LIST_PATH, Matchers.hasSize(3)))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_ID_LIST_PATH,
                        Matchers.containsInAnyOrder(id1, id2, id3)))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_APPS_LINK_PATH,
                        EntitiesLinksMatcher.matchUrisAnyOrder(COMMANDS_API, APPLICATIONS_LINK_KEY, null,
                                Lists.newArrayList(id1, id2, id3))))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_CLUSTERS_LINK_PATH,
                        EntitiesLinksMatcher.matchUrisAnyOrder(COMMANDS_API, CLUSTERS_LINK_KEY,
                                CLUSTERS_OPTIONAL_HAL_LINK_PARAMETERS, Lists.newArrayList(id1, id2, id3))))
                .andDo(findResultHandler);

        // Try to limit the number of results
        this.mvc.perform(MockMvcRequestBuilders.get(COMMANDS_API).param("size", "2"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_LIST_PATH, Matchers.hasSize(2)))
                .andDo(findResultHandler);

        // Query by name
        this.mvc.perform(MockMvcRequestBuilders.get(COMMANDS_API).param("name", name2))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_LIST_PATH, Matchers.hasSize(1)))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_LIST_PATH + "[0].id", Matchers.is(id2)))
                .andDo(findResultHandler);

        // Query by user
        this.mvc.perform(MockMvcRequestBuilders.get(COMMANDS_API).param("user", user3))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_LIST_PATH, Matchers.hasSize(1)))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_LIST_PATH + "[0].id", Matchers.is(id3)))
                .andDo(findResultHandler);

        // Query by statuses
        this.mvc.perform(MockMvcRequestBuilders.get(COMMANDS_API).param("status", CommandStatus.ACTIVE.toString(),
                CommandStatus.INACTIVE.toString())).andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_LIST_PATH, Matchers.hasSize(2)))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_LIST_PATH + "[0].id", Matchers.is(id3)))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_LIST_PATH + "[1].id", Matchers.is(id1)))
                .andDo(findResultHandler);

        // Query by tags
        this.mvc.perform(MockMvcRequestBuilders.get(COMMANDS_API).param("tag", "genie.id:" + id1))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_LIST_PATH, Matchers.hasSize(1)))
                .andExpect(MockMvcResultMatchers.jsonPath(COMMANDS_LIST_PATH + "[0].id", Matchers.is(id1)))
                .andDo(findResultHandler);

        //TODO: Add tests for sort, orderBy etc

        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(3L));
    }

    /**
     * Test to make sure that a command can be updated.
     *
     * @throws Exception on configuration errors
     */
    @Test
    public void canUpdateCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);
        final String commandResource = COMMANDS_API + "/{id}";
        final Command createdCommand = objectMapper
                .readValue(this.mvc.perform(MockMvcRequestBuilders.get(commandResource, ID)).andReturn()
                        .getResponse().getContentAsByteArray(), CommandResource.class)
                .getContent();
        Assert.assertThat(createdCommand.getStatus(), Matchers.is(CommandStatus.ACTIVE));

        final Command.Builder updateCommand = new Command.Builder(createdCommand.getName(),
                createdCommand.getUser(), createdCommand.getVersion(), CommandStatus.INACTIVE,
                createdCommand.getExecutable(), createdCommand.getCheckDelay())
                        .withId(createdCommand.getId().orElseThrow(IllegalArgumentException::new))
                        .withCreated(createdCommand.getCreated().orElseThrow(IllegalArgumentException::new))
                        .withUpdated(createdCommand.getUpdated().orElseThrow(IllegalArgumentException::new))
                        .withTags(createdCommand.getTags()).withConfigs(createdCommand.getConfigs())
                        .withDependencies(createdCommand.getDependencies());

        createdCommand.getDescription().ifPresent(updateCommand::withDescription);
        createdCommand.getSetupFile().ifPresent(updateCommand::withSetupFile);

        final RestDocumentationResultHandler updateResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.CONTENT_TYPE_HEADER, // request header
                Snippets.ID_PATH_PARAM, // path parameters
                Snippets.getCommandRequestPayload() // payload fields
        );

        this.mvc.perform(
                RestDocumentationRequestBuilders.put(commandResource, ID).contentType(MediaType.APPLICATION_JSON)
                        .content(this.objectMapper.writeValueAsBytes(updateCommand.build())))
                .andExpect(MockMvcResultMatchers.status().isNoContent()).andDo(updateResultHandler);

        this.mvc.perform(MockMvcRequestBuilders.get(commandResource, ID))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath(STATUS_PATH,
                        Matchers.is(CommandStatus.INACTIVE.toString())));
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(1L));
    }

    /**
     * Test to make sure that a command can be patched.
     *
     * @throws Exception on configuration errors
     */
    @Test
    public void canPatchCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        final String id = this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);
        final String commandResource = COMMANDS_API + "/{id}";
        this.mvc.perform(MockMvcRequestBuilders.get(commandResource, id))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath(NAME_PATH, Matchers.is(NAME)));

        final String newName = UUID.randomUUID().toString();
        final String patchString = "[{ \"op\": \"replace\", \"path\": \"/name\", \"value\": \"" + newName + "\" }]";
        final JsonPatch patch = JsonPatch.fromJson(this.objectMapper.readTree(patchString));

        final RestDocumentationResultHandler patchResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.CONTENT_TYPE_HEADER, // request headers
                Snippets.ID_PATH_PARAM, // path params
                Snippets.PATCH_FIELDS // request payload
        );

        this.mvc.perform(RestDocumentationRequestBuilders.patch(commandResource, id)
                .contentType(MediaType.APPLICATION_JSON).content(this.objectMapper.writeValueAsBytes(patch)))
                .andExpect(MockMvcResultMatchers.status().isNoContent()).andDo(patchResultHandler);

        this.mvc.perform(MockMvcRequestBuilders.get(commandResource, id))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath(NAME_PATH, Matchers.is(newName)));
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(1L));
    }

    /**
     * Make sure can successfully delete all commands.
     *
     * @throws Exception on a configuration error
     */
    @Test
    public void canDeleteAllCommands() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).build(),
                null);
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.DEPRECATED, EXECUTABLE, CHECK_DELAY).build(),
                null);
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.INACTIVE, EXECUTABLE, CHECK_DELAY).build(),
                null);
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(3L));

        final RestDocumentationResultHandler deleteResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()));

        this.mvc.perform(MockMvcRequestBuilders.delete(COMMANDS_API))
                .andExpect(MockMvcResultMatchers.status().isNoContent()).andDo(deleteResultHandler);

        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
    }

    /**
     * Test to make sure that you can delete a command.
     *
     * @throws Exception on configuration error
     */
    @Test
    public void canDeleteACommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        final String id1 = UUID.randomUUID().toString();
        final String id2 = UUID.randomUUID().toString();
        final String id3 = UUID.randomUUID().toString();
        final String name1 = UUID.randomUUID().toString();
        final String name2 = UUID.randomUUID().toString();
        final String name3 = UUID.randomUUID().toString();
        final String user1 = UUID.randomUUID().toString();
        final String user2 = UUID.randomUUID().toString();
        final String user3 = UUID.randomUUID().toString();
        final String version1 = UUID.randomUUID().toString();
        final String version2 = UUID.randomUUID().toString();
        final String version3 = UUID.randomUUID().toString();
        final String executable1 = UUID.randomUUID().toString();
        final String executable2 = UUID.randomUUID().toString();
        final String executable3 = UUID.randomUUID().toString();

        this.createConfigResource(
                new Command.Builder(name1, user1, version1, CommandStatus.ACTIVE, executable1, CHECK_DELAY)
                        .withId(id1).build(),
                null);
        this.createConfigResource(
                new Command.Builder(name2, user2, version2, CommandStatus.ACTIVE, executable2, CHECK_DELAY)
                        .withId(id2).build(),
                null);
        this.createConfigResource(
                new Command.Builder(name3, user3, version3, CommandStatus.ACTIVE, executable3, CHECK_DELAY)
                        .withId(id3).build(),
                null);
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(3L));

        final RestDocumentationResultHandler deleteResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM // path parameters
        );

        this.mvc.perform(RestDocumentationRequestBuilders.delete(COMMANDS_API + "/{id}", id2))
                .andExpect(MockMvcResultMatchers.status().isNoContent()).andDo(deleteResultHandler);

        this.mvc.perform(MockMvcRequestBuilders.get(COMMANDS_API + "/{id}", id2))
                .andExpect(MockMvcResultMatchers.status().isNotFound());

        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(2L));
    }

    /**
     * Test to make sure we can add configurations to the command after it is created.
     *
     * @throws Exception on configuration problems
     */
    @Test
    public void canAddConfigsToCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);

        final RestDocumentationResultHandler addResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // path params
                Snippets.CONTENT_TYPE_HEADER, // request header
                PayloadDocumentation.requestFields(Snippets.CONFIG_FIELDS) // response fields
        );
        final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // path params
                Snippets.JSON_CONTENT_TYPE_HEADER, // response headers
                PayloadDocumentation.responseFields(Snippets.CONFIG_FIELDS) // response fields
        );
        this.canAddElementsToResource(COMMANDS_API + "/{id}/configs", ID, addResultHandler, getResultHandler);
    }

    /**
     * Test to make sure we can update the configurations for a command after it is created.
     *
     * @throws Exception on configuration problems
     */
    @Test
    public void canUpdateConfigsForCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);

        final RestDocumentationResultHandler updateResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.CONTENT_TYPE_HEADER, // Request header
                Snippets.ID_PATH_PARAM, // Path parameters
                PayloadDocumentation.requestFields(Snippets.CONFIG_FIELDS) // Request fields
        );
        this.canUpdateElementsForResource(COMMANDS_API + "/{id}/configs", ID, updateResultHandler);
    }

    /**
     * Test to make sure we can delete the configurations for a command after it is created.
     *
     * @throws Exception on configuration problems
     */
    @Test
    public void canDeleteConfigsForCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);

        final RestDocumentationResultHandler deleteResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM);
        this.canDeleteElementsFromResource(COMMANDS_API + "/{id}/configs", ID, deleteResultHandler);
    }

    /**
     * Test to make sure we can add dependencies to the command after it is created.
     *
     * @throws Exception on configuration problems
     */
    @Test
    public void canAddDependenciesToCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);

        final RestDocumentationResultHandler addResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // path params
                Snippets.CONTENT_TYPE_HEADER, // request header
                PayloadDocumentation.requestFields(Snippets.DEPENDENCIES_FIELDS) // response fields
        );
        final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // path params
                Snippets.JSON_CONTENT_TYPE_HEADER, // response headers
                PayloadDocumentation.responseFields(Snippets.DEPENDENCIES_FIELDS) // response fields
        );
        this.canAddElementsToResource(COMMANDS_API + "/{id}/dependencies", ID, addResultHandler, getResultHandler);
    }

    /**
     * Test to make sure we can update the dependencies for an command after it is created.
     *
     * @throws Exception on configuration problems
     */
    @Test
    public void canUpdateDependenciesForCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);

        final RestDocumentationResultHandler updateResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.CONTENT_TYPE_HEADER, // Request header
                Snippets.ID_PATH_PARAM, // Path parameters
                PayloadDocumentation.requestFields(Snippets.DEPENDENCIES_FIELDS) // Request fields
        );
        this.canUpdateElementsForResource(COMMANDS_API + "/{id}/dependencies", ID, updateResultHandler);
    }

    /**
     * Test to make sure we can delete the dependencies for an command after it is created.
     *
     * @throws Exception on configuration problems
     */
    @Test
    public void canDeleteDependenciesForCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);

        final RestDocumentationResultHandler deleteResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM // Path variables
        );
        this.canDeleteElementsFromResource(COMMANDS_API + "/{id}/dependencies", ID, deleteResultHandler);
    }

    /**
     * Test to make sure we can add tags to the command after it is created.
     *
     * @throws Exception on configuration problems
     */
    @Test
    public void canAddTagsToCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);
        final String api = COMMANDS_API + "/{id}/tags";

        final RestDocumentationResultHandler addResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.CONTENT_TYPE_HEADER, // Request header
                Snippets.ID_PATH_PARAM, // Path parameters
                PayloadDocumentation.requestFields(Snippets.TAGS_FIELDS) // Request fields
        );
        final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // Path parameters
                Snippets.JSON_CONTENT_TYPE_HEADER, // Response header
                PayloadDocumentation.responseFields(Snippets.TAGS_FIELDS));
        this.canAddTagsToResource(api, ID, NAME, addResultHandler, getResultHandler);
    }

    /**
     * Test to make sure we can update the tags for a command after it is created.
     *
     * @throws Exception on configuration problems
     */
    @Test
    public void canUpdateTagsForCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);
        final String api = COMMANDS_API + "/{id}/tags";

        final RestDocumentationResultHandler updateResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.CONTENT_TYPE_HEADER, // Request header
                Snippets.ID_PATH_PARAM, // Path parameters
                PayloadDocumentation.requestFields(Snippets.TAGS_FIELDS) // Request fields
        );
        this.canUpdateTagsForResource(api, ID, NAME, updateResultHandler);
    }

    /**
     * Test to make sure we can delete the tags for a command after it is created.
     *
     * @throws Exception on configuration problems
     */
    @Test
    public void canDeleteTagsForCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);
        final String api = COMMANDS_API + "/{id}/tags";

        final RestDocumentationResultHandler deleteResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM);
        this.canDeleteTagsForResource(api, ID, NAME, deleteResultHandler);
    }

    /**
     * Test to make sure we can delete a tag for a command after it is created.
     *
     * @throws Exception on configuration problems
     */
    @Test
    public void canDeleteTagForCommand() throws Exception {
        Assert.assertThat(this.jpaCommandRepository.count(), Matchers.is(0L));
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);
        final String api = COMMANDS_API + "/{id}/tags";

        final RestDocumentationResultHandler deleteResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM
                        .and(RequestDocumentation.parameterWithName("tag").description("The tag to remove")));
        this.canDeleteTagForResource(api, ID, NAME, deleteResultHandler);
    }

    /**
     * Make sure can add the applications for a command.
     *
     * @throws Exception on configuration error
     */
    @Test
    public void canAddApplicationsForACommand() throws Exception {
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);
        final String commandApplicationsAPI = COMMANDS_API + "/{id}/applications";
        this.mvc.perform(MockMvcRequestBuilders.get(commandApplicationsAPI, ID))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.empty()));

        final String placeholder = UUID.randomUUID().toString();
        final String applicationId1 = UUID.randomUUID().toString();
        final String applicationId2 = UUID.randomUUID().toString();
        this.createConfigResource(
                new Application.Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE)
                        .withId(applicationId1).build(),
                null);
        this.createConfigResource(
                new Application.Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE)
                        .withId(applicationId2).build(),
                null);

        final RestDocumentationResultHandler addResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.CONTENT_TYPE_HEADER, // Request Headers
                Snippets.ID_PATH_PARAM, // Path parameters
                PayloadDocumentation.requestFields(PayloadDocumentation.fieldWithPath("[]")
                        .description("Array of application ids to add to existing set of applications")
                        .attributes(Snippets.EMPTY_CONSTRAINTS)) // Request payload
        );

        this.mvc.perform(RestDocumentationRequestBuilders.post(commandApplicationsAPI, ID)
                .contentType(MediaType.APPLICATION_JSON)
                .content(this.objectMapper.writeValueAsBytes(Lists.newArrayList(applicationId1, applicationId2))))
                .andExpect(MockMvcResultMatchers.status().isNoContent()).andDo(addResultHandler);

        this.mvc.perform(MockMvcRequestBuilders.get(commandApplicationsAPI, ID))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(2)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[0].id", Matchers.is(applicationId1)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[1].id", Matchers.is(applicationId2)));

        //Shouldn't add anything
        this.mvc.perform(
                MockMvcRequestBuilders.post(commandApplicationsAPI, ID).contentType(MediaType.APPLICATION_JSON)
                        .content(this.objectMapper.writeValueAsBytes(Lists.newArrayList())))
                .andExpect(MockMvcResultMatchers.status().isPreconditionFailed());

        final String applicationId3 = UUID.randomUUID().toString();
        this.createConfigResource(
                new Application.Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE)
                        .withId(applicationId3).build(),
                null);
        this.mvc.perform(
                MockMvcRequestBuilders.post(commandApplicationsAPI, ID).contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(Lists.newArrayList(applicationId3))))
                .andExpect(MockMvcResultMatchers.status().isNoContent());

        final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // Path parameters
                Snippets.HAL_CONTENT_TYPE_HEADER, // Response Headers
                PayloadDocumentation.responseFields(PayloadDocumentation.fieldWithPath("[]")
                        .description("The set of applications this command depends on")
                        .attributes(Snippets.EMPTY_CONSTRAINTS)));

        this.mvc.perform(RestDocumentationRequestBuilders.get(commandApplicationsAPI, ID))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(3)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[0].id", Matchers.is(applicationId1)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[1].id", Matchers.is(applicationId2)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[2].id", Matchers.is(applicationId3)))
                .andDo(getResultHandler);
    }

    /**
     * Make sure can set the applications for a command.
     *
     * @throws Exception on configuration error
     */
    @Test
    public void canSetApplicationsForACommand() throws Exception {
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);
        final String commandApplicationsAPI = COMMANDS_API + "/{id}/applications";
        this.mvc.perform(MockMvcRequestBuilders.get(commandApplicationsAPI, ID))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.empty()));

        final String placeholder = UUID.randomUUID().toString();
        final String applicationId1 = UUID.randomUUID().toString();
        final String applicationId2 = UUID.randomUUID().toString();
        final String applicationId3 = UUID.randomUUID().toString();
        this.createConfigResource(
                new Application.Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE)
                        .withId(applicationId1).build(),
                null);
        this.createConfigResource(
                new Application.Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE)
                        .withId(applicationId2).build(),
                null);
        this.createConfigResource(
                new Application.Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE)
                        .withId(applicationId3).build(),
                null);

        final RestDocumentationResultHandler setResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.CONTENT_TYPE_HEADER, // Request Headers
                Snippets.ID_PATH_PARAM, // Path parameters
                PayloadDocumentation.requestFields(PayloadDocumentation.fieldWithPath("[]")
                        .description("Array of application ids to replace the existing set of applications with")
                        .attributes(Snippets.EMPTY_CONSTRAINTS)) // Request payload
        );

        this.mvc.perform(RestDocumentationRequestBuilders.put(commandApplicationsAPI, ID)
                .contentType(MediaType.APPLICATION_JSON)
                .content(this.objectMapper.writeValueAsBytes(Lists.newArrayList(applicationId1, applicationId2))))
                .andExpect(MockMvcResultMatchers.status().isNoContent()).andDo(setResultHandler);

        this.mvc.perform(MockMvcRequestBuilders.get(commandApplicationsAPI, ID))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(2)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[0].id", Matchers.is(applicationId1)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[1].id", Matchers.is(applicationId2)));

        // Should flip the order
        this.mvc.perform(
                MockMvcRequestBuilders.put(commandApplicationsAPI, ID).contentType(MediaType.APPLICATION_JSON)
                        .content(this.objectMapper
                                .writeValueAsBytes(Lists.newArrayList(applicationId2, applicationId1))))
                .andExpect(MockMvcResultMatchers.status().isNoContent());

        this.mvc.perform(MockMvcRequestBuilders.get(commandApplicationsAPI, ID))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(2)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[0].id", Matchers.is(applicationId2)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[1].id", Matchers.is(applicationId1)));

        // Should reorder and add a new one
        this.mvc.perform(
                MockMvcRequestBuilders.put(commandApplicationsAPI, ID).contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(
                                Lists.newArrayList(applicationId1, applicationId2, applicationId3))))
                .andExpect(MockMvcResultMatchers.status().isNoContent());

        this.mvc.perform(MockMvcRequestBuilders.get(commandApplicationsAPI, ID))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(3)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[0].id", Matchers.is(applicationId1)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[1].id", Matchers.is(applicationId2)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[2].id", Matchers.is(applicationId3)));

        //Should clear applications
        this.mvc.perform(
                MockMvcRequestBuilders.put(commandApplicationsAPI, ID).contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(Lists.newArrayList())))
                .andExpect(MockMvcResultMatchers.status().isNoContent());

        this.mvc.perform(MockMvcRequestBuilders.get(commandApplicationsAPI, ID))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.empty()));
    }

    /**
     * Make sure that we can remove all the applications from a command.
     *
     * @throws Exception on configuration error
     */
    @Test
    public void canRemoveApplicationsFromACommand() throws Exception {
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);
        final String commandApplicationsAPI = COMMANDS_API + "/{id}/applications";

        final String placeholder = UUID.randomUUID().toString();
        final String applicationId1 = UUID.randomUUID().toString();
        final String applicationId2 = UUID.randomUUID().toString();
        this.createConfigResource(
                new Application.Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE)
                        .withId(applicationId1).build(),
                null);
        this.createConfigResource(
                new Application.Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE)
                        .withId(applicationId2).build(),
                null);

        this.mvc.perform(MockMvcRequestBuilders.post(commandApplicationsAPI, ID)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsBytes(Lists.newArrayList(applicationId1, applicationId2))))
                .andExpect(MockMvcResultMatchers.status().isNoContent());

        final RestDocumentationResultHandler deleteResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM // Path parameters
        );

        this.mvc.perform(RestDocumentationRequestBuilders.delete(commandApplicationsAPI, ID))
                .andExpect(MockMvcResultMatchers.status().isNoContent()).andDo(deleteResultHandler);

        this.mvc.perform(MockMvcRequestBuilders.get(commandApplicationsAPI, ID))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.empty()));
    }

    /**
     * Make sure that we can remove an application from a command.
     *
     * @throws Exception on configuration error
     */
    @Test
    public void canRemoveApplicationFromACommand() throws Exception {
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);
        final String commandApplicationsAPI = COMMANDS_API + "/{id}/applications";

        final String placeholder = UUID.randomUUID().toString();
        final String applicationId1 = UUID.randomUUID().toString();
        final String applicationId2 = UUID.randomUUID().toString();
        final String applicationId3 = UUID.randomUUID().toString();
        this.createConfigResource(
                new Application.Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE)
                        .withId(applicationId1).build(),
                null);
        this.createConfigResource(
                new Application.Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE)
                        .withId(applicationId2).build(),
                null);
        this.createConfigResource(
                new Application.Builder(placeholder, placeholder, placeholder, ApplicationStatus.ACTIVE)
                        .withId(applicationId3).build(),
                null);

        this.mvc.perform(
                MockMvcRequestBuilders.post(commandApplicationsAPI, ID).contentType(MediaType.APPLICATION_JSON)
                        .content(this.objectMapper.writeValueAsBytes(
                                Lists.newArrayList(applicationId1, applicationId2, applicationId3))))
                .andExpect(MockMvcResultMatchers.status().isNoContent());

        final RestDocumentationResultHandler deleteResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()),
                Snippets.ID_PATH_PARAM.and(RequestDocumentation.parameterWithName("applicationId")
                        .description("The id of the application to remove")) // Path parameters
        );

        this.mvc.perform(RestDocumentationRequestBuilders.delete(commandApplicationsAPI + "/{applicationId}", ID,
                applicationId2)).andExpect(MockMvcResultMatchers.status().isNoContent()).andDo(deleteResultHandler);

        this.mvc.perform(MockMvcRequestBuilders.get(commandApplicationsAPI, ID))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(2)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[0].id", Matchers.is(applicationId1)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[1].id", Matchers.is(applicationId3)));

        // Check reverse side of relationship
        this.mvc.perform(MockMvcRequestBuilders.get(APPLICATIONS_API + "/{id}/commands", applicationId1))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(1)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[0].id", Matchers.is(ID)));

        this.mvc.perform(MockMvcRequestBuilders.get(APPLICATIONS_API + "/{id}/commands", applicationId2))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.empty()));

        this.mvc.perform(MockMvcRequestBuilders.get(APPLICATIONS_API + "/{id}/commands", applicationId3))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(1)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[0].id", Matchers.is(ID)));
    }

    /**
     * Make sure can get all the clusters which use a given command.
     *
     * @throws Exception on configuration error
     */
    @Test
    public void canGetClustersForCommand() throws Exception {
        this.createConfigResource(
                new Command.Builder(NAME, USER, VERSION, CommandStatus.ACTIVE, EXECUTABLE, CHECK_DELAY).withId(ID)
                        .build(),
                null);
        final String placeholder = UUID.randomUUID().toString();
        final String cluster1Id = UUID.randomUUID().toString();
        final String cluster2Id = UUID.randomUUID().toString();
        final String cluster3Id = UUID.randomUUID().toString();
        this.createConfigResource(new Cluster.Builder(placeholder, placeholder, placeholder, ClusterStatus.UP)
                .withId(cluster1Id).build(), null);
        this.createConfigResource(
                new Cluster.Builder(placeholder, placeholder, placeholder, ClusterStatus.OUT_OF_SERVICE)
                        .withId(cluster2Id).build(),
                null);
        this.createConfigResource(
                new Cluster.Builder(placeholder, placeholder, placeholder, ClusterStatus.TERMINATED)
                        .withId(cluster3Id).build(),
                null);

        final List<String> commandIds = Lists.newArrayList(ID);
        this.mvc.perform(MockMvcRequestBuilders.post(CLUSTERS_API + "/" + cluster1Id + "/commands")
                .contentType(MediaType.APPLICATION_JSON).content(this.objectMapper.writeValueAsBytes(commandIds)))
                .andExpect(MockMvcResultMatchers.status().isNoContent());
        this.mvc.perform(MockMvcRequestBuilders.post(CLUSTERS_API + "/" + cluster3Id + "/commands")
                .contentType(MediaType.APPLICATION_JSON).content(this.objectMapper.writeValueAsBytes(commandIds)))
                .andExpect(MockMvcResultMatchers.status().isNoContent());

        Arrays.stream(
                this.objectMapper
                        .readValue(
                                this.mvc.perform(MockMvcRequestBuilders.get(COMMANDS_API + "/" + ID + "/clusters"))
                                        .andExpect(MockMvcResultMatchers.status().isOk())
                                        .andExpect(MockMvcResultMatchers.content()
                                                .contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                                        .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(2)))
                                        .andReturn().getResponse().getContentAsByteArray(),
                                ClusterResource[].class))
                .map(ClusterResource::getContent).forEach(cluster -> {
                    final String id = cluster.getId().orElseThrow(IllegalArgumentException::new);
                    if (!id.equals(cluster1Id) && !id.equals(cluster3Id)) {
                        Assert.fail();
                    }
                });

        // Test filtering
        final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document(
                "{class-name}/{method-name}/{step}/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()),
                Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // Path parameters
                RequestDocumentation.requestParameters(RequestDocumentation.parameterWithName("status")
                        .description("The status of clusters to search for")
                        .attributes(Attributes.key(Snippets.CONSTRAINTS).value(CommandStatus.values())).optional()), // Query Parameters
                Snippets.HAL_CONTENT_TYPE_HEADER, // Response Headers
                PayloadDocumentation.responseFields(PayloadDocumentation.fieldWithPath("[]")
                        .description("The list of clusters found").attributes(Snippets.EMPTY_CONSTRAINTS)));
        this.mvc.perform(RestDocumentationRequestBuilders.get(COMMANDS_API + "/{id}/clusters", ID).param("status",
                ClusterStatus.UP.toString())).andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(1)))
                .andExpect(MockMvcResultMatchers.jsonPath("$[0].id", Matchers.is(cluster1Id)))
                .andDo(getResultHandler);
    }
}