/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.workspace.server; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.jayway.restassured.response.Response; import org.eclipse.che.account.shared.model.Account; import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.agent.server.WsAgentHealthChecker; import org.eclipse.che.api.core.model.machine.MachineStatus; import org.eclipse.che.api.core.model.project.ProjectConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.rest.ApiExceptionMapper; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; import org.eclipse.che.api.environment.server.MachineLinksInjector; import org.eclipse.che.api.environment.server.MachineProcessManager; import org.eclipse.che.api.machine.server.model.impl.CommandImpl; import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; import org.eclipse.che.api.machine.server.model.impl.MachineLimitsImpl; import org.eclipse.che.api.machine.server.model.impl.MachineRuntimeInfoImpl; import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl; import org.eclipse.che.api.machine.server.model.impl.ServerImpl; import org.eclipse.che.api.machine.server.model.impl.ServerPropertiesImpl; import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; import org.eclipse.che.api.machine.shared.dto.CommandDto; import org.eclipse.che.api.machine.shared.dto.SnapshotDto; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentRecipeImpl; import org.eclipse.che.api.workspace.server.model.impl.ExtendedMachineImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceRuntimeImpl; import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; import org.eclipse.che.api.workspace.shared.dto.WsAgentHealthStateDto; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.dto.server.DtoFactory; import org.everrest.assured.EverrestJetty; import org.everrest.core.Filter; import org.everrest.core.GenericContainerRequest; import org.everrest.core.RequestFilter; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import static com.jayway.restassured.RestAssured.given; import static java.lang.String.format; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.RUNNING; import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STARTING; import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL; import static org.eclipse.che.api.machine.shared.Constants.LINK_REL_ENVIRONMENT_STATUS_CHANNEL; import static org.eclipse.che.api.machine.shared.Constants.WSAGENT_REFERENCE; import static org.eclipse.che.api.machine.shared.Constants.WSAGENT_WEBSOCKET_REFERENCE; import static org.eclipse.che.api.workspace.shared.Constants.GET_ALL_USER_WORKSPACES; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_GET_SNAPSHOT; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_GET_WORKSPACE_EVENTS_CHANNEL; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_IDE_URL; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_REMOVE_WORKSPACE; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_SELF; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_START_WORKSPACE; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_STOP_WORKSPACE; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; import static org.everrest.assured.JettyHttpServer.SECURE_PATH; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; /** * Tests for {@link WorkspaceService}. * * @author Yevhenii Voevodin */ @Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class}) public class WorkspaceServiceTest { @SuppressWarnings("unused") private static final ApiExceptionMapper MAPPER = new ApiExceptionMapper(); private static final String NAMESPACE = "namespace_part1/namespace_part_2"; private static final String USER_ID = "user123"; private static final String API_ENDPOINT = "http://localhost:8080/api"; private static final Account TEST_ACCOUNT = new AccountImpl("anyId", NAMESPACE, "test"); @SuppressWarnings("unused") private static final EnvironmentFilter FILTER = new EnvironmentFilter(); @Mock private WorkspaceManager wsManager; @Mock private MachineProcessManager machineProcessManager; @Mock private WorkspaceValidator validator; @Mock private WsAgentHealthChecker wsAgentHealthChecker; private WorkspaceService service; @BeforeMethod public void setup() { service = new WorkspaceService(API_ENDPOINT, wsManager, validator, wsAgentHealthChecker, new WorkspaceServiceLinksInjector(new MachineLinksInjector()), true, false, true); } @Test public void shouldCreateWorkspace() throws Exception { final WorkspaceConfigDto configDto = createConfigDto(); final WorkspaceImpl workspace = createWorkspace(configDto); when(wsManager.createWorkspace(any(), any(), any())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(configDto) .when() .post(SECURE_PATH + "/workspace" + "?namespace=test" + "&attribute=stackId:stack123" + "&attribute=factoryId:factory123" + "&attribute=custom:custom:value"); assertEquals(response.getStatusCode(), 201); assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); verify(validator).validateConfig(any()); verify(validator).validateAttributes(any()); verify(wsManager).createWorkspace(anyObject(), eq("test"), eq(ImmutableMap.of("stackId", "stack123", "factoryId", "factory123", "custom", "custom:value"))); } @Test public void shouldUseUsernameAsNamespaceWhenCreatingWorkspaceWithoutSpecifiedNamespace() throws Exception { final WorkspaceConfigDto configDto = createConfigDto(); final WorkspaceImpl workspace = createWorkspace(configDto); when(wsManager.createWorkspace(any(), any(), any())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(configDto) .when() .post(SECURE_PATH + "/workspace" + "?attribute=stackId:stack123" + "&attribute=factoryId:factory123" + "&attribute=custom:custom:value"); assertEquals(response.getStatusCode(), 201); assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); verify(validator).validateConfig(any()); verify(validator).validateAttributes(any()); verify(wsManager).createWorkspace(anyObject(), eq(NAMESPACE), eq(ImmutableMap.of("stackId", "stack123", "factoryId", "factory123", "custom", "custom:value"))); } @Test public void shouldStartTheWorkspaceAfterItIsCreatedWhenStartAfterCreateParamIsTrue() throws Exception { final WorkspaceConfigDto configDto = createConfigDto(); final WorkspaceImpl workspace = createWorkspace(configDto); when(wsManager.createWorkspace(any(), any(), any())).thenReturn(workspace); given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(configDto) .when() .post(SECURE_PATH + "/workspace" + "?attribute=stackId:stack123" + "&attribute=factoryId:factory123" + "&attribute=custom:custom:value" + "&start-after-create=true"); verify(wsManager).startWorkspace(workspace.getId(), null, false); verify(wsManager).createWorkspace(anyObject(), anyString(), eq(ImmutableMap.of("stackId", "stack123", "factoryId", "factory123", "custom", "custom:value"))); } @Test public void createShouldReturn400WhenAttributesAreNotValid() throws Exception { final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(createConfigDto()) .when() .post(SECURE_PATH + "/workspace?attribute=stackId=stack123"); assertEquals(response.getStatusCode(), 400); assertEquals(unwrapError(response), "Attribute 'stackId=stack123' is not valid, " + "it should contain name and value separated " + "with colon. For example: attributeName:attributeValue"); } @Test public void shouldRelativizeLinksOnCreateWorkspace() throws Exception { final String initialLocation = "http://localhost:8080/api/recipe/idrecipe123456789/script"; final WorkspaceConfigDto configDto = createConfigDto(); configDto.getEnvironments().get(configDto.getDefaultEnv()).getRecipe().withLocation(initialLocation) .withType("dockerfile"); ArgumentCaptor<WorkspaceConfigDto> captor = ArgumentCaptor.forClass(WorkspaceConfigDto.class); when(wsManager.createWorkspace(captor.capture(), any(), any())).thenAnswer(invocation -> createWorkspace(captor.getValue())); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(configDto) .when() .post(SECURE_PATH + "/workspace" + "?namespace=test" + "&attribute=stackId:stack123" + "&attribute=custom:custom:value"); assertEquals(response.getStatusCode(), 201); String savedLocation = unwrapDto(response, WorkspaceDto.class).getConfig() .getEnvironments() .get(configDto.getDefaultEnv()) .getRecipe() .getLocation(); assertEquals(savedLocation, initialLocation.substring(API_ENDPOINT.length())); } @Test public void createShouldReturn400WhenConfigIsNotSent() throws Exception { final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .when() .post(SECURE_PATH + "/workspace?attribute=stackId=stack123"); assertEquals(response.getStatusCode(), 400); assertEquals(unwrapError(response), "Workspace configuration required"); } @Test public void shouldGetWorkspaceById() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/workspace/" + workspace.getId()); assertEquals(response.getStatusCode(), 200); assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); } @Test public void shouldGetWorkspaceByKey() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getNamespace() + "/" + workspace.getConfig().getName())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/workspace/" + workspace.getNamespace() + "/" + workspace.getConfig().getName()); assertEquals(response.getStatusCode(), 200); assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); } @Test public void shouldGetWorkspaces() throws Exception { final WorkspaceImpl workspace1 = createWorkspace(createConfigDto()); final WorkspaceImpl workspace2 = createWorkspace(createConfigDto(), STARTING); when(wsManager.getWorkspaces(USER_ID, false)).thenReturn(asList(workspace1, workspace2)); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/workspace"); assertEquals(response.getStatusCode(), 200); assertEquals(unwrapDtoList(response, WorkspaceDto.class).stream() .map(ws -> new WorkspaceImpl(ws, TEST_ACCOUNT)) .collect(toList()), asList(workspace1, workspace2)); } @Test public void shouldGetWorkspacesByNamespace() throws Exception { final WorkspaceImpl workspace1 = createWorkspace(createConfigDto()); final WorkspaceImpl workspace2 = createWorkspace(createConfigDto(), STARTING); when(wsManager.getByNamespace(NAMESPACE, false)).thenReturn(asList(workspace1, workspace2)); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/workspace/namespace/" + NAMESPACE); assertEquals(response.getStatusCode(), 200); assertEquals(unwrapDtoList(response, WorkspaceDto.class).stream() .map(ws -> new WorkspaceImpl(ws, TEST_ACCOUNT)) .collect(toList()), asList(workspace1, workspace2)); } @Test public void shouldGetWorkspacesByStatus() throws Exception { final WorkspaceImpl workspace1 = createWorkspace(createConfigDto()); final WorkspaceImpl workspace2 = createWorkspace(createConfigDto(), STARTING); when(wsManager.getWorkspaces(USER_ID, false)).thenReturn(asList(workspace1, workspace2)); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/workspace?status=starting"); assertEquals(response.getStatusCode(), 200); assertEquals(unwrapDtoList(response, WorkspaceDto.class).stream() .map(ws -> new WorkspaceImpl(ws, TEST_ACCOUNT)) .collect(toList()), singletonList(workspace2)); } @Test public void shouldUpdateTheWorkspace() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.updateWorkspace(any(), any())).thenReturn(workspace); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final WorkspaceDto workspaceDto = DtoConverter.asDto(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(workspaceDto) .when() .put(SECURE_PATH + "/workspace/" + workspace.getId()); assertEquals(response.getStatusCode(), 200); assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); verify(validator).validateWorkspace(any()); } @Test public void shouldDeleteWorkspace() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); when(wsManager.getSnapshot(anyString())).thenReturn(ImmutableList.of(mock(SnapshotImpl.class))); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .delete(SECURE_PATH + "/workspace/" + workspace.getId()); assertEquals(response.getStatusCode(), 204); verify(wsManager).removeSnapshots(workspace.getId()); verify(wsManager).removeWorkspace(workspace.getId()); } @Test public void shouldStartWorkspace() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.startWorkspace(any(), any(), any())).thenReturn(workspace); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .post(SECURE_PATH + "/workspace/" + workspace.getId() + "/runtime" + "?environment=" + workspace.getConfig().getDefaultEnv()); assertEquals(response.getStatusCode(), 200); assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); verify(wsManager).startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), null); } @Test public void shouldRestoreWorkspace() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.startWorkspace(any(), any(), any())).thenReturn(workspace); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .post(SECURE_PATH + "/workspace/" + workspace.getId() + "/runtime" + "?environment=" + workspace.getConfig().getDefaultEnv() + "&restore=true"); assertEquals(response.getStatusCode(), 200); assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); verify(wsManager).startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), true); } @Test public void shouldNotRestoreWorkspace() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.startWorkspace(any(), any(), any())).thenReturn(workspace); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .post(SECURE_PATH + "/workspace/" + workspace.getId() + "/runtime" + "?environment=" + workspace.getConfig().getDefaultEnv() + "&restore=false"); assertEquals(response.getStatusCode(), 200); assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); verify(wsManager).startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), false); } @Test public void shouldStartWorkspaceFromConfig() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.startWorkspace(anyObject(), anyString(), anyBoolean())).thenReturn(workspace); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final WorkspaceDto workspaceDto = DtoConverter.asDto(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(workspaceDto.getConfig()) .when() .post(SECURE_PATH + "/workspace/runtime" + "?namespace=test" + "&temporary=true"); assertEquals(response.getStatusCode(), 200); verify(validator).validateConfig(any()); verify(wsManager).startWorkspace(any(), eq("test"), eq(true)); } @Test public void shouldUseUsernameAsNamespaceWhenStartingWorkspaceFromConfigWithoutNamespace() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.startWorkspace(anyObject(), anyString(), anyBoolean())).thenReturn(workspace); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final WorkspaceDto workspaceDto = DtoConverter.asDto(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(workspaceDto.getConfig()) .when() .post(SECURE_PATH + "/workspace/runtime" + "?temporary=true"); assertEquals(response.getStatusCode(), 200); verify(validator).validateConfig(any()); verify(wsManager).startWorkspace(any(), eq(NAMESPACE), eq(true)); } @Test public void shouldStopWorkspace() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .delete(SECURE_PATH + "/workspace/" + workspace.getId() + "/runtime"); assertEquals(response.getStatusCode(), 204); verify(wsManager).stopWorkspace(workspace.getId(), null); } @Test public void shouldCreateSnapshot() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .post(SECURE_PATH + "/workspace/" + workspace.getId() + "/snapshot"); assertEquals(response.getStatusCode(), 204); verify(wsManager).createSnapshot(workspace.getId()); } @Test public void shouldAddCommand() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); when(wsManager.updateWorkspace(any(), any())).thenReturn(workspace); final CommandDto commandDto = createCommandDto(); final int commandsSizeBefore = workspace.getConfig().getCommands().size(); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(commandDto) .when() .post(SECURE_PATH + "/workspace/" + workspace.getId() + "/command"); assertEquals(response.getStatusCode(), 200); assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT) .getConfig() .getCommands() .size(), commandsSizeBefore + 1); verify(validator).validateConfig(workspace.getConfig()); verify(wsManager).updateWorkspace(any(), any()); } @Test public void shouldUpdateCommand() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); when(wsManager.updateWorkspace(any(), any())).thenReturn(workspace); final CommandDto commandDto = createCommandDto(); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(commandDto) .when() .put(SECURE_PATH + "/workspace/" + workspace.getId() + "/command/" + commandDto.getName()); assertEquals(response.getStatusCode(), 200); verify(validator).validateConfig(workspace.getConfig()); verify(wsManager).updateWorkspace(any(), any()); } @Test public void shouldRespond404WhenUpdatingCommandWhichDoesNotExist() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(createCommandDto()) .when() .put(SECURE_PATH + "/workspace/" + workspace.getId() + "/command/fake"); assertEquals(response.getStatusCode(), 404); assertEquals(unwrapError(response), "Workspace '" + workspace.getId() + "' doesn't contain command 'fake'"); verify(wsManager, never()).updateWorkspace(any(), any()); } @Test public void shouldDeleteCommand() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final int commandsSizeBefore = workspace.getConfig().getCommands().size(); final CommandImpl firstCommand = workspace.getConfig().getCommands().iterator().next(); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .delete(SECURE_PATH + "/workspace/" + workspace.getId() + "/command/" + firstCommand.getName()); assertEquals(response.getStatusCode(), 204); assertEquals(workspace.getConfig().getCommands().size(), commandsSizeBefore - 1); verify(wsManager).updateWorkspace(any(), any()); } @Test public void shouldAddEnvironment() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); when(wsManager.updateWorkspace(any(), any())).thenReturn(workspace); final EnvironmentDto envDto = createEnvDto(); final int envsSizeBefore = workspace.getConfig().getEnvironments().size(); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(envDto) .when() .queryParam("name", "new-env") .post(SECURE_PATH + "/workspace/" + workspace.getId() + "/environment"); assertEquals(response.getStatusCode(), 200); assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT) .getConfig() .getEnvironments() .size(), envsSizeBefore + 1); verify(validator).validateConfig(workspace.getConfig()); verify(wsManager).updateWorkspace(any(), any()); } @Test public void shouldUpdateEnvironment() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); when(wsManager.updateWorkspace(any(), any())).thenReturn(workspace); final EnvironmentDto envDto = createEnvDto(); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(envDto) .when() .put(SECURE_PATH + "/workspace/" + workspace.getId() + "/environment/" + workspace.getConfig().getDefaultEnv()); assertEquals(response.getStatusCode(), 200); assertEquals(workspace.getConfig().getEnvironments().size(), 1); verify(validator).validateConfig(workspace.getConfig()); verify(wsManager).updateWorkspace(any(), any()); } @Test public void shouldRespond404WhenUpdatingEnvironmentWhichDoesNotExist() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(createEnvDto()) .when() .put(SECURE_PATH + "/workspace/" + workspace.getId() + "/environment/fake"); assertEquals(response.getStatusCode(), 404); assertEquals(unwrapError(response), "Workspace '" + workspace.getId() + "' doesn't contain environment 'fake'"); verify(wsManager, never()).updateWorkspace(any(), any()); } @Test public void shouldDeleteEnvironment() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); Map.Entry<String, EnvironmentImpl> envEntry = workspace.getConfig().getEnvironments().entrySet().iterator().next(); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .delete(SECURE_PATH + "/workspace/" + workspace.getId() + "/environment/" + envEntry.getKey()); assertEquals(response.getStatusCode(), 204); verify(wsManager).updateWorkspace(any(), any()); } @Test public void shouldRelativizeLinksOnAddEnvironment() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); final String initialLocation = "http://localhost:8080/api/recipe/idrecipe123456789/script"; when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); when(wsManager.updateWorkspace(any(), any())).thenReturn(workspace); final EnvironmentDto envDto = createEnvDto(); envDto.getRecipe().withLocation(initialLocation).withType("dockerfile"); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(envDto) .when() .queryParam("name", "new-env") .post(SECURE_PATH + "/workspace/" + workspace.getId() + "/environment"); assertEquals(response.getStatusCode(), 200); String savedLocation = unwrapDto(response, WorkspaceDto.class).getConfig() .getEnvironments() .get("new-env") .getRecipe() .getLocation(); assertEquals(savedLocation, initialLocation.substring(API_ENDPOINT.length())); } @Test public void shouldAddProject() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); when(wsManager.updateWorkspace(any(), any())).thenReturn(workspace); final ProjectConfigDto projectDto = createProjectDto(); final int projectsSizeBefore = workspace.getConfig().getProjects().size(); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(projectDto) .when() .post(SECURE_PATH + "/workspace/" + workspace.getId() + "/project"); assertEquals(response.getStatusCode(), 200); assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT) .getConfig() .getProjects() .size(), projectsSizeBefore + 1); verify(validator).validateConfig(workspace.getConfig()); verify(wsManager).updateWorkspace(any(), any()); } @Test public void shouldUpdateProject() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); when(wsManager.updateWorkspace(any(), any())).thenReturn(workspace); final ProjectConfigDto projectDto = createProjectDto(); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(projectDto) .when() .put(SECURE_PATH + "/workspace/" + workspace.getId() + "/project" + projectDto.getPath()); assertEquals(response.getStatusCode(), 200); verify(validator).validateConfig(workspace.getConfig()); verify(wsManager).updateWorkspace(any(), any()); } @Test public void shouldRespond404WhenUpdatingProjectWhichDoesNotExist() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(createProjectDto()) .when() .put(SECURE_PATH + "/workspace/" + workspace.getId() + "/project/fake"); assertEquals(response.getStatusCode(), 404); assertEquals(unwrapError(response), "Workspace '" + workspace.getId() + "' doesn't contain project with path '/fake'"); verify(wsManager, never()).updateWorkspace(any(), any()); } @Test public void shouldDeleteProject() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final ProjectConfig firstProject = workspace.getConfig().getProjects().iterator().next(); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .delete(SECURE_PATH + "/workspace/" + workspace.getId() + "/project" + firstProject.getPath()); assertEquals(response.getStatusCode(), 204); verify(wsManager).updateWorkspace(any(), any()); } @Test public void testWorkspaceLinks() throws Exception { // given final WorkspaceImpl workspace = createWorkspace(createConfigDto()); EnvironmentImpl environment = workspace.getConfig() .getEnvironments() .get(workspace.getConfig().getDefaultEnv()); assertNotNull(environment); final WorkspaceRuntimeImpl runtime = new WorkspaceRuntimeImpl(workspace.getConfig().getDefaultEnv(), null); MachineConfigImpl devMachineConfig = MachineConfigImpl.builder() .setDev(true) .setEnvVariables(emptyMap()) .setServers(emptyList()) .setLimits(new MachineLimitsImpl(1024)) .setSource(new MachineSourceImpl("type").setContent("content")) .setName(environment.getMachines() .keySet() .iterator() .next()) .setType("type") .build(); runtime.setDevMachine(new MachineImpl(devMachineConfig, "machine123", workspace.getId(), workspace.getConfig().getDefaultEnv(), USER_ID, MachineStatus.RUNNING, new MachineRuntimeInfoImpl(emptyMap(), emptyMap(), singletonMap("8080/https", new ServerImpl( "wsagent", "https", "address", "url", new ServerPropertiesImpl( "path", "internaladdress", "internalurl")))))); runtime.getMachines().add(runtime.getDevMachine()); workspace.setStatus(RUNNING); workspace.setRuntime(runtime); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); // when final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/workspace/" + workspace.getId()); // then assertEquals(response.getStatusCode(), 200); final WorkspaceDto workspaceDto = unwrapDto(response, WorkspaceDto.class); final Set<String> actualRels = workspaceDto.getLinks() .stream() .map(Link::getRel) .collect(toSet()); final Set<String> expectedRels = new HashSet<>(asList(LINK_REL_START_WORKSPACE, LINK_REL_REMOVE_WORKSPACE, GET_ALL_USER_WORKSPACES, LINK_REL_GET_SNAPSHOT, LINK_REL_GET_WORKSPACE_EVENTS_CHANNEL, LINK_REL_IDE_URL, LINK_REL_SELF, LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL, LINK_REL_ENVIRONMENT_STATUS_CHANNEL)); assertTrue(actualRels.equals(expectedRels), format("Links difference: '%s'. \n" + "Returned links: '%s', \n" + "Expected links: '%s'.", Sets.symmetricDifference(actualRels, expectedRels), actualRels.toString(), expectedRels.toString())); assertNotNull(workspaceDto.getRuntime().getLink(LINK_REL_STOP_WORKSPACE), "Runtime doesn't contain stop link"); assertNotNull(workspaceDto.getRuntime().getLink(WSAGENT_REFERENCE), "Runtime doesn't contain wsagent link"); assertNotNull(workspaceDto.getRuntime().getLink(WSAGENT_WEBSOCKET_REFERENCE), "Runtime doesn't contain wsagent.websocket link"); } @Test public void shouldReturnSnapshotsOnGetSnapshot() throws Exception { // given String workspaceId = "testWsId1"; SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() .setCreationDate(System.currentTimeMillis()) .setDescription("description") .setDev(true) .setEnvName("envName") .setId("snap1") .setMachineName("machine1") .setMachineSource(new MachineSourceImpl("type") .setContent("content")) .setType("type") .setWorkspaceId(workspaceId); SnapshotImpl snapshot1 = snapshotBuilder.build(); SnapshotImpl snapshot2 = snapshotBuilder.setDev(false).build(); List<SnapshotImpl> originSnapshots = Arrays.asList(snapshot1, snapshot2); when(wsManager.getSnapshot(workspaceId)).thenReturn(originSnapshots); // when final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/workspace/" + workspaceId + "/snapshot"); // then assertEquals(response.getStatusCode(), 200); List<SnapshotDto> snapshotDtos = unwrapDtoList(response, SnapshotDto.class); List<SnapshotImpl> newSnapshots = snapshotDtos.stream().map(SnapshotImpl::new).collect(Collectors.toList()); originSnapshots.forEach(snapshot -> snapshot.setMachineSource(null)); assertEquals(newSnapshots, originSnapshots); verify(wsManager).getSnapshot(workspaceId); } @Test public void stateOfWsAgentShouldBeChecked() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); workspace.setStatus(RUNNING); WsAgentHealthStateDto wsAgentState = newDto(WsAgentHealthStateDto.class); WorkspaceRuntimeImpl runtime = mock(WorkspaceRuntimeImpl.class); MachineImpl machine = mock(MachineImpl.class); when(runtime.getDevMachine()).thenReturn(machine); when(wsAgentHealthChecker.check(machine)).thenReturn(wsAgentState); workspace.setRuntime(runtime); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/workspace/" + workspace.getId() + "/check"); verify(wsAgentHealthChecker).check(machine); assertEquals(RUNNING, wsAgentState.getWorkspaceStatus()); assertEquals(200, response.getStatusCode()); } @Test public void stateOfWsAgentShouldNotBeCheckedIfWsIsNotRunning() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); workspace.setStatus(STARTING); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/workspace/" + workspace.getId() + "/check"); verify(wsAgentHealthChecker, never()).check(any()); assertEquals(200, response.getStatusCode()); } @Test public void shouldReturnEmptyListIfNotSnapshotsFound() throws Exception { // given String workspaceId = "testWsId1"; when(wsManager.getSnapshot(workspaceId)).thenReturn(Collections.emptyList()); // when final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/workspace/" + workspaceId + "/snapshot"); // then assertEquals(response.getStatusCode(), 200); List<SnapshotDto> snapshotDtos = unwrapDtoList(response, SnapshotDto.class); assertTrue(snapshotDtos.isEmpty()); verify(wsManager).getSnapshot(workspaceId); } @Test public void shouldBeAbleToGetSettings() throws Exception { final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/workspace/settings"); assertEquals(response.getStatusCode(), 200); final Map<String, String> settings = new Gson().fromJson(response.print(), new TypeToken<Map<String, String>>() {}.getType()); assertEquals(settings, ImmutableMap.of("che.workspace.auto_snapshot", "true", // "che.workspace.auto_restore", "false", // "che.workspace.auto_start", "true")); } private static String unwrapError(Response response) { return unwrapDto(response, ServiceError.class).getMessage(); } private static <T> T unwrapDto(Response response, Class<T> dtoClass) { return DtoFactory.getInstance().createDtoFromJson(response.body().print(), dtoClass); } private static <T> List<T> unwrapDtoList(Response response, Class<T> dtoClass) { return DtoFactory.getInstance().createListDtoFromJson(response.body().print(), dtoClass) .stream() .collect(toList()); } private static WorkspaceImpl createWorkspace(WorkspaceConfig configDto, WorkspaceStatus status) { return WorkspaceImpl.builder() .setConfig(configDto) .generateId() .setAccount(TEST_ACCOUNT) .setStatus(status) .build(); } private static WorkspaceImpl createWorkspace(WorkspaceConfig configDto) { return createWorkspace(configDto, WorkspaceStatus.STOPPED); } private static CommandDto createCommandDto() { return DtoConverter.asDto(new CommandImpl("MCI", "mvn clean install", "maven")); } private static ProjectConfigDto createProjectDto() { return newDto(ProjectConfigDto.class).withName("project-name") .withPath("/project/path") .withDescription("Test project") .withMixins(new ArrayList<>(singleton("maven"))) .withSource(newDto(SourceStorageDto.class).withLocation("location") .withType("type")) .withAttributes(new HashMap<>()); } private static EnvironmentDto createEnvDto() { ExtendedMachineImpl devMachine = new ExtendedMachineImpl(singletonList("org.eclipse.che.ws-agent"), null, singletonMap("memoryLimitBytes", "10000")); return DtoConverter.asDto(new EnvironmentImpl(new EnvironmentRecipeImpl("type", "content-type", "content", null), singletonMap("dev-machine", devMachine))); } private static WorkspaceConfigDto createConfigDto() { final WorkspaceConfigImpl config = WorkspaceConfigImpl.builder() .setName("dev-workspace") .setDefaultEnv("dev-env") .setEnvironments(singletonMap("dev-env", new EnvironmentImpl(createEnvDto()))) .setCommands(singletonList(createCommandDto())) .setProjects(singletonList(createProjectDto())) .build(); return DtoConverter.asDto(config); } @Filter public static class EnvironmentFilter implements RequestFilter { public void doFilter(GenericContainerRequest request) { EnvironmentContext.getCurrent().setSubject(new SubjectImpl(NAMESPACE, USER_ID, "token", false)); } } }