3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.v2.api.user.controller;
22 import com.google.gson.Gson;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Optional;
26 import java.util.stream.IntStream;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.mockito.ArgumentCaptor;
30 import org.sonar.db.user.UserDto;
31 import org.sonar.server.common.SearchResults;
32 import org.sonar.server.common.user.service.UserSearchResult;
33 import org.sonar.server.common.user.service.UserService;
34 import org.sonar.server.common.user.service.UsersSearchRequest;
35 import org.sonar.server.exceptions.BadRequestException;
36 import org.sonar.server.exceptions.NotFoundException;
37 import org.sonar.server.tester.UserSessionRule;
38 import org.sonar.server.v2.api.ControllerTester;
39 import org.sonar.server.v2.api.response.PageRestResponse;
40 import org.sonar.server.v2.api.user.converter.UsersSearchRestResponseGenerator;
41 import org.sonar.server.v2.api.user.model.RestUser;
42 import org.sonar.server.v2.api.user.response.UsersSearchRestResponse;
43 import org.springframework.test.web.servlet.MockMvc;
44 import org.springframework.test.web.servlet.MvcResult;
46 import static org.assertj.core.api.Assertions.assertThat;
47 import static org.mockito.ArgumentMatchers.any;
48 import static org.mockito.ArgumentMatchers.eq;
49 import static org.mockito.Mockito.doThrow;
50 import static org.mockito.Mockito.mock;
51 import static org.mockito.Mockito.verify;
52 import static org.mockito.Mockito.when;
53 import static org.sonar.api.utils.DateUtils.formatDateTime;
54 import static org.sonar.server.v2.WebApiEndpoints.USER_ENDPOINT;
55 import static org.sonar.server.v2.api.model.RestPage.DEFAULT_PAGE_INDEX;
56 import static org.sonar.server.v2.api.model.RestPage.DEFAULT_PAGE_SIZE;
57 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
58 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
59 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
60 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
62 public class DefaultUserControllerTest {
65 public UserSessionRule userSession = UserSessionRule.standalone();
66 private final UserService userService = mock(UserService.class);
67 private final UsersSearchRestResponseGenerator responseGenerator = mock(UsersSearchRestResponseGenerator.class);
68 private final MockMvc mockMvc = ControllerTester.getMockMvc(new DefaultUserController(userSession, userService, responseGenerator));
70 private static final Gson gson = new Gson();
73 public void search_whenNoParameters_shouldUseDefaultAndForwardToUserService() throws Exception {
74 when(userService.findUsers(any())).thenReturn(new SearchResults<>(List.of(), 0));
76 mockMvc.perform(get(USER_ENDPOINT))
77 .andExpect(status().isOk());
79 ArgumentCaptor<UsersSearchRequest> requestCaptor = ArgumentCaptor.forClass(UsersSearchRequest.class);
80 verify(userService).findUsers(requestCaptor.capture());
81 assertThat(requestCaptor.getValue().getPageSize()).isEqualTo(Integer.valueOf(DEFAULT_PAGE_SIZE));
82 assertThat(requestCaptor.getValue().getPage()).isEqualTo(Integer.valueOf(DEFAULT_PAGE_INDEX));
83 assertThat(requestCaptor.getValue().isDeactivated()).isFalse();
87 public void search_whenParametersUsed_shouldForwardWithParameters() throws Exception {
88 when(userService.findUsers(any())).thenReturn(new SearchResults<>(List.of(), 0));
89 userSession.logIn().setSystemAdministrator();
91 mockMvc.perform(get(USER_ENDPOINT)
92 .param("active", "false")
93 .param("managed", "true")
95 .param("sonarQubeLastConnectionDateFrom", "2020-01-01T00:00:00+0100")
96 .param("sonarQubeLastConnectionDateTo", "2020-01-01T00:00:00+0100")
97 .param("sonarLintLastConnectionDateFrom", "2020-01-01T00:00:00+0100")
98 .param("sonarLintLastConnectionDateTo", "2020-01-01T00:00:00+0100")
99 .param("pageSize", "100")
100 .param("pageIndex", "2"))
101 .andExpect(status().isOk());
103 ArgumentCaptor<UsersSearchRequest> requestCaptor = ArgumentCaptor.forClass(UsersSearchRequest.class);
104 verify(userService).findUsers(requestCaptor.capture());
105 assertThat(requestCaptor.getValue().getPageSize()).isEqualTo(100);
106 assertThat(requestCaptor.getValue().getPage()).isEqualTo(2);
107 assertThat(requestCaptor.getValue().isDeactivated()).isTrue();
111 public void search_whenAdminParametersUsedButNotAdmin_shouldFail() throws Exception {
112 mockMvc.perform(get(USER_ENDPOINT)
113 .param("sonarQubeLastConnectionDateFrom", "2020-01-01T00:00:00+0100"))
115 status().isForbidden(),
116 content().string("{\"message\":\"parameter sonarQubeLastConnectionDateFrom requires Administer System permission.\"}"));
118 mockMvc.perform(get(USER_ENDPOINT)
119 .param("sonarQubeLastConnectionDateTo", "2020-01-01T00:00:00+0100"))
121 status().isForbidden(),
122 content().string("{\"message\":\"parameter sonarQubeLastConnectionDateTo requires Administer System permission.\"}"));
124 mockMvc.perform(get(USER_ENDPOINT)
125 .param("sonarLintLastConnectionDateFrom", "2020-01-01T00:00:00+0100"))
127 status().isForbidden(),
128 content().string("{\"message\":\"parameter sonarLintLastConnectionDateFrom requires Administer System permission.\"}"));
130 mockMvc.perform(get(USER_ENDPOINT)
131 .param("sonarLintLastConnectionDateTo", "2020-01-01T00:00:00+0100"))
133 status().isForbidden(),
134 content().string("{\"message\":\"parameter sonarLintLastConnectionDateTo requires Administer System permission.\"}"));
138 public void search_whenUserServiceReturnUsers_shouldReturnThem() throws Exception {
139 UserSearchResult user1 = generateUserSearchResult("user1", true, true, false, 2, 3);
140 UserSearchResult user2 = generateUserSearchResult("user2", true, false, false, 3, 0);
141 UserSearchResult user3 = generateUserSearchResult("user3", true, false, true, 1, 1);
142 UserSearchResult user4 = generateUserSearchResult("user4", false, true, false, 0, 0);
143 List<UserSearchResult> users = List.of(user1, user2, user3, user4);
144 SearchResults<UserSearchResult> searchResult = new SearchResults<>(users, users.size());
145 when(userService.findUsers(any())).thenReturn(searchResult);
146 List<RestUser> restUsers = List.of(toRestUser(user1), toRestUser(user2), toRestUser(user3), toRestUser(user4));
147 when(responseGenerator.toUsersForResponse(eq(searchResult.searchResults()), any())).thenReturn(new UsersSearchRestResponse(restUsers, new PageRestResponse(1, 50, 4)));
148 userSession.logIn().setSystemAdministrator();
150 MvcResult mvcResult = mockMvc.perform(get(USER_ENDPOINT))
151 .andExpect(status().isOk())
154 UsersSearchRestResponse actualUsersSearchRestResponse = gson.fromJson(mvcResult.getResponse().getContentAsString(), UsersSearchRestResponse.class);
155 assertThat(actualUsersSearchRestResponse.users())
156 .containsExactlyElementsOf(restUsers);
157 assertThat(actualUsersSearchRestResponse.pageRestResponse().total()).isEqualTo(users.size());
161 private UserSearchResult generateUserSearchResult(String id, boolean active, boolean local, boolean managed, int groupsCount, int tokensCount) {
162 UserDto userDto = new UserDto()
163 .setLogin("login_" + id)
164 .setUuid("uuid_" + id)
165 .setName("name_" + id)
166 .setEmail(id + "@email.com")
169 .setExternalLogin("externalLogin_" + id)
170 .setExternalId("externalId_" + id)
171 .setExternalIdentityProvider("externalIdentityProvider_" + id)
172 .setLastConnectionDate(0L)
173 .setLastSonarlintConnectionDate(1L);
175 List<String> groups = new ArrayList<>();
176 IntStream.range(1, groupsCount).forEach(i -> groups.add("group" + i));
178 return new UserSearchResult(userDto, managed, Optional.of("avatar_" + id), groups, tokensCount);
181 private RestUser toRestUser(UserSearchResult userSearchResult) {
183 userSearchResult.userDto().getLogin(),
184 userSearchResult.userDto().getLogin(),
185 userSearchResult.userDto().getName(),
186 userSearchResult.userDto().getEmail(),
187 userSearchResult.userDto().isActive(),
188 userSearchResult.userDto().isLocal(),
189 userSearchResult.managed(),
190 userSearchResult.userDto().getExternalLogin(),
191 userSearchResult.userDto().getExternalIdentityProvider(),
192 userSearchResult.avatar().orElse(""),
193 formatDateTime(userSearchResult.userDto().getLastConnectionDate()),
194 formatDateTime(userSearchResult.userDto().getLastSonarlintConnectionDate()),
195 userSearchResult.groups().size(),
196 userSearchResult.tokensCount(),
197 userSearchResult.userDto().getSortedScmAccounts());
201 public void deactivate_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
202 userSession.logIn().setNonSystemAdministrator();
204 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
206 status().isForbidden(),
207 content().json("{\"message\":\"Insufficient privileges\"}"));
211 public void deactivate_whenUserServiceThrowsNotFoundException_shouldReturnNotFound() throws Exception {
212 userSession.logIn().setSystemAdministrator();
213 doThrow(new NotFoundException("User not found.")).when(userService).deactivate("userToDelete", false);
215 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
217 status().isNotFound(),
218 content().json("{\"message\":\"User not found.\"}"));
222 public void deactivate_whenUserServiceThrowsBadRequestException_shouldReturnBadRequest() throws Exception {
223 userSession.logIn().setSystemAdministrator();
224 doThrow(BadRequestException.create("Not allowed")).when(userService).deactivate("userToDelete", false);
226 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
228 status().isBadRequest(),
229 content().json("{\"message\":\"Not allowed\"}"));
233 public void deactivate_whenUserTryingToDeactivateThemself_shouldReturnBadRequest() throws Exception {
234 userSession.logIn("userToDelete").setSystemAdministrator();
236 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
238 status().isBadRequest(),
239 content().json("{\"message\":\"Self-deactivation is not possible\"}"));
243 public void deactivate_whenAnonymizeParameterIsNotBoolean_shouldReturnBadRequest() throws Exception {
244 userSession.logIn().setSystemAdministrator();
246 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete?anonymize=maybe"))
248 status().isBadRequest());
252 public void deactivate_whenAnonymizeIsNotSpecified_shouldDeactivateUserWithoutAnonymization() throws Exception {
253 userSession.logIn().setSystemAdministrator();
255 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
256 .andExpect(status().isNoContent());
258 verify(userService).deactivate("userToDelete", false);
262 public void deactivate_whenAnonymizeFalse_shouldDeactivateUserWithoutAnonymization() throws Exception {
263 userSession.logIn().setSystemAdministrator();
265 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete").param("anonymize", "false"))
266 .andExpect(status().isNoContent());
268 verify(userService).deactivate("userToDelete", false);
272 public void deactivate_whenAnonymizeTrue_shouldDeactivateUserWithAnonymization() throws Exception {
273 userSession.logIn().setSystemAdministrator();
275 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete").param("anonymize", "true"))
276 .andExpect(status().isNoContent());
278 verify(userService).deactivate("userToDelete", true);
282 public void fetchUser_whenUserServiceThrowsNotFoundException_returnsNotFound() throws Exception {
283 when(userService.fetchUser("userLogin")).thenThrow(new NotFoundException("Not found"));
284 mockMvc.perform(get(USER_ENDPOINT + "/userLogin"))
286 status().isNotFound(),
287 content().json("{\"message\":\"Not found\"}")
293 public void fetchUser_whenUserExists_shouldReturnUser() throws Exception {
294 UserSearchResult user = generateUserSearchResult("user1", true, true, false, 2, 3);
295 RestUser restUser = toRestUser(user);
296 when(userService.fetchUser("userLogin")).thenReturn(user);
297 when(responseGenerator.toRestUser(user)).thenReturn(restUser);
298 MvcResult mvcResult = mockMvc.perform(get(USER_ENDPOINT + "/userLogin"))
299 .andExpect(status().isOk())
301 RestUser responseUser = gson.fromJson(mvcResult.getResponse().getContentAsString(), RestUser.class);
302 assertThat(responseUser).isEqualTo(restUser);