]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7907 It should be possible to add SCM account that contains comma
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 15 Sep 2016 16:37:18 +0000 (18:37 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 16 Sep 2016 13:15:30 +0000 (15:15 +0200)
20 files changed:
server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/UpdateAction.java
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/GroupsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java
sonar-db/src/main/java/org/sonar/db/user/UserDto.java
sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java
sonar-db/src/test/java/org/sonar/db/user/UserTesting.java
sonar-ws/src/main/java/org/sonarqube/ws/client/user/CreateRequest.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/user/UpdateRequest.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/user/UserService.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersWsParameters.java [new file with mode: 0644]
sonar-ws/src/test/java/org/sonarqube/ws/client/user/CreateRequestTest.java [new file with mode: 0644]
sonar-ws/src/test/java/org/sonarqube/ws/client/user/UpdateRequestTest.java [new file with mode: 0644]
sonar-ws/src/test/java/org/sonarqube/ws/client/user/UserServiceTest.java [new file with mode: 0644]

index b5a7cfb722944214b9efeebb9d7201b0a2c140f7..5c52cef1c238f6e5ec8e5a280f30b735db4d9cb3 100644 (file)
@@ -21,10 +21,10 @@ package org.sonar.server.user;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import java.net.HttpURLConnection;
 import java.security.SecureRandom;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
 import javax.annotation.CheckForNull;
@@ -35,6 +35,7 @@ import org.sonar.api.config.Settings;
 import org.sonar.api.platform.NewUserHandler;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.System2;
+import org.sonar.core.util.stream.Collectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.user.GroupDto;
@@ -353,9 +354,9 @@ public class UserUpdater {
   @CheckForNull
   private static List<String> sanitizeScmAccounts(@Nullable List<String> scmAccounts) {
     if (scmAccounts != null) {
-      scmAccounts.removeAll(Arrays.asList(null, ""));
+      return scmAccounts.stream().filter(s -> !Strings.isNullOrEmpty(s)).collect(Collectors.toList());
     }
-    return scmAccounts;
+    return null;
   }
 
   private void saveUser(DbSession dbSession, UserDto userDto) {
index 78a47459faff7838e35006581a84b50e7ae77d49..caa1ec13598faca1143543dcc2e9d44f6838b784 100644 (file)
@@ -20,6 +20,8 @@
 package org.sonar.server.user.ws;
 
 import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import java.util.List;
 import org.sonar.api.i18n.I18n;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
@@ -32,15 +34,17 @@ import org.sonar.db.user.UserDto;
 import org.sonar.server.user.NewUser;
 import org.sonar.server.user.UserSession;
 import org.sonar.server.user.UserUpdater;
+import org.sonarqube.ws.client.user.CreateRequest;
 
-public class CreateAction implements UsersWsAction {
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_EMAIL;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOGIN;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_NAME;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_PASSWORD;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_SCM_ACCOUNT;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_SCM_ACCOUNTS;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_SCM_ACCOUNTS_DEPRECATED;
 
-  private static final String PARAM_LOGIN = "login";
-  private static final String PARAM_PASSWORD = "password";
-  private static final String PARAM_NAME = "name";
-  private static final String PARAM_EMAIL = "email";
-  private static final String PARAM_SCM_ACCOUNTS = "scmAccounts";
-  private static final String PARAM_SCM_ACCOUNTS_DEPRECATED = "scm_accounts";
+public class CreateAction implements UsersWsAction {
 
   private final DbClient dbClient;
   private final UserUpdater userUpdater;
@@ -85,26 +89,31 @@ public class CreateAction implements UsersWsAction {
       .setExampleValue("myname@email.com");
 
     action.createParam(PARAM_SCM_ACCOUNTS)
-      .setDescription("SCM accounts. This parameter has been added in 5.1")
+      .setDescription("This parameter is deprecated, please use '%s' instead", PARAM_SCM_ACCOUNT)
       .setDeprecatedKey(PARAM_SCM_ACCOUNTS_DEPRECATED)
+      .setDeprecatedSince("6.1")
       .setExampleValue("myscmaccount1,myscmaccount2");
+
+    action.createParam(PARAM_SCM_ACCOUNT)
+      .setDescription("SCM accounts. To set several values, the parameter must be called once for each value.")
+      .setExampleValue("scmAccount=firstValue&scmAccount=secondValue&scmAccount=thirdValue");
   }
 
   @Override
   public void handle(Request request, Response response) throws Exception {
     userSession.checkLoggedIn().checkPermission(GlobalPermissions.SYSTEM_ADMIN);
+    doHandle(toWsRequest(request), response);
+  }
 
-    String login = request.mandatoryParam(PARAM_LOGIN);
-    String password = request.mandatoryParam(PARAM_PASSWORD);
+  private void doHandle(CreateRequest request, Response response) {
     NewUser newUser = NewUser.create()
-      .setLogin(login)
-      .setName(request.mandatoryParam(PARAM_NAME))
-      .setEmail(request.param(PARAM_EMAIL))
-      .setScmAccounts(request.paramAsStrings(PARAM_SCM_ACCOUNTS))
-      .setPassword(password);
-
+      .setLogin(request.getLogin())
+      .setName(request.getName())
+      .setEmail(request.getEmail())
+      .setScmAccounts(request.getScmAccounts())
+      .setPassword(request.getPassword());
     boolean isUserReactivated = userUpdater.create(newUser);
-    writeResponse(response, login, isUserReactivated);
+    writeResponse(response, request.getLogin(), isUserReactivated);
   }
 
   private void writeResponse(Response response, String login, boolean isUserReactivated) {
@@ -119,7 +128,7 @@ public class CreateAction implements UsersWsAction {
 
   private void writeUser(JsonWriter json, UserDto user) {
     json.name("user");
-    userWriter.write(json, user, ImmutableSet.<String>of(), UserJsonWriter.FIELDS);
+    userWriter.write(json, user, ImmutableSet.of(), UserJsonWriter.FIELDS);
   }
 
   private void writeReactivationMessage(JsonWriter json, String login) {
@@ -131,7 +140,7 @@ public class CreateAction implements UsersWsAction {
     json.endArray();
   }
 
-  private UserDto loadUser(String login){
+  private UserDto loadUser(String login) {
     DbSession dbSession = dbClient.openSession(false);
     try {
       return dbClient.userDao().selectOrFailByLogin(dbSession, login);
@@ -139,4 +148,22 @@ public class CreateAction implements UsersWsAction {
       dbClient.closeSession(dbSession);
     }
   }
+
+  private static CreateRequest toWsRequest(Request request) {
+    return CreateRequest.builder()
+      .setLogin(request.mandatoryParam(PARAM_LOGIN))
+      .setPassword(request.mandatoryParam(PARAM_PASSWORD))
+      .setName(request.param(PARAM_NAME))
+      .setEmail(request.param(PARAM_EMAIL))
+      .setScmAccounts(getScmAccounts(request))
+      .build();
+  }
+
+  private static List<String> getScmAccounts(Request request) {
+    if (request.hasParam(PARAM_SCM_ACCOUNT)) {
+      return request.multiParam(PARAM_SCM_ACCOUNT);
+    }
+    List<String> oldScmAccounts = request.paramAsStrings(PARAM_SCM_ACCOUNTS);
+    return oldScmAccounts != null ? oldScmAccounts : Collections.emptyList();
+  }
 }
index 9da94fae484bf5895d48194f3d8947b6f8a91e6e..362b5fe19a260c9b39e4def8e6b058141876e464 100644 (file)
@@ -20,6 +20,8 @@
 package org.sonar.server.user.ws;
 
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
@@ -33,19 +35,21 @@ import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.user.UpdateUser;
 import org.sonar.server.user.UserSession;
 import org.sonar.server.user.UserUpdater;
+import org.sonarqube.ws.client.user.UpdateRequest;
 
 import static com.google.common.base.Strings.emptyToNull;
 import static java.lang.String.format;
 import static java.util.Collections.singletonList;
+import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_UPDATE;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_EMAIL;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOGIN;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_NAME;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_SCM_ACCOUNT;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_SCM_ACCOUNTS;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_SCM_ACCOUNTS_DEPRECATED;
 
 public class UpdateAction implements UsersWsAction {
 
-  private static final String PARAM_LOGIN = "login";
-  private static final String PARAM_NAME = "name";
-  private static final String PARAM_EMAIL = "email";
-  private static final String PARAM_SCM_ACCOUNTS = "scmAccounts";
-  private static final String PARAM_SCM_ACCOUNTS_DEPRECATED = "scm_accounts";
-
   private final UserUpdater userUpdater;
   private final UserSession userSession;
   private final UserJsonWriter userWriter;
@@ -60,7 +64,7 @@ public class UpdateAction implements UsersWsAction {
 
   @Override
   public void define(WebService.NewController controller) {
-    WebService.NewAction action = controller.createAction("update")
+    WebService.NewAction action = controller.createAction(ACTION_UPDATE)
       .setDescription("Update a user. If a deactivated user account exists with the given login, it will be reactivated. " +
         "Requires Administer System permission. Since 5.2, a user's password can only be changed using the 'change_password' action.")
       .setSince("3.7")
@@ -82,29 +86,37 @@ public class UpdateAction implements UsersWsAction {
       .setExampleValue("myname@email.com");
 
     action.createParam(PARAM_SCM_ACCOUNTS)
-      .setDescription("SCM accounts. This parameter has been added in 5.1")
+      .setDescription("This parameter is deprecated, please use '%s' instead", PARAM_SCM_ACCOUNT)
       .setDeprecatedKey(PARAM_SCM_ACCOUNTS_DEPRECATED)
+      .setDeprecatedSince("6.1")
       .setExampleValue("myscmaccount1,myscmaccount2");
+
+    action.createParam(PARAM_SCM_ACCOUNT)
+      .setDescription("SCM accounts. To set several values, the parameter must be called once for each value.")
+      .setExampleValue("scmAccount=firstValue&scmAccount=secondValue&scmAccount=thirdValue");
   }
 
   @Override
   public void handle(Request request, Response response) throws Exception {
     userSession.checkLoggedIn().checkPermission(GlobalPermissions.SYSTEM_ADMIN);
+    UpdateRequest updateRequest = toWsRequest(request);
+    doHandle(toWsRequest(request));
+    writeResponse(response, updateRequest.getLogin());
+  }
 
-    String login = request.mandatoryParam(PARAM_LOGIN);
+  private void doHandle(UpdateRequest request) {
+    String login = request.getLogin();
     UpdateUser updateUser = UpdateUser.create(login);
-    if (request.hasParam(PARAM_NAME)) {
-      updateUser.setName(request.mandatoryParam(PARAM_NAME));
+    if (request.getName() != null) {
+      updateUser.setName(request.getName());
     }
-    if (request.hasParam(PARAM_EMAIL)) {
-      updateUser.setEmail(emptyToNull(request.param(PARAM_EMAIL)));
+    if (request.getEmail() != null) {
+      updateUser.setEmail(emptyToNull(request.getEmail()));
     }
-    if (request.hasParam(PARAM_SCM_ACCOUNTS) || request.hasParam(PARAM_SCM_ACCOUNTS_DEPRECATED)) {
-      updateUser.setScmAccounts(request.paramAsStrings(PARAM_SCM_ACCOUNTS));
+    if (!request.getScmAccounts().isEmpty()) {
+      updateUser.setScmAccounts(request.getScmAccounts());
     }
-
     userUpdater.update(updateUser);
-    writeResponse(response, login);
   }
 
   private void writeResponse(Response response, String login) {
@@ -128,4 +140,21 @@ public class UpdateAction implements UsersWsAction {
       dbClient.closeSession(dbSession);
     }
   }
+
+  private static UpdateRequest toWsRequest(Request request) {
+    return UpdateRequest.builder()
+      .setLogin(request.mandatoryParam(PARAM_LOGIN))
+      .setName(request.param(PARAM_NAME))
+      .setEmail(request.param(PARAM_EMAIL))
+      .setScmAccounts(getScmAccounts(request))
+      .build();
+  }
+
+  private static List<String> getScmAccounts(Request request) {
+    if (request.hasParam(PARAM_SCM_ACCOUNT)) {
+      return new ArrayList<>(request.multiParam(PARAM_SCM_ACCOUNT));
+    }
+    List<String> oldScmAccounts = request.paramAsStrings(PARAM_SCM_ACCOUNTS);
+    return oldScmAccounts != null ? oldScmAccounts : new ArrayList<>();
+  }
 }
index fbb906739e6389f9700e09a7d24d0f4e1414c60b..df4c1fcfb2cdc7d017f9e6c841c9b1fd85701baf 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.user;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.elasticsearch.search.SearchHit;
 import org.junit.Before;
@@ -106,7 +107,7 @@ public class UserUpdaterTest {
       .setName("User")
       .setEmail("user@mail.com")
       .setPassword("PASSWORD")
-      .setScmAccounts(newArrayList("u1", "u_1", "User 1")));
+      .setScmAccounts(ImmutableList.of("u1", "u_1", "User 1")));
 
     UserDto dto = userDao.selectByLogin(session, "user");
     assertThat(dto.getId()).isNotNull();
index 9ef9707fe16a2471a04ece3f023c3aa41aad0920..962ca17728f6f916694426c59c9a0b3352e13d50 100644 (file)
@@ -24,8 +24,8 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.config.Settings;
 import org.sonar.api.config.MapSettings;
+import org.sonar.api.config.Settings;
 import org.sonar.api.i18n.I18n;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.System2;
@@ -112,7 +112,7 @@ public class CreateActionTest {
       .setParam("login", "john")
       .setParam("name", "John")
       .setParam("email", "john@email.com")
-      .setParam("scmAccounts", "jn")
+      .setParam("scmAccount", "jn")
       .setParam("password", "1234").execute()
       .assertJson(getClass(), "create_user.json");
 
@@ -124,7 +124,38 @@ public class CreateActionTest {
   }
 
   @Test
-  public void create_user_with_deprecated_scm_parameter() throws Exception {
+  public void create_user_with_coma_in_scm_account() throws Exception {
+    userSessionRule.login("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+
+    tester.newPostRequest("api/users", "create")
+      .setParam("login", "john")
+      .setParam("name", "John")
+      .setParam("email", "john@email.com")
+      .setParam("scmAccount", "j,n")
+      .setParam("password", "1234").execute();
+
+    UserDoc user = index.getNullableByLogin("john");
+    assertThat(user.scmAccounts()).containsOnly("j,n");
+  }
+
+  @Test
+  public void create_user_with_deprecated_scmAccounts_parameter() throws Exception {
+    userSessionRule.login("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+
+    tester.newPostRequest("api/users", "create")
+      .setParam("login", "john")
+      .setParam("name", "John")
+      .setParam("email", "john@email.com")
+      .setParam("scmAccounts", "jn")
+      .setParam("password", "1234").execute()
+      .assertJson(getClass(), "create_user.json");
+
+    UserDoc user = index.getNullableByLogin("john");
+    assertThat(user.scmAccounts()).containsOnly("jn");
+  }
+
+  @Test
+  public void create_user_with_deprecated_scm_accounts_parameter() throws Exception {
     userSessionRule.login("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
 
     tester.newPostRequest("api/users", "create")
@@ -136,9 +167,6 @@ public class CreateActionTest {
       .assertJson(getClass(), "create_user.json");
 
     UserDoc user = index.getNullableByLogin("john");
-    assertThat(user.login()).isEqualTo("john");
-    assertThat(user.name()).isEqualTo("John");
-    assertThat(user.email()).isEqualTo("john@email.com");
     assertThat(user.scmAccounts()).containsOnly("jn");
   }
 
index 925c2767fec651af1e149a41bfea53d8abd7e3bc..6b7650b5584dfa211a6e53e2e934d29da8fc0e90 100644 (file)
@@ -22,8 +22,8 @@ package org.sonar.server.user.ws;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.config.Settings;
 import org.sonar.api.config.MapSettings;
+import org.sonar.api.config.Settings;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.System2;
 import org.sonar.core.permission.GlobalPermissions;
@@ -47,6 +47,7 @@ import org.sonar.server.user.index.UserIndexDefinition;
 import org.sonar.server.user.index.UserIndexer;
 import org.sonar.server.ws.WsTester;
 
+import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.sonar.db.user.UserTokenTesting.newUserToken;
@@ -136,7 +137,7 @@ public class DeactivateActionTest {
       .setEmail("john@email.com")
       .setLogin("john")
       .setName("John")
-      .setScmAccounts("jn"));
+      .setScmAccounts(singletonList("jn")));
     dbClient.userTokenDao().insert(dbSession, newUserToken().setLogin("john"));
     dbSession.commit();
     userIndexer.index();
index ec198c0f995ebbbc787d6784c03c7571c6d5712e..f70f4d8da6301ef8965de75ed7c047b5e297bb46 100644 (file)
@@ -42,6 +42,8 @@ import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsTester;
 
+import static java.util.Collections.singletonList;
+
 public class GroupsActionTest {
 
   @Rule
@@ -154,7 +156,7 @@ public class GroupsActionTest {
       .setEmail("john@email.com")
       .setLogin("john")
       .setName("John")
-      .setScmAccounts("jn"));
+      .setScmAccounts(singletonList("jn")));
   }
 
   @Test
index 91cbb2d30644837adca0f719cb50728958af95ca..8884751a7b8297de2818f033b05817ab1ce2f582 100644 (file)
@@ -122,9 +122,7 @@ public class SearchActionTest {
   public void search_with_query() throws Exception {
     loginAsSimpleUser();
     injectUsers(5);
-    UserDto user = userDb.insertUser(
-      newUserDto("user-%_%-login", "user-name", "user@mail.com")
-        .setScmAccounts("user1"));
+    UserDto user = userDb.insertUser(newUserDto("user-%_%-login", "user-name", "user@mail.com").setScmAccounts(singletonList("user1")));
     esTester.putDocuments(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER,
       new UserDoc()
         .setActive(true)
index 95c527dd96f2070e212beca4067bd747389d2211..2a329a49b744fdf2fb50e3f3d40f3273274b4eb2 100644 (file)
@@ -23,8 +23,8 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.config.Settings;
 import org.sonar.api.config.MapSettings;
+import org.sonar.api.config.Settings;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.System2;
 import org.sonar.core.permission.GlobalPermissions;
@@ -147,19 +147,62 @@ public class UpdateActionTest {
     assertThat(userDto.getEmail()).isNull();
   }
 
+  @Test
+  public void remove_scm_accounts() throws Exception {
+    createUser();
+
+    tester.newPostRequest("api/users", "update")
+      .setParam("login", "john")
+      .setParam("scmAccount", "")
+      .execute();
+
+    UserDto userDto = dbClient.userDao().selectByLogin(session, "john");
+    assertThat(userDto.getScmAccounts()).isNull();
+  }
+
   @Test
   public void update_only_scm_accounts() throws Exception {
     createUser();
 
+    tester.newPostRequest("api/users", "update")
+      .setParam("login", "john")
+      .setParam("scmAccount", "jon.snow")
+      .execute()
+      .assertJson(getClass(), "update_scm_accounts.json");
+
+    UserDto user = dbClient.userDao().selectByLogin(session, "john");
+    assertThat(user.getScmAccountsAsList()).containsOnly("jon.snow");
+  }
+
+  @Test
+  public void update_scm_account_having_coma() throws Exception {
+    createUser();
+
+    tester.newPostRequest("api/users", "update")
+      .setParam("login", "john")
+      .setParam("scmAccount", "jon,snow")
+      .execute();
+
+    UserDto user = dbClient.userDao().selectByLogin(session, "john");
+    assertThat(user.getScmAccountsAsList()).containsOnly("jon,snow");
+  }
+
+  @Test
+  public void update_only_scm_accounts_with_deprecated_scmAccounts_parameter() throws Exception {
+    createUser();
+
     tester.newPostRequest("api/users", "update")
       .setParam("login", "john")
       .setParam("scmAccounts", "jon.snow")
       .execute()
       .assertJson(getClass(), "update_scm_accounts.json");
+
+    UserDto user = dbClient.userDao().selectByLogin(session, "john");
+    assertThat(user.getScmAccountsAsList()).containsOnly("jon.snow");
   }
 
   @Test
-  public void update_only_scm_accounts_with_deprecated_parameter() throws Exception {
+  public void update_only_scm_accounts_with_deprecated_scm_accounts_parameter() throws Exception {
     createUser();
 
     tester.newPostRequest("api/users", "update")
@@ -167,6 +210,9 @@ public class UpdateActionTest {
       .setParam("scm_accounts", "jon.snow")
       .execute()
       .assertJson(getClass(), "update_scm_accounts.json");
+
+    UserDto user = dbClient.userDao().selectByLogin(session, "john");
+    assertThat(user.getScmAccountsAsList()).containsOnly("jon.snow");
   }
 
   @Test(expected = NotFoundException.class)
index 58c2963e3896949e783eb587b2eafe57809c5e73..843437efb0f558d0befa74f40500a4e56234686a 100644 (file)
@@ -72,7 +72,7 @@ public class UsersWsTest {
     WebService.Action action = controller.action("create");
     assertThat(action).isNotNull();
     assertThat(action.isPost()).isTrue();
-    assertThat(action.params()).hasSize(5);
+    assertThat(action.params()).hasSize(6);
   }
 
   @Test
@@ -80,7 +80,7 @@ public class UsersWsTest {
     WebService.Action action = controller.action("update");
     assertThat(action).isNotNull();
     assertThat(action.isPost()).isTrue();
-    assertThat(action.params()).hasSize(4);
+    assertThat(action.params()).hasSize(5);
   }
 
   @Test
index 9b83805dbaad7e21ff6ea143f1cdd16cb0b607a0..826317bcc8701458d6d64ef1189e612f446dc554 100644 (file)
@@ -19,8 +19,6 @@
  */
 package org.sonar.db.user;
 
-import static java.util.Objects.requireNonNull;
-
 import com.google.common.base.Splitter;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
@@ -31,6 +29,8 @@ import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.core.user.DefaultUser;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * @since 3.2
  */
@@ -110,9 +110,6 @@ public class UserDto {
     return decodeScmAccounts(scmAccounts);
   }
 
-  /**
-   * List of SCM accounts separated by '|'
-   */
   public UserDto setScmAccounts(@Nullable String s) {
     this.scmAccounts = s;
     return this;
index 6e5a916033c940dfecd40b2625440d5b1de23dde..cd28421be2440419b94a07e232ad8c4570bc3c34 100644 (file)
  */
 package org.sonar.db.user;
 
-import static java.util.Arrays.asList;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.user.GroupMembershipQuery.IN;
-import static org.sonar.db.user.GroupMembershipQuery.builder;
-import static org.sonar.db.user.UserTesting.newUserDto;
-
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -52,6 +42,16 @@ import org.sonar.db.measure.MeasureFilterFavouriteDto;
 import org.sonar.db.property.PropertyDto;
 import org.sonar.db.property.PropertyQuery;
 
+import static java.util.Arrays.asList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.user.GroupMembershipQuery.IN;
+import static org.sonar.db.user.GroupMembershipQuery.builder;
+import static org.sonar.db.user.UserTesting.newUserDto;
+
 public class UserDaoTest {
 
   @Rule
index b2f74af3c5c440006b6652142ef0e893a0bd9a2e..cdf891465f4c1e19f634f77414068d69c45c1cfe 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.db.user;
 
+import static java.util.Collections.singletonList;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.apache.commons.lang.math.RandomUtils.nextLong;
 
@@ -35,7 +36,7 @@ public class UserTesting {
       .setName(name)
       .setEmail(email)
       .setLogin(login)
-      .setScmAccounts(randomAlphanumeric(40))
+      .setScmAccounts(singletonList(randomAlphanumeric(40)))
       .setExternalIdentity(login)
       .setExternalIdentityProvider("sonarqube")
       .setSalt(randomAlphanumeric(40))
@@ -51,7 +52,7 @@ public class UserTesting {
       .setName(name)
       .setEmail(email)
       .setLogin(login)
-      .setScmAccounts(randomAlphanumeric(40))
+      .setScmAccounts(singletonList(randomAlphanumeric(40)))
       .setExternalIdentity(login)
       .setExternalIdentityProvider("sonarqube")
       .setSalt(randomAlphanumeric(40))
@@ -67,7 +68,7 @@ public class UserTesting {
       .setName(name)
       .setEmail(email)
       .setLogin(login)
-      .setScmAccounts(randomAlphanumeric(40))
+      .setScmAccounts(singletonList(randomAlphanumeric(40)))
       .setExternalIdentity(randomAlphanumeric(40))
       .setExternalIdentityProvider(randomAlphanumeric(40))
       .setCreatedAt(nextLong())
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/CreateRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/CreateRequest.java
new file mode 100644 (file)
index 0000000..4144497
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.user;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.util.Collections.emptyList;
+
+public class CreateRequest {
+
+  private final String login;
+  private final String password;
+  private final String name;
+  private final String email;
+  private final List<String> scmAccounts;
+
+  public CreateRequest(Builder builder) {
+    this.login = builder.login;
+    this.password = builder.password;
+    this.name = builder.name;
+    this.email = builder.email;
+    this.scmAccounts = builder.scmAccounts;
+  }
+
+  public String getLogin() {
+    return login;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  @CheckForNull
+  public String getEmail() {
+    return email;
+  }
+
+  public List<String> getScmAccounts() {
+    return scmAccounts;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private String login;
+    private String password;
+    private String name;
+    private String email;
+    private List<String> scmAccounts = emptyList();
+
+    private Builder() {
+      // enforce factory method use
+    }
+
+    public Builder setLogin(String login) {
+      this.login = login;
+      return this;
+    }
+
+    public Builder setPassword(String password) {
+      this.password = password;
+      return this;
+    }
+
+    public Builder setName(String name) {
+      this.name = name;
+      return this;
+    }
+
+    public Builder setEmail(@Nullable String email) {
+      this.email = email;
+      return this;
+    }
+
+    public Builder setScmAccounts(List<String> scmAccounts) {
+      this.scmAccounts = scmAccounts;
+      return this;
+    }
+
+    public CreateRequest build() {
+      checkArgument(!isNullOrEmpty(login), "Login is mandatory and must not be empty");
+      checkArgument(!isNullOrEmpty(password), "Password is mandatory and must not be empty");
+      checkArgument(!isNullOrEmpty(name), "Name is mandatory and must not be empty");
+      return new CreateRequest(this);
+    }
+  }
+}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UpdateRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UpdateRequest.java
new file mode 100644 (file)
index 0000000..87757cc
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.user;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.util.Collections.emptyList;
+
+public class UpdateRequest {
+
+  private final String login;
+  private final String name;
+  private final String email;
+  private final List<String> scmAccounts;
+
+  public UpdateRequest(Builder builder) {
+    this.login = builder.login;
+    this.name = builder.name;
+    this.email = builder.email;
+    this.scmAccounts = builder.scmAccounts;
+  }
+
+  public String getLogin() {
+    return login;
+  }
+
+  @CheckForNull
+  public String getName() {
+    return name;
+  }
+
+  @CheckForNull
+  public String getEmail() {
+    return email;
+  }
+
+  public List<String> getScmAccounts() {
+    return scmAccounts;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private String login;
+    private String name;
+    private String email;
+    private List<String> scmAccounts = emptyList();
+
+    private Builder() {
+      // enforce factory method use
+    }
+
+    public Builder setLogin(String login) {
+      this.login = login;
+      return this;
+    }
+
+    public Builder setName(@Nullable String name) {
+      this.name = name;
+      return this;
+    }
+
+    public Builder setEmail(@Nullable String email) {
+      this.email = email;
+      return this;
+    }
+
+    public Builder setScmAccounts(List<String> scmAccounts) {
+      this.scmAccounts = scmAccounts;
+      return this;
+    }
+
+    public UpdateRequest build() {
+      checkArgument(!isNullOrEmpty(login), "Login is mandatory and must not be empty");
+      return new UpdateRequest(this);
+    }
+  }
+}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UserService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UserService.java
new file mode 100644 (file)
index 0000000..df9c73f
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.user;
+
+import org.sonarqube.ws.client.BaseService;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.WsConnector;
+
+import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_CREATE;
+import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_UPDATE;
+import static org.sonarqube.ws.client.user.UsersWsParameters.CONTROLLER_USERS;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_EMAIL;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOGIN;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_NAME;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_PASSWORD;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_SCM_ACCOUNT;
+
+public class UserService extends BaseService {
+
+  public UserService(WsConnector wsConnector) {
+    super(wsConnector, CONTROLLER_USERS);
+  }
+
+  public void create(CreateRequest request) {
+    call(new PostRequest(path(ACTION_CREATE))
+      .setParam(PARAM_LOGIN, request.getLogin())
+      .setParam(PARAM_PASSWORD, request.getPassword())
+      .setParam(PARAM_NAME, request.getName())
+      .setParam(PARAM_EMAIL, request.getEmail())
+      .setParam(PARAM_SCM_ACCOUNT, request.getScmAccounts()));
+  }
+
+  public void update(UpdateRequest request) {
+    call(new PostRequest(path(ACTION_UPDATE))
+      .setParam(PARAM_LOGIN, request.getLogin())
+      .setParam(PARAM_NAME, request.getName())
+      .setParam(PARAM_EMAIL, request.getEmail())
+      .setParam(PARAM_SCM_ACCOUNT, request.getScmAccounts()));
+  }
+
+}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersWsParameters.java
new file mode 100644 (file)
index 0000000..ea79758
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.user;
+
+public class UsersWsParameters {
+
+  public static final String CONTROLLER_USERS = "api/users";
+
+  public static final String ACTION_CREATE = "create";
+  public static final String ACTION_UPDATE = "update";
+
+  public static final String PARAM_LOGIN = "login";
+  public static final String PARAM_PASSWORD = "password";
+  public static final String PARAM_NAME = "name";
+  public static final String PARAM_EMAIL = "email";
+  public static final String PARAM_SCM_ACCOUNTS = "scmAccounts";
+  public static final String PARAM_SCM_ACCOUNTS_DEPRECATED = "scm_accounts";
+  public static final String PARAM_SCM_ACCOUNT = "scmAccount";
+
+  private UsersWsParameters() {
+    // Only static stuff
+  }
+
+}
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/user/CreateRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/user/CreateRequestTest.java
new file mode 100644 (file)
index 0000000..0ba49c2
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.user;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CreateRequestTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  CreateRequest.Builder underTest = CreateRequest.builder();
+
+  @Test
+  public void create_request() {
+    CreateRequest result = underTest
+      .setLogin("john")
+      .setPassword("123456")
+      .setName("John")
+      .setEmail("john@doo.com")
+      .setScmAccounts(asList("jo", "hn"))
+      .build();
+
+    assertThat(result.getLogin()).isEqualTo("john");
+    assertThat(result.getPassword()).isEqualTo("123456");
+    assertThat(result.getName()).isEqualTo("John");
+    assertThat(result.getEmail()).isEqualTo("john@doo.com");
+    assertThat(result.getScmAccounts()).containsOnly("jo", "hn");
+  }
+
+  @Test
+  public void scm_accounts_is_empty_by_default() throws Exception {
+    CreateRequest result = underTest
+      .setLogin("john")
+      .setPassword("123456")
+      .setName("John")
+      .setEmail("john@doo.com")
+      .build();
+
+    assertThat(result.getScmAccounts()).isEmpty();
+  }
+
+  @Test
+  public void fail_when_empty_login() {
+    expectedException.expect(IllegalArgumentException.class);
+    underTest
+      .setLogin("")
+      .setPassword("123456")
+      .setName("John")
+      .build();
+  }
+
+  @Test
+  public void fail_when_empty_password() {
+    expectedException.expect(IllegalArgumentException.class);
+    underTest
+      .setLogin("john")
+      .setPassword("")
+      .setName("John")
+      .build();
+  }
+
+  @Test
+  public void fail_when_empty_name() {
+    expectedException.expect(IllegalArgumentException.class);
+    underTest
+      .setLogin("john")
+      .setPassword("12345")
+      .setName("")
+      .build();
+  }
+
+}
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/user/UpdateRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/user/UpdateRequestTest.java
new file mode 100644 (file)
index 0000000..2d8428d
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.user;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UpdateRequestTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  UpdateRequest.Builder underTest = UpdateRequest.builder();
+
+  @Test
+  public void create_request() {
+    UpdateRequest result = underTest
+      .setLogin("john")
+      .setName("John")
+      .setEmail("john@doo.com")
+      .setScmAccounts(asList("jo", "hn"))
+      .build();
+
+    assertThat(result.getLogin()).isEqualTo("john");
+    assertThat(result.getName()).isEqualTo("John");
+    assertThat(result.getEmail()).isEqualTo("john@doo.com");
+    assertThat(result.getScmAccounts()).containsOnly("jo", "hn");
+  }
+
+  @Test
+  public void scm_accounts_is_empty_by_default() throws Exception {
+    UpdateRequest result = underTest
+      .setLogin("john")
+      .setName("John")
+      .setEmail("john@doo.com")
+      .build();
+
+    assertThat(result.getScmAccounts()).isEmpty();
+  }
+
+  @Test
+  public void fail_when_empty_login() {
+    expectedException.expect(IllegalArgumentException.class);
+    underTest
+      .setLogin("")
+      .setName("John")
+      .build();
+  }
+
+}
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/user/UserServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/user/UserServiceTest.java
new file mode 100644 (file)
index 0000000..675894b
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.user;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonarqube.ws.client.ServiceTester;
+import org.sonarqube.ws.client.WsConnector;
+
+import static java.util.Arrays.asList;
+import static org.mockito.Mockito.mock;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_EMAIL;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOGIN;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_NAME;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_PASSWORD;
+import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_SCM_ACCOUNT;
+
+public class UserServiceTest {
+
+  @Rule
+  public ServiceTester<UserService> serviceTester = new ServiceTester<>(new UserService(mock(WsConnector.class)));
+
+  private UserService underTest = serviceTester.getInstanceUnderTest();
+
+  @Test
+  public void create() {
+    underTest.create(CreateRequest.builder()
+      .setLogin("john")
+      .setPassword("123456")
+      .setName("John")
+      .setEmail("john@doo.com")
+      .setScmAccounts(asList("jo", "hn"))
+      .build());
+
+    serviceTester.assertThat(serviceTester.getPostRequest())
+      .hasParam(PARAM_LOGIN, "john")
+      .hasParam(PARAM_PASSWORD, "123456")
+      .hasParam(PARAM_NAME, "John")
+      .hasParam(PARAM_EMAIL, "john@doo.com")
+      .hasParam(PARAM_SCM_ACCOUNT, asList("jo", "hn"))
+      .andNoOtherParam();
+  }
+
+  @Test
+  public void update() {
+    underTest.update(UpdateRequest.builder()
+      .setLogin("john")
+      .setName("John")
+      .setEmail("john@doo.com")
+      .setScmAccounts(asList("jo", "hn"))
+      .build());
+
+    serviceTester.assertThat(serviceTester.getPostRequest())
+      .hasParam(PARAM_LOGIN, "john")
+      .hasParam(PARAM_NAME, "John")
+      .hasParam(PARAM_EMAIL, "john@doo.com")
+      .hasParam(PARAM_SCM_ACCOUNT, asList("jo", "hn"))
+      .andNoOtherParam();
+  }
+
+}