From a1190645ceee4f0623e9688b5760755790feaae9 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Fri, 17 Mar 2017 15:20:34 +0100 Subject: [PATCH] SONAR-8968 Use protobuf in api/users/groups --- .../sonar/server/user/ws/GroupsAction.java | 118 +++---- .../sonar/server/user/ws/groups-example.json | 22 +- .../server/user/ws/GroupsActionTest.java | 290 ++++++++++-------- .../server/user/ws/GroupsActionTest/all.json | 8 - .../user/ws/GroupsActionTest/all_admin.json | 7 - .../user/ws/GroupsActionTest/all_page1.json | 8 - .../user/ws/GroupsActionTest/all_page2.json | 8 - .../user/ws/GroupsActionTest/all_users.json | 7 - .../user/ws/GroupsActionTest/deselected.json | 7 - .../user/ws/GroupsActionTest/empty.json | 5 - .../user/ws/GroupsActionTest/selected.json | 7 - .../ws/client/user/GroupsRequest.java | 115 +++++++ .../ws/client/user/UsersService.java | 17 + .../ws/client/user/UsersWsParameters.java | 2 + sonar-ws/src/main/protobuf/ws-users.proto | 17 + .../ws/client/user/GroupsRequestTest.java | 82 +++++ .../ws/client/user/UsersServiceTest.java | 25 ++ 17 files changed, 503 insertions(+), 242 deletions(-) delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all.json delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_admin.json delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_page1.json delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_page2.json delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_users.json delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/deselected.json delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/empty.json delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/selected.json create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/user/GroupsRequest.java create mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/user/GroupsRequestTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/GroupsAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/GroupsAction.java index 87a58a806ca..e13adec5c81 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/GroupsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/GroupsAction.java @@ -20,33 +20,35 @@ package org.sonar.server.user.ws; import java.util.List; +import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService.NewAction; import org.sonar.api.server.ws.WebService.NewController; -import org.sonar.api.server.ws.WebService.Param; import org.sonar.api.server.ws.WebService.SelectionMode; import org.sonar.api.utils.Paging; -import org.sonar.api.utils.text.JsonWriter; +import org.sonar.core.util.Protobuf; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.user.GroupMembershipDto; import org.sonar.db.user.GroupMembershipQuery; import org.sonar.db.user.UserDto; -import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.user.UserSession; - +import org.sonarqube.ws.WsUsers.GroupsWsResponse; +import org.sonarqube.ws.WsUsers.GroupsWsResponse.Group; +import org.sonarqube.ws.client.user.GroupsRequest; + +import static org.sonar.api.server.ws.WebService.Param.PAGE; +import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; +import static org.sonar.api.server.ws.WebService.Param.SELECTED; +import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; import static org.sonar.api.utils.Paging.forPageIndex; +import static org.sonar.server.ws.WsUtils.checkFound; +import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOGIN; public class GroupsAction implements UsersWsAction { - private static final String PARAM_LOGIN = "login"; - - private static final String FIELD_ID = "id"; - private static final String FIELD_NAME = "name"; - private static final String FIELD_DESCRIPTION = "description"; - private static final String FIELD_SELECTED = "selected"; - private final DbClient dbClient; private final UserSession userSession; @@ -61,74 +63,55 @@ public class GroupsAction implements UsersWsAction { .setDescription("Lists the groups a user belongs to. Requires Administer System permission.") .setHandler(this) .setResponseExample(getClass().getResource("groups-example.json")) + .addSelectionModeParam() + .addSearchQuery("users", "group names") + .addPagingParams(25) + .setChangelog(new Change("6.4", "Paging response fields moved to a Paging object")) .setSince("5.2"); action.createParam(PARAM_LOGIN) .setDescription("A user login") .setExampleValue("admin") .setRequired(true); - - action.addSelectionModeParam(); - - action.addSearchQuery("users", "group names"); - - action.addPagingParams(25); } @Override public void handle(Request request, Response response) throws Exception { userSession.checkLoggedIn().checkIsSystemAdministrator(); + GroupsWsResponse groupsWsResponse = doHandle(toGroupsRequest(request)); + writeProtobuf(groupsWsResponse, request, response); + } - String login = request.mandatoryParam(PARAM_LOGIN); - int pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE); - int page = request.mandatoryParamAsInt(Param.PAGE); - String queryString = request.param(Param.TEXT_QUERY); - String selected = request.mandatoryParam(Param.SELECTED); - + private GroupsWsResponse doHandle(GroupsRequest request) { GroupMembershipQuery query = GroupMembershipQuery.builder() - .login(login) - .groupSearch(queryString) - .membership(getMembership(selected)) - .pageIndex(page) - .pageSize(pageSize) + .login(request.getLogin()) + .groupSearch(request.getQuery()) + .membership(getMembership(request.getSelected())) + .pageIndex(request.getPage()) + .pageSize(request.getPageSize()) .build(); try (DbSession dbSession = dbClient.openSession(false)) { - UserDto user = dbClient.userDao().selectByLogin(dbSession, login); - if (user == null) { - throw new NotFoundException(String.format("User with login '%s' has not been found", login)); - } + String login = request.getLogin(); + UserDto user = checkFound(dbClient.userDao().selectActiveUserByLogin(dbSession, login), "Unknown user: %s", login); int total = dbClient.groupMembershipDao().countGroups(dbSession, query, user.getId()); - Paging paging = forPageIndex(page).withPageSize(pageSize).andTotal(total); - List groups = dbClient.groupMembershipDao().selectGroups(dbSession, query, user.getId(), paging.offset(), pageSize); - - JsonWriter json = response.newJsonWriter().beginObject(); - writeGroups(json, groups); - writePaging(json, paging); - json.endObject().close(); + Paging paging = forPageIndex(query.pageIndex()).withPageSize(query.pageSize()).andTotal(total); + List groups = dbClient.groupMembershipDao().selectGroups(dbSession, query, user.getId(), paging.offset(), query.pageSize()); + return buildResponse(groups, paging); } } - private static void writeGroups(JsonWriter json, List groups) { - json.name("groups").beginArray(); - for (GroupMembershipDto group : groups) { - json.beginObject() - .prop(FIELD_ID, group.getId().toString()) - .prop(FIELD_NAME, group.getName()) - .prop(FIELD_DESCRIPTION, group.getDescription()) - .prop(FIELD_SELECTED, group.getUserId() != null) - .endObject(); - } - json.endArray(); - } - - private static void writePaging(JsonWriter json, Paging paging) { - json.prop("p", paging.pageIndex()) - .prop("ps", paging.pageSize()) - .prop("total", paging.total()); + private static GroupsRequest toGroupsRequest(Request request) { + return GroupsRequest.builder() + .setLogin(request.mandatoryParam(PARAM_LOGIN)) + .setSelected(request.mandatoryParam(SELECTED)) + .setQuery(request.param(TEXT_QUERY)) + .setPage(request.mandatoryParamAsInt(PAGE)) + .setPageSize(request.mandatoryParamAsInt(PAGE_SIZE)) + .build(); } - private String getMembership(String selected) { + private static String getMembership(String selected) { SelectionMode selectionMode = SelectionMode.fromParam(selected); String membership = GroupMembershipQuery.ANY; if (SelectionMode.SELECTED == selectionMode) { @@ -138,4 +121,25 @@ public class GroupsAction implements UsersWsAction { } return membership; } + + private static GroupsWsResponse buildResponse(List groups, Paging paging) { + GroupsWsResponse.Builder responseBuilder = GroupsWsResponse.newBuilder(); + groups.forEach(group -> responseBuilder.addGroups(toWsGroup(group))); + responseBuilder.getPagingBuilder() + .setPageIndex(paging.pageIndex()) + .setPageSize(paging.pageSize()) + .setTotal(paging.total()) + .build(); + return responseBuilder.build(); + } + + private static Group toWsGroup(GroupMembershipDto group) { + Group.Builder groupBuilder = Group.newBuilder() + .setId(group.getId()) + .setName(group.getName()) + .setSelected(group.getUserId() != null); + Protobuf.setNullable(group.getDescription(), groupBuilder::setDescription); + return groupBuilder.build(); + } + } diff --git a/server/sonar-server/src/main/resources/org/sonar/server/user/ws/groups-example.json b/server/sonar-server/src/main/resources/org/sonar/server/user/ws/groups-example.json index 52a39b53438..9180af46523 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/user/ws/groups-example.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/user/ws/groups-example.json @@ -1,9 +1,21 @@ { - "p": 1, - "ps": 25, - "total": 2, + "paging": { + "pageIndex": 1, + "pageSize": 25, + "total": 2 + }, "groups": [ - {"id": "2", "name": "sonar-admins", "description": "Sonar Admins", "selected": false}, - {"id": "1", "name": "sonar-users", "description": "Sonar Users", "selected": true} + { + "id": 1, + "name": "sonar-admins", + "description": "Sonar Admins", + "selected": false + }, + { + "id": 2, + "name": "sonar-users", + "description": "Sonar Users", + "selected": true + } ] } diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/GroupsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/GroupsActionTest.java index c27e6df2a58..99b59f5ee3b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/GroupsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/GroupsActionTest.java @@ -19,198 +19,242 @@ */ package org.sonar.server.user.ws; -import org.junit.After; -import org.junit.Before; +import com.google.common.base.Throwables; +import java.io.IOException; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.Param; -import org.sonar.api.server.ws.WebService.SelectionMode; import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; import org.sonar.db.DbTester; -import org.sonar.db.user.GroupDao; import org.sonar.db.user.GroupDto; -import org.sonar.db.user.GroupMembershipDao; -import org.sonar.db.user.UserDao; import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserGroupDao; -import org.sonar.db.user.UserGroupDto; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.tester.UserSessionRule; -import org.sonar.server.ws.WsTester; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.WsActionTester; +import org.sonarqube.ws.MediaTypes; +import org.sonarqube.ws.WsUsers.GroupsWsResponse; import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.Mockito.mock; +import static org.sonar.api.server.ws.WebService.SelectionMode.ALL; +import static org.sonar.api.server.ws.WebService.SelectionMode.DESELECTED; +import static org.sonar.api.server.ws.WebService.SelectionMode.SELECTED; import static org.sonar.db.user.GroupTesting.newGroupDto; +import static org.sonar.db.user.UserTesting.newUserDto; +import static org.sonar.test.JsonAssert.assertJson; public class GroupsActionTest { + private System2 system2 = mock(System2.class); + @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); + public ExpectedException expectedException = ExpectedException.none(); + @Rule - public UserSessionRule userSession = UserSessionRule.standalone(); + public DbTester db = DbTester.create(system2); - private WsTester tester; - private DbClient dbClient; - private DbSession session; + @Rule + public UserSessionRule userSession = UserSessionRule.standalone().logIn().setSystemAdministrator(); - @Before - public void setUp() { - System2 system2 = new System2(); - UserDao userDao = new UserDao(system2); - GroupDao groupDao = new GroupDao(system2); - UserGroupDao userGroupDao = new UserGroupDao(); - GroupMembershipDao groupMembershipDao = new GroupMembershipDao(); + private WsActionTester ws = new WsActionTester(new GroupsAction(db.getDbClient(), userSession)); - dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), userDao, groupDao, userGroupDao, groupMembershipDao); - session = dbClient.openSession(false); - session.commit(); + @Test + public void fail_on_unknown_user() throws Exception { + expectedException.expect(NotFoundException.class); - tester = new WsTester(new UsersWs(new GroupsAction(dbClient, userSession))); - userSession.logIn().setSystemAdministrator(); + call(ws.newRequest().setParam("login", "john")); } - @After - public void tearDown() { - session.close(); - } + @Test + public void fail_on_disabled_user() throws Exception { + UserDto userDto = db.users().insertUser(user -> user.setActive(false)); - @Test(expected = NotFoundException.class) - public void fail_on_unknown_user() throws Exception { - tester.newGetRequest("api/users", "groups") - .setParam("login", "john").execute(); + expectedException.expect(NotFoundException.class); + + call(ws.newRequest().setParam("login", userDto.getLogin())); } - @Test(expected = ForbiddenException.class) + @Test public void fail_on_missing_permission() throws Exception { userSession.logIn("not-admin"); - tester.newGetRequest("api/users", "groups") - .setParam("login", "john").execute(); + + expectedException.expect(ForbiddenException.class); + + call(ws.newRequest().setParam("login", "john")); } @Test public void empty_groups() throws Exception { - createUser(); - session.commit(); + insertUser(); - tester.newGetRequest("api/users", "groups") - .setParam("login", "john") - .execute() - .assertJson(getClass(), "empty.json"); + GroupsWsResponse response = call(ws.newRequest().setParam("login", "john")); + + assertThat(response.getGroupsList()).isEmpty(); } @Test - public void all_groups() throws Exception { - UserDto user = createUser(); - GroupDto usersGroup = createGroup("sonar-users", "Sonar Users"); - createGroup("sonar-admins", "Sonar Admins"); + public void return_selected_groups_selected_param_is_set_to_all() throws Exception { + UserDto user = insertUser(); + GroupDto usersGroup = insertGroup("sonar-users", "Sonar Users"); + GroupDto adminGroup = insertGroup("sonar-admins", "Sonar Admins"); addUserToGroup(user, usersGroup); - session.commit(); - tester.newGetRequest("api/users", "groups") - .setParam("login", "john") - .setParam(Param.SELECTED, SelectionMode.ALL.value()) - .execute() - .assertJson(getClass(), "all.json"); + GroupsWsResponse response = call(ws.newRequest().setParam("login", "john").setParam(Param.SELECTED, ALL.value())); + + assertThat(response.getGroupsList()) + .extracting(GroupsWsResponse.Group::getId, GroupsWsResponse.Group::getName, GroupsWsResponse.Group::getDescription, GroupsWsResponse.Group::getSelected) + .containsOnly( + tuple(usersGroup.getId().longValue(), usersGroup.getName(), usersGroup.getDescription(), true), + tuple(adminGroup.getId().longValue(), adminGroup.getName(), adminGroup.getDescription(), false)); } @Test - public void selected_groups() throws Exception { - UserDto user = createUser(); - GroupDto usersGroup = createGroup("sonar-users", "Sonar Users"); - createGroup("sonar-admins", "Sonar Admins"); + public void return_selected_groups_selected_param_is_set_to_selected() throws Exception { + UserDto user = insertUser(); + GroupDto usersGroup = insertGroup("sonar-users", "Sonar Users"); + insertGroup("sonar-admins", "Sonar Admins"); addUserToGroup(user, usersGroup); - session.commit(); - tester.newGetRequest("api/users", "groups") - .setParam("login", "john") - .execute() - .assertJson(getClass(), "selected.json"); + GroupsWsResponse response = call(ws.newRequest().setParam("login", "john").setParam(Param.SELECTED, SELECTED.value())); - tester.newGetRequest("api/users", "groups") - .setParam("login", "john") - .setParam(Param.SELECTED, SelectionMode.SELECTED.value()) - .execute() - .assertJson(getClass(), "selected.json"); + assertThat(response.getGroupsList()) + .extracting(GroupsWsResponse.Group::getId, GroupsWsResponse.Group::getName, GroupsWsResponse.Group::getDescription, GroupsWsResponse.Group::getSelected) + .containsOnly(tuple(usersGroup.getId().longValue(), usersGroup.getName(), usersGroup.getDescription(), true)); } @Test - public void deselected_groups() throws Exception { - UserDto user = createUser(); - GroupDto usersGroup = createGroup("sonar-users", "Sonar Users"); - createGroup("sonar-admins", "Sonar Admins"); + public void return_selected_groups_selected_param_is_not_set() throws Exception { + UserDto user = insertUser(); + GroupDto usersGroup = insertGroup("sonar-users", "Sonar Users"); + insertGroup("sonar-admins", "Sonar Admins"); addUserToGroup(user, usersGroup); - session.commit(); - tester.newGetRequest("api/users", "groups") - .setParam("login", "john") - .setParam(Param.SELECTED, SelectionMode.DESELECTED.value()) - .execute() - .assertJson(getClass(), "deselected.json"); - } + GroupsWsResponse response = call(ws.newRequest().setParam("login", "john")); - private UserDto createUser() { - return dbClient.userDao().insert(session, new UserDto() - .setActive(true) - .setEmail("john@email.com") - .setLogin("john") - .setName("John") - .setScmAccounts(singletonList("jn"))); + assertThat(response.getGroupsList()) + .extracting(GroupsWsResponse.Group::getId, GroupsWsResponse.Group::getName, GroupsWsResponse.Group::getDescription, GroupsWsResponse.Group::getSelected) + .containsOnly(tuple(usersGroup.getId().longValue(), usersGroup.getName(), usersGroup.getDescription(), true)); } @Test - public void paging() throws Exception { - UserDto user = createUser(); - GroupDto usersGroup = createGroup("sonar-users", "Sonar Users"); - createGroup("sonar-admins", "Sonar Admins"); + public void return_not_selected_groups_selected_param_is_set_to_deselected() throws Exception { + UserDto user = insertUser(); + GroupDto usersGroup = insertGroup("sonar-users", "Sonar Users"); + GroupDto adminGroup = insertGroup("sonar-admins", "Sonar Admins"); addUserToGroup(user, usersGroup); - session.commit(); - tester.newGetRequest("api/users", "groups") - .setParam("login", "john") - .setParam(Param.PAGE_SIZE, "1") - .setParam(Param.SELECTED, SelectionMode.ALL.value()) - .execute() - .assertJson(getClass(), "all_page1.json"); + GroupsWsResponse response = call(ws.newRequest().setParam("login", "john").setParam(Param.SELECTED, DESELECTED.value())); - tester.newGetRequest("api/users", "groups") - .setParam("login", "john") - .setParam(Param.PAGE_SIZE, "1") + assertThat(response.getGroupsList()) + .extracting(GroupsWsResponse.Group::getId, GroupsWsResponse.Group::getName, GroupsWsResponse.Group::getDescription, GroupsWsResponse.Group::getSelected) + .containsOnly(tuple(adminGroup.getId().longValue(), adminGroup.getName(), adminGroup.getDescription(), false)); + } + + @Test + public void return_group_not_having_description() throws Exception { + UserDto user = insertUser(); + GroupDto group = insertGroup("sonar-users", null); + addUserToGroup(user, group); + + GroupsWsResponse response = call(ws.newRequest().setParam("login", "john").setParam(Param.SELECTED, ALL.value())); + + assertThat(response.getGroupsList()).extracting(GroupsWsResponse.Group::hasDescription).containsOnly(false); + } + + @Test + public void search_with_pagination() throws IOException { + UserDto user = insertUser(); + for (int i = 1; i <= 9; i++) { + GroupDto groupDto = insertGroup("group-" + i, "group-" + i); + addUserToGroup(user, groupDto); + } + + GroupsWsResponse response = call(ws.newRequest().setParam("login", "john") + .setParam(Param.PAGE_SIZE, "3") .setParam(Param.PAGE, "2") - .setParam(Param.SELECTED, SelectionMode.ALL.value()) - .execute() - .assertJson(getClass(), "all_page2.json"); + .setParam(Param.SELECTED, ALL.value())); + + assertThat(response.getGroupsList()).extracting(GroupsWsResponse.Group::getName).containsOnly("group-4", "group-5", "group-6"); + assertThat(response.getPaging().getPageSize()).isEqualTo(3); + assertThat(response.getPaging().getPageIndex()).isEqualTo(2); + assertThat(response.getPaging().getTotal()).isEqualTo(9); } @Test - public void filtering() throws Exception { - UserDto user = createUser(); - GroupDto usersGroup = createGroup("sonar-users", "Sonar Users"); - createGroup("sonar-admins", "Sonar Admins"); + public void search_by_text_query() throws Exception { + UserDto user = insertUser(); + GroupDto usersGroup = insertGroup("sonar-users", "Sonar Users"); + GroupDto adminGroup = insertGroup("sonar-admins", "Sonar Admins"); addUserToGroup(user, usersGroup); - session.commit(); - tester.newGetRequest("api/users", "groups") - .setParam("login", "john") - .setParam("q", "users") - .setParam(Param.SELECTED, SelectionMode.ALL.value()) - .execute() - .assertJson(getClass(), "all_users.json"); + assertThat(call(ws.newRequest().setParam("login", "john").setParam("q", "admin").setParam(Param.SELECTED, ALL.value())).getGroupsList()) + .extracting(GroupsWsResponse.Group::getName).containsOnly(adminGroup.getName()); + assertThat(call(ws.newRequest().setParam("login", "john").setParam("q", "users").setParam(Param.SELECTED, ALL.value())).getGroupsList()) + .extracting(GroupsWsResponse.Group::getName).containsOnly(usersGroup.getName()); + } - tester.newGetRequest("api/users", "groups") + @Test + public void test_json_example() { + UserDto user = insertUser(); + GroupDto usersGroup = insertGroup("sonar-users", "Sonar Users"); + insertGroup("sonar-admins", "Sonar Admins"); + addUserToGroup(user, usersGroup); + + String response = ws.newRequest() + .setMediaType(MediaTypes.JSON) .setParam("login", "john") - .setParam("q", "admin") - .setParam(Param.SELECTED, SelectionMode.ALL.value()) - .execute() - .assertJson(getClass(), "all_admin.json"); + .setParam(Param.SELECTED, ALL.value()) + .setParam(Param.PAGE_SIZE, "25") + .setParam(Param.PAGE, "1") + .execute().getInput(); + assertJson(response).ignoreFields("id").isSimilarTo(ws.getDef().responseExampleAsString()); } - private GroupDto createGroup(String name, String description) { - return dbClient.groupDao().insert(session, newGroupDto().setName(name).setDescription(description)); + @Test + public void verify_definition() { + WebService.Action action = ws.getDef(); + + assertThat(action.since()).isEqualTo("5.2"); + assertThat(action.isPost()).isFalse(); + assertThat(action.isInternal()).isFalse(); + assertThat(action.responseExampleAsString()).isNotEmpty(); + + assertThat(action.params()).extracting(Param::key).containsOnly("p", "q", "ps", "login", "selected"); + + WebService.Param qualifiers = action.param("login"); + assertThat(qualifiers.isRequired()).isTrue(); + } + + private GroupDto insertGroup(String name, String description) { + return db.users().insertGroup(newGroupDto().setName(name).setDescription(description)); } private void addUserToGroup(UserDto user, GroupDto usersGroup) { - dbClient.userGroupDao().insert(session, new UserGroupDto().setUserId(user.getId()).setGroupId(usersGroup.getId())); + db.users().insertMember(usersGroup, user); + } + + private UserDto insertUser() { + return db.users().insertUser(newUserDto() + .setActive(true) + .setEmail("john@email.com") + .setLogin("john") + .setName("John") + .setScmAccounts(singletonList("jn"))); } + + private GroupsWsResponse call(TestRequest request) { + request.setMediaType(MediaTypes.PROTOBUF); + try { + return GroupsWsResponse.parseFrom(request.execute().getInputStream()); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all.json deleted file mode 100644 index af9bb6d1dd2..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "p": 1, - "total": 2, - "groups": [ - {"name": "sonar-admins", "description": "Sonar Admins", "selected": false}, - {"name": "sonar-users", "description": "Sonar Users", "selected": true} - ] -} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_admin.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_admin.json deleted file mode 100644 index 9310fce67e5..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_admin.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "p": 1, - "total": 1, - "groups": [ - {"name": "sonar-admins", "description": "Sonar Admins", "selected": false} - ] -} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_page1.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_page1.json deleted file mode 100644 index 230e55493eb..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_page1.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "p": 1, - "ps": 1, - "total": 2, - "groups": [ - {"name": "sonar-admins", "description": "Sonar Admins", "selected": false} - ] -} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_page2.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_page2.json deleted file mode 100644 index 054d1f53f7b..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_page2.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "p": 2, - "ps": 1, - "total": 2, - "groups": [ - {"name": "sonar-users", "description": "Sonar Users", "selected": true} - ] -} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_users.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_users.json deleted file mode 100644 index 021346810c1..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/all_users.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "p": 1, - "total": 1, - "groups": [ - {"name": "sonar-users", "description": "Sonar Users", "selected": true} - ] -} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/deselected.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/deselected.json deleted file mode 100644 index 9310fce67e5..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/deselected.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "p": 1, - "total": 1, - "groups": [ - {"name": "sonar-admins", "description": "Sonar Admins", "selected": false} - ] -} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/empty.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/empty.json deleted file mode 100644 index 2160187bbb6..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/empty.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "p": 1, - "total": 0, - "groups": [] -} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/selected.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/selected.json deleted file mode 100644 index 021346810c1..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/GroupsActionTest/selected.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "p": 1, - "total": 1, - "groups": [ - {"name": "sonar-users", "description": "Sonar Users", "selected": true} - ] -} \ No newline at end of file diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/GroupsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/GroupsRequest.java new file mode 100644 index 00000000000..4b44f65ff43 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/GroupsRequest.java @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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 javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Strings.isNullOrEmpty; + +@Immutable +public class GroupsRequest { + + private final String login; + private final String query; + private final String selected; + private final Integer page; + private final Integer pageSize; + + private GroupsRequest(Builder builder) { + this.login = builder.login; + this.query = builder.query; + this.selected = builder.selected; + this.page = builder.page; + this.pageSize = builder.pageSize; + } + + public String getLogin() { + return login; + } + + @CheckForNull + public String getQuery() { + return query; + } + + @CheckForNull + public String getSelected() { + return selected; + } + + @CheckForNull + public Integer getPage() { + return page; + } + + @CheckForNull + public Integer getPageSize() { + return pageSize; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String login; + private String query; + private String selected; + private Integer page; + private Integer pageSize; + + private Builder() { + // enforce factory method use + } + + public Builder setLogin(String login) { + this.login = login; + return this; + } + + public Builder setQuery(@Nullable String query) { + this.query = query; + return this; + } + + public Builder setSelected(@Nullable String selected) { + this.selected = selected; + return this; + } + + public Builder setPage(@Nullable Integer page) { + this.page = page; + return this; + } + + public Builder setPageSize(@Nullable Integer pageSize) { + this.pageSize = pageSize; + return this; + } + + public GroupsRequest build() { + checkArgument(!isNullOrEmpty(login), "Login is mandatory and must not be empty"); + return new GroupsRequest(this); + } + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersService.java index 98dff5c3742..1a55512f6e0 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/user/UsersService.java @@ -20,11 +20,17 @@ package org.sonarqube.ws.client.user; import org.sonarqube.ws.WsUsers.CreateWsResponse; +import org.sonarqube.ws.WsUsers.GroupsWsResponse; import org.sonarqube.ws.client.BaseService; +import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.PostRequest; import org.sonarqube.ws.client.WsConnector; +import static org.sonar.api.server.ws.WebService.Param.PAGE; +import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; +import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_CREATE; +import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_GROUPS; 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; @@ -33,6 +39,7 @@ 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_SELECTED; public class UsersService extends BaseService { @@ -59,4 +66,14 @@ public class UsersService extends BaseService { .setParam(PARAM_SCM_ACCOUNT, request.getScmAccounts())); } + public GroupsWsResponse groups(GroupsRequest request) { + return call(new GetRequest(path(ACTION_GROUPS)) + .setParam(PARAM_LOGIN, request.getLogin()) + .setParam(PARAM_SELECTED, request.getSelected()) + .setParam(TEXT_QUERY, request.getQuery()) + .setParam(PAGE, request.getPage()) + .setParam(PAGE_SIZE, request.getPageSize()), + GroupsWsResponse.parser()); + } + } 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 index 3b02c6f47ea..cf1946786b3 100644 --- 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 @@ -25,6 +25,7 @@ public class UsersWsParameters { public static final String ACTION_CREATE = "create"; public static final String ACTION_UPDATE = "update"; + public static final String ACTION_GROUPS = "groups"; public static final String PARAM_LOGIN = "login"; public static final String PARAM_PASSWORD = "password"; @@ -34,6 +35,7 @@ public class UsersWsParameters { public static final String PARAM_SCM_ACCOUNTS_DEPRECATED = "scm_accounts"; public static final String PARAM_SCM_ACCOUNT = "scmAccount"; public static final String PARAM_LOCAL = "local"; + public static final String PARAM_SELECTED = "selected"; private UsersWsParameters() { // Only static stuff diff --git a/sonar-ws/src/main/protobuf/ws-users.proto b/sonar-ws/src/main/protobuf/ws-users.proto index 0587640916c..25bcdf43458 100644 --- a/sonar-ws/src/main/protobuf/ws-users.proto +++ b/sonar-ws/src/main/protobuf/ws-users.proto @@ -20,6 +20,8 @@ syntax = "proto2"; package sonarqube.ws.users; +import "ws-commons.proto"; + option java_package = "org.sonarqube.ws"; option java_outer_classname = "WsUsers"; option optimize_for = SPEED; @@ -48,3 +50,18 @@ message CreateWsResponse { optional bool local = 6; } } + +// WS api/users/groups +message GroupsWsResponse { + optional sonarqube.ws.commons.Paging paging = 1; + repeated Group groups = 2; + + message Group { + optional int64 id = 1; + optional string name = 2; + optional string description = 3; + optional bool selected = 4; + } +} + + diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/user/GroupsRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/user/GroupsRequestTest.java new file mode 100644 index 00000000000..082e5f720b6 --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/user/GroupsRequestTest.java @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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 org.assertj.core.api.Assertions.assertThat; + +public class GroupsRequestTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + GroupsRequest.Builder underTest = GroupsRequest.builder(); + + @Test + public void create_request() { + GroupsRequest result = underTest + .setLogin("john") + .setSelected("all") + .setQuery("sonar-users") + .setPage(10) + .setPageSize(50) + .build(); + + assertThat(result.getLogin()).isEqualTo("john"); + assertThat(result.getSelected()).isEqualTo("all"); + assertThat(result.getQuery()).isEqualTo("sonar-users"); + assertThat(result.getPage()).isEqualTo(10); + assertThat(result.getPageSize()).isEqualTo(50); + } + + @Test + public void create_request_wih_minimal_fields() { + GroupsRequest result = underTest.setLogin("john").build(); + + assertThat(result.getLogin()).isEqualTo("john"); + assertThat(result.getSelected()).isNull(); + assertThat(result.getQuery()).isNull(); + assertThat(result.getPage()).isNull(); + assertThat(result.getPageSize()).isNull(); + } + + @Test + public void fail_when_empty_login() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Login is mandatory and must not be empty"); + + underTest + .setLogin("") + .build(); + } + + @Test + public void fail_when_null_login() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Login is mandatory and must not be empty"); + + underTest.build(); + } + +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/user/UsersServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/user/UsersServiceTest.java index a6ef0a8da44..37902de632c 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/user/UsersServiceTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/user/UsersServiceTest.java @@ -22,18 +22,23 @@ package org.sonarqube.ws.client.user; import org.junit.Rule; import org.junit.Test; import org.sonarqube.ws.WsUsers.CreateWsResponse; +import org.sonarqube.ws.WsUsers.GroupsWsResponse; import org.sonarqube.ws.client.ServiceTester; import org.sonarqube.ws.client.WsConnector; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.sonar.api.server.ws.WebService.Param.PAGE; +import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; +import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_EMAIL; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOCAL; 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_SELECTED; public class UsersServiceTest { @@ -81,4 +86,24 @@ public class UsersServiceTest { .andNoOtherParam(); } + @Test + public void groups() { + underTest.groups(GroupsRequest.builder() + .setLogin("john") + .setSelected("all") + .setQuery("sonar-users") + .setPage(10) + .setPageSize(50) + .build()); + + assertThat(serviceTester.getGetParser()).isSameAs(GroupsWsResponse.parser()); + serviceTester.assertThat(serviceTester.getGetRequest()) + .hasParam(PARAM_LOGIN, "john") + .hasParam(PARAM_SELECTED, "all") + .hasParam(TEXT_QUERY, "sonar-users") + .hasParam(PAGE, 10) + .hasParam(PAGE_SIZE, 50) + .andNoOtherParam(); + } + } -- 2.39.5