/*******************************************************************************
* 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.user.server;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.jayway.restassured.response.Response;
import org.eclipse.che.account.api.AccountManager;
import org.eclipse.che.account.spi.AccountValidator;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.model.user.User;
import org.eclipse.che.api.core.rest.ApiExceptionMapper;
import org.eclipse.che.api.core.rest.shared.dto.ServiceError;
import org.eclipse.che.api.user.server.model.impl.UserImpl;
import org.eclipse.che.api.user.shared.dto.UserDto;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.Subject;
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.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
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.Map;
import static com.jayway.restassured.RestAssured.given;
import static java.util.Collections.emptyList;
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.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.testng.Assert.assertEquals;
/**
* Tests for {@link UserService}
*
* @author Eugene Veovodin
* @author Max Shaposhnik
*/
@Listeners({EverrestJetty.class, MockitoTestNGListener.class})
public class UserServiceTest {
@SuppressWarnings("unused")
private static final ApiExceptionMapper MAPPER = new ApiExceptionMapper();
@SuppressWarnings("unused")
private static final EnvironmentFilter FILTER = new EnvironmentFilter();
private static final Subject SUBJECT = new SubjectImpl("user", "user123", "token", false);
@Mock(answer = Answers.RETURNS_MOCKS)
private UserManager userManager;
@Mock
private AccountManager accountManager;
@Mock
private TokenValidator tokenValidator;
@Mock
private UserLinksInjector linksInjector;
private UserValidator userValidator;
@Captor
private ArgumentCaptor<User> userCaptor;
private UserService userService;
@BeforeMethod
public void initService() {
initMocks(this);
userValidator = new UserValidator(new AccountValidator(accountManager));
// Return the incoming instance when injectLinks is called
when(linksInjector.injectLinks(any(), any())).thenAnswer(inv -> inv.getArguments()[0]);
userService = new UserService(userManager, tokenValidator, userValidator, linksInjector, true);
}
@Test
public void shouldCreateUserFromToken() throws Exception {
when(tokenValidator.validateToken("token_value")).thenReturn(new UserImpl("id", "test@eclipse.org", "test"));
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.contentType("application/json")
.post(SECURE_PATH + "/user?token=token_value");
assertEquals(response.statusCode(), 201);
verify(userManager).create(userCaptor.capture(), anyBoolean());
final User user = userCaptor.getValue();
assertEquals(user.getEmail(), "test@eclipse.org");
assertEquals(user.getName(), "test");
}
@Test
public void shouldCreateUserFromEntity() throws Exception {
final UserDto newUser = newDto(UserDto.class).withName("test")
.withEmail("test@codenvy.com")
.withPassword("password12345");
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.body(newUser)
.contentType("application/json")
.post(SECURE_PATH + "/user");
assertEquals(response.statusCode(), 201);
verify(userManager).create(userCaptor.capture(), anyBoolean());
final User user = userCaptor.getValue();
assertEquals(user.getEmail(), "test@codenvy.com");
assertEquals(user.getName(), "test");
assertEquals(user.getPassword(), "password12345");
}
@Test
public void shouldNotCreateUserFromEntityWhenPasswordIsNotValid() throws Exception {
final UserDto newUser = newDto(UserDto.class).withName("test")
.withEmail("test@codenvy.com")
.withPassword("1");
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.body(newUser)
.contentType("application/json")
.post(SECURE_PATH + "/user");
assertEquals(response.statusCode(), 400);
assertEquals(unwrapError(response), "Password should contain at least 8 characters");
}
@Test
public void shouldNotCreateUserIfTokenIsNotValid() throws Exception {
when(tokenValidator.validateToken("token_value")).thenThrow(new ConflictException("error"));
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.contentType("application/json")
.post(SECURE_PATH + "/user?token=token_value");
assertEquals(response.statusCode(), 409);
assertEquals(unwrapError(response), "error");
}
@Test
public void shouldNotCreateUserFromEntityIfEntityIsNull() throws Exception {
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.contentType("application/json")
.post(SECURE_PATH + "/user");
assertEquals(response.statusCode(), 400);
assertEquals(unwrapError(response), "User required");
}
@Test
public void shouldNotCreateUserFromEntityIfEmailIsNull() throws Exception {
final UserDto newUser = newDto(UserDto.class).withName("test")
.withPassword("password12345");
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.body(newUser)
.contentType("application/json")
.post(SECURE_PATH + "/user");
assertEquals(response.statusCode(), 400);
assertEquals(unwrapError(response), "User email required");
}
@Test
public void shouldNotCreateUserFromEntityIfNameIsNull() throws Exception {
final UserDto newUser = newDto(UserDto.class).withEmail("test@codenvy.com")
.withPassword("password12345");
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.body(newUser)
.contentType("application/json")
.post(SECURE_PATH + "/user");
assertEquals(response.statusCode(), 400);
assertEquals(unwrapError(response), "User name required");
}
@Test
public void shouldNotCreateUserFromEntityIfPasswordIsNotValid() throws Exception {
final UserDto newUser = newDto(UserDto.class).withEmail("test@codenvy.com")
.withName("test")
.withPassword("1");
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.body(newUser)
.contentType("application/json")
.post(SECURE_PATH + "/user");
assertEquals(response.statusCode(), 400);
assertEquals(unwrapError(response), "Password should contain at least 8 characters");
}
@Test
public void shouldBeAbleToGetCurrentUser() throws Exception {
when(userManager.getById(SUBJECT.getUserId())).thenReturn(copySubject());
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.get(SECURE_PATH + "/user");
assertEquals(response.getStatusCode(), 200);
}
@Test
public void shouldBeAbleToGetUserById() throws Exception {
final UserImpl testUser = copySubject();
when(userManager.getById(SUBJECT.getUserId())).thenReturn(testUser);
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.get(SECURE_PATH + "/user/" + SUBJECT.getUserId());
assertEquals(response.getStatusCode(), 200);
final UserDto fetchedUser = unwrapDto(response, UserDto.class);
assertEquals(fetchedUser.getId(), testUser.getId());
assertEquals(fetchedUser.getName(), testUser.getName());
assertEquals(fetchedUser.getEmail(), testUser.getEmail());
}
@Test
public void shouldBeAbleToFindUserByEmail() throws Exception {
final UserImpl testUser = copySubject();
when(userManager.getByEmail(testUser.getEmail())).thenReturn(testUser);
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.get(SECURE_PATH + "/user/find?email=" + testUser.getEmail());
assertEquals(response.getStatusCode(), 200);
final UserDto fetchedUser = unwrapDto(response, UserDto.class);
assertEquals(fetchedUser.getId(), testUser.getId());
assertEquals(fetchedUser.getName(), testUser.getName());
assertEquals(fetchedUser.getEmail(), testUser.getEmail());
}
@Test
public void shouldBeAbleToFindUserByName() throws Exception {
final UserImpl testUser = copySubject();
when(userManager.getByName(testUser.getName())).thenReturn(testUser);
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.get(SECURE_PATH + "/user/find?name=" + testUser.getName());
assertEquals(response.getStatusCode(), 200);
final UserDto fetchedUser = unwrapDto(response, UserDto.class);
assertEquals(fetchedUser.getId(), testUser.getId());
assertEquals(fetchedUser.getName(), testUser.getName());
assertEquals(fetchedUser.getEmail(), testUser.getEmail());
}
@Test
public void shouldNotFindUserByNameOrEmailWhenBothSpecified() throws Exception {
final UserImpl testUser = copySubject();
when(userManager.getByName(testUser.getName())).thenReturn(testUser);
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.get(SECURE_PATH + "/user/find?" +
"name=" + testUser.getName() +
"&email=" + testUser.getEmail());
assertEquals(response.getStatusCode(), 400);
assertEquals(unwrapError(response), "Expected either user's email or name, while both values received");
}
@Test
public void shouldNotFindUserByNameOrEmailWhenBothAreEmpty() throws Exception {
final UserImpl testUser = copySubject();
when(userManager.getByName(testUser.getName())).thenReturn(testUser);
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.get(SECURE_PATH + "/user/find");
assertEquals(response.getStatusCode(), 400);
assertEquals(unwrapError(response), "Missed user's email or name");
}
@Test
public void shouldUpdatePassword() throws Exception {
final UserImpl testUser = copySubject();
when(userManager.getById(testUser.getId())).thenReturn(testUser);
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.contentType("application/x-www-form-urlencoded")
.body("password=password12345")
.when()
.post(SECURE_PATH + "/user/password");
verify(userManager).update(userCaptor.capture());
final User fetchedUser = userCaptor.getValue();
assertEquals(fetchedUser.getPassword(), "password12345");
}
@Test
public void shouldNotUpdatePasswordIfPasswordContainsOnlyDigits() throws Exception {
final UserImpl testUser = copySubject();
when(userManager.getById(testUser.getId())).thenReturn(testUser);
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.contentType("application/x-www-form-urlencoded")
.body("password=1234567890")
.when()
.post(SECURE_PATH + "/user/password");
assertEquals(response.getStatusCode(), 400);
assertEquals(unwrapError(response), "Password should contain letters and digits");
}
@Test
public void shouldNotUpdatePasswordIfPasswordContainsLessThan8Chars() throws Exception {
final UserImpl testUser = copySubject();
when(userManager.getById(testUser.getId())).thenReturn(testUser);
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.contentType("application/x-www-form-urlencoded")
.body("password=0xf")
.when()
.post(SECURE_PATH + "/user/password");
assertEquals(response.getStatusCode(), 400);
assertEquals(unwrapError(response), "Password should contain at least 8 characters");
}
@Test
public void shouldNotUpdatePasswordIfPasswordIsNull() throws Exception {
final UserImpl testUser = copySubject();
when(userManager.getById(testUser.getId())).thenReturn(testUser);
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.contentType("application/x-www-form-urlencoded")
.when()
.post(SECURE_PATH + "/user/password");
assertEquals(response.getStatusCode(), 400);
assertEquals(unwrapError(response), "Password required");
}
@Test
public void shouldRemoveUser() throws Exception {
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.delete(SECURE_PATH + "/user/" + SUBJECT.getUserId());
assertEquals(response.getStatusCode(), 204);
verify(userManager).remove(SUBJECT.getUserId());
}
@Test
public void shouldBeAbleToGetSettings() throws Exception {
final Response response = given().auth()
.basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
.when()
.get(SECURE_PATH + "/user/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.auth.user_self_creation", "true"));
}
@Filter
public static class EnvironmentFilter implements RequestFilter {
public void doFilter(GenericContainerRequest request) {
EnvironmentContext.getCurrent().setSubject(SUBJECT);
}
}
private static <T> T unwrapDto(Response response, Class<T> dtoClass) {
return DtoFactory.getInstance().createDtoFromJson(response.body().print(), dtoClass);
}
private static String unwrapError(Response response) {
return unwrapDto(response, ServiceError.class).getMessage();
}
private static UserImpl copySubject() {
return new UserImpl(SUBJECT.getUserId(),
SUBJECT.getUserName() + "@codenvy.com",
SUBJECT.getUserName(),
null,
emptyList());
}
}