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.model.RestUserForAdmins;
43 import org.sonar.server.v2.api.user.request.UserCreateRestRequest;
44 import org.sonar.server.v2.api.user.response.UsersSearchRestResponse;
45 import org.springframework.http.MediaType;
46 import org.springframework.test.web.servlet.MockMvc;
47 import org.springframework.test.web.servlet.MvcResult;
49 import static org.assertj.core.api.Assertions.assertThat;
50 import static org.mockito.ArgumentMatchers.any;
51 import static org.mockito.ArgumentMatchers.eq;
52 import static org.mockito.Mockito.doThrow;
53 import static org.mockito.Mockito.mock;
54 import static org.mockito.Mockito.verify;
55 import static org.mockito.Mockito.when;
56 import static org.sonar.api.utils.DateUtils.formatDateTime;
57 import static org.sonar.server.v2.WebApiEndpoints.USER_ENDPOINT;
58 import static org.sonar.server.v2.api.model.RestPage.DEFAULT_PAGE_INDEX;
59 import static org.sonar.server.v2.api.model.RestPage.DEFAULT_PAGE_SIZE;
60 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
61 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
62 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
63 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
64 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
66 public class DefaultUserControllerTest {
69 public UserSessionRule userSession = UserSessionRule.standalone();
70 private final UserService userService = mock(UserService.class);
71 private final UsersSearchRestResponseGenerator responseGenerator = mock(UsersSearchRestResponseGenerator.class);
72 private final MockMvc mockMvc = ControllerTester.getMockMvc(new DefaultUserController(userSession, userService, responseGenerator));
74 private static final Gson gson = new Gson();
77 public void search_whenNoParameters_shouldUseDefaultAndForwardToUserService() throws Exception {
78 when(userService.findUsers(any())).thenReturn(new SearchResults<>(List.of(), 0));
80 mockMvc.perform(get(USER_ENDPOINT))
81 .andExpect(status().isOk());
83 ArgumentCaptor<UsersSearchRequest> requestCaptor = ArgumentCaptor.forClass(UsersSearchRequest.class);
84 verify(userService).findUsers(requestCaptor.capture());
85 assertThat(requestCaptor.getValue().getPageSize()).isEqualTo(Integer.valueOf(DEFAULT_PAGE_SIZE));
86 assertThat(requestCaptor.getValue().getPage()).isEqualTo(Integer.valueOf(DEFAULT_PAGE_INDEX));
87 assertThat(requestCaptor.getValue().isDeactivated()).isFalse();
91 public void search_whenParametersUsed_shouldForwardWithParameters() throws Exception {
92 when(userService.findUsers(any())).thenReturn(new SearchResults<>(List.of(), 0));
93 userSession.logIn().setSystemAdministrator();
95 mockMvc.perform(get(USER_ENDPOINT)
96 .param("active", "false")
97 .param("managed", "true")
99 .param("sonarQubeLastConnectionDateFrom", "2020-01-01T00:00:00+0100")
100 .param("sonarQubeLastConnectionDateTo", "2020-01-01T00:00:00+0100")
101 .param("sonarLintLastConnectionDateFrom", "2020-01-01T00:00:00+0100")
102 .param("sonarLintLastConnectionDateTo", "2020-01-01T00:00:00+0100")
103 .param("pageSize", "100")
104 .param("pageIndex", "2"))
105 .andExpect(status().isOk());
107 ArgumentCaptor<UsersSearchRequest> requestCaptor = ArgumentCaptor.forClass(UsersSearchRequest.class);
108 verify(userService).findUsers(requestCaptor.capture());
109 assertThat(requestCaptor.getValue().getPageSize()).isEqualTo(100);
110 assertThat(requestCaptor.getValue().getPage()).isEqualTo(2);
111 assertThat(requestCaptor.getValue().isDeactivated()).isTrue();
115 public void search_whenAdminParametersUsedButNotAdmin_shouldFail() throws Exception {
116 mockMvc.perform(get(USER_ENDPOINT)
117 .param("sonarQubeLastConnectionDateFrom", "2020-01-01T00:00:00+0100"))
119 status().isForbidden(),
120 content().string("{\"message\":\"parameter sonarQubeLastConnectionDateFrom requires Administer System permission.\"}"));
122 mockMvc.perform(get(USER_ENDPOINT)
123 .param("sonarQubeLastConnectionDateTo", "2020-01-01T00:00:00+0100"))
125 status().isForbidden(),
126 content().string("{\"message\":\"parameter sonarQubeLastConnectionDateTo requires Administer System permission.\"}"));
128 mockMvc.perform(get(USER_ENDPOINT)
129 .param("sonarLintLastConnectionDateFrom", "2020-01-01T00:00:00+0100"))
131 status().isForbidden(),
132 content().string("{\"message\":\"parameter sonarLintLastConnectionDateFrom requires Administer System permission.\"}"));
134 mockMvc.perform(get(USER_ENDPOINT)
135 .param("sonarLintLastConnectionDateTo", "2020-01-01T00:00:00+0100"))
137 status().isForbidden(),
138 content().string("{\"message\":\"parameter sonarLintLastConnectionDateTo requires Administer System permission.\"}"));
142 public void search_whenUserServiceReturnUsers_shouldReturnThem() throws Exception {
143 UserSearchResult user1 = generateUserSearchResult("user1", true, true, false, 2, 3);
144 UserSearchResult user2 = generateUserSearchResult("user2", true, false, false, 3, 0);
145 UserSearchResult user3 = generateUserSearchResult("user3", true, false, true, 1, 1);
146 UserSearchResult user4 = generateUserSearchResult("user4", false, true, false, 0, 0);
147 List<UserSearchResult> users = List.of(user1, user2, user3, user4);
148 SearchResults<UserSearchResult> searchResult = new SearchResults<>(users, users.size());
149 when(userService.findUsers(any())).thenReturn(searchResult);
150 List<RestUser> restUserForAdmins = List.of(toRestUser(user1), toRestUser(user2), toRestUser(user3), toRestUser(user4));
151 when(responseGenerator.toUsersForResponse(eq(searchResult.searchResults()), any())).thenReturn(new UsersSearchRestResponse(restUserForAdmins, new PageRestResponse(1, 50, 4)));
152 userSession.logIn().setSystemAdministrator();
154 MvcResult mvcResult = mockMvc.perform(get(USER_ENDPOINT))
155 .andExpect(status().isOk())
158 UsersSearchRestResponse actualUsersSearchRestResponse = gson.fromJson(mvcResult.getResponse().getContentAsString(), UsersSearchRestResponse.class);
159 assertThat(actualUsersSearchRestResponse.users())
160 .containsExactlyElementsOf(restUserForAdmins);
161 assertThat(actualUsersSearchRestResponse.page().total()).isEqualTo(users.size());
165 private UserSearchResult generateUserSearchResult(String id, boolean active, boolean local, boolean managed, int groupsCount, int tokensCount) {
166 UserDto userDto = new UserDto()
167 .setLogin("login_" + id)
168 .setUuid("uuid_" + id)
169 .setName("name_" + id)
170 .setEmail(id + "@email.com")
173 .setExternalLogin("externalLogin_" + id)
174 .setExternalId("externalId_" + id)
175 .setExternalIdentityProvider("externalIdentityProvider_" + id)
176 .setLastConnectionDate(0L)
177 .setLastSonarlintConnectionDate(1L);
179 List<String> groups = new ArrayList<>();
180 IntStream.range(1, groupsCount).forEach(i -> groups.add("group" + i));
182 return new UserSearchResult(userDto, managed, Optional.of("avatar_" + id), groups, tokensCount);
185 private RestUserForAdmins toRestUser(UserSearchResult userSearchResult) {
186 return new RestUserForAdmins(
187 userSearchResult.userDto().getLogin(),
188 userSearchResult.userDto().getLogin(),
189 userSearchResult.userDto().getName(),
190 userSearchResult.userDto().getEmail(),
191 userSearchResult.userDto().isActive(),
192 userSearchResult.userDto().isLocal(),
193 userSearchResult.managed(),
194 userSearchResult.userDto().getExternalLogin(),
195 userSearchResult.userDto().getExternalIdentityProvider(),
196 userSearchResult.avatar().orElse(""),
197 formatDateTime(userSearchResult.userDto().getLastConnectionDate()),
198 formatDateTime(userSearchResult.userDto().getLastSonarlintConnectionDate()),
199 userSearchResult.groups().size(),
200 userSearchResult.tokensCount(),
201 userSearchResult.userDto().getSortedScmAccounts());
205 public void deactivate_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
206 userSession.logIn().setNonSystemAdministrator();
208 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
210 status().isForbidden(),
211 content().json("{\"message\":\"Insufficient privileges\"}"));
215 public void deactivate_whenUserServiceThrowsNotFoundException_shouldReturnNotFound() throws Exception {
216 userSession.logIn().setSystemAdministrator();
217 doThrow(new NotFoundException("User not found.")).when(userService).deactivate("userToDelete", false);
219 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
221 status().isNotFound(),
222 content().json("{\"message\":\"User not found.\"}"));
226 public void deactivate_whenUserServiceThrowsBadRequestException_shouldReturnBadRequest() throws Exception {
227 userSession.logIn().setSystemAdministrator();
228 doThrow(BadRequestException.create("Not allowed")).when(userService).deactivate("userToDelete", false);
230 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
232 status().isBadRequest(),
233 content().json("{\"message\":\"Not allowed\"}"));
237 public void deactivate_whenUserTryingToDeactivateThemself_shouldReturnBadRequest() throws Exception {
238 userSession.logIn("userToDelete").setSystemAdministrator();
240 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
242 status().isBadRequest(),
243 content().json("{\"message\":\"Self-deactivation is not possible\"}"));
247 public void deactivate_whenAnonymizeParameterIsNotBoolean_shouldReturnBadRequest() throws Exception {
248 userSession.logIn().setSystemAdministrator();
250 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete").param("anonymize", "maybe"))
252 status().isBadRequest());
256 public void deactivate_whenAnonymizeIsNotSpecified_shouldDeactivateUserWithoutAnonymization() throws Exception {
257 userSession.logIn().setSystemAdministrator();
259 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
260 .andExpect(status().isNoContent());
262 verify(userService).deactivate("userToDelete", false);
266 public void deactivate_whenAnonymizeFalse_shouldDeactivateUserWithoutAnonymization() throws Exception {
267 userSession.logIn().setSystemAdministrator();
269 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete").param("anonymize", "false"))
270 .andExpect(status().isNoContent());
272 verify(userService).deactivate("userToDelete", false);
276 public void deactivate_whenAnonymizeTrue_shouldDeactivateUserWithAnonymization() throws Exception {
277 userSession.logIn().setSystemAdministrator();
279 mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete").param("anonymize", "true"))
280 .andExpect(status().isNoContent());
282 verify(userService).deactivate("userToDelete", true);
286 public void fetchUser_whenUserServiceThrowsNotFoundException_returnsNotFound() throws Exception {
287 when(userService.fetchUser("userLogin")).thenThrow(new NotFoundException("Not found"));
288 mockMvc.perform(get(USER_ENDPOINT + "/userLogin"))
290 status().isNotFound(),
291 content().json("{\"message\":\"Not found\"}")
297 public void fetchUser_whenUserExists_shouldReturnUser() throws Exception {
298 UserSearchResult user = generateUserSearchResult("user1", true, true, false, 2, 3);
299 RestUserForAdmins restUserForAdmins = toRestUser(user);
300 when(userService.fetchUser("userLogin")).thenReturn(user);
301 when(responseGenerator.toRestUser(user)).thenReturn(restUserForAdmins);
302 MvcResult mvcResult = mockMvc.perform(get(USER_ENDPOINT + "/userLogin"))
303 .andExpect(status().isOk())
305 RestUserForAdmins responseUser = gson.fromJson(mvcResult.getResponse().getContentAsString(), RestUserForAdmins.class);
306 assertThat(responseUser).isEqualTo(restUserForAdmins);
310 public void create_whenNotAnAdmin_shouldReturnForbidden() throws Exception {
311 userSession.logIn().setNonSystemAdministrator();
315 .contentType(MediaType.APPLICATION_JSON_VALUE)
316 .content(gson.toJson(new UserCreateRestRequest(null, null, "login", "name", null, null))))
318 status().isForbidden(),
319 content().json("{\"message\":\"Insufficient privileges\"}"));
323 public void create_whenNoLogin_shouldReturnBadRequest() throws Exception {
324 userSession.logIn().setSystemAdministrator();
328 .contentType(MediaType.APPLICATION_JSON_VALUE)
329 .content(gson.toJson(new UserCreateRestRequest(null, null, null, "name", null, null))))
331 status().isBadRequest(),
332 content().json("{\"message\":\"Value {} for field login was rejected. Error: must not be null\"}"));
336 public void create_whenNoName_shouldReturnBadRequest() throws Exception {
337 userSession.logIn().setSystemAdministrator();
341 .contentType(MediaType.APPLICATION_JSON_VALUE)
342 .content(gson.toJson(new UserCreateRestRequest(null, null, "login", null, null, null))))
344 status().isBadRequest(),
345 content().json("{\"message\":\"Value {} for field name was rejected. Error: must not be null\"}"));
349 public void create_whenUserServiceThrow_shouldReturnServerError() throws Exception {
350 userSession.logIn().setSystemAdministrator();
351 when(userService.createUser(any())).thenThrow(new IllegalArgumentException("IllegalArgumentException"));
355 .contentType(MediaType.APPLICATION_JSON_VALUE)
356 .content(gson.toJson(new UserCreateRestRequest("e@mail.com", true, "login", "name", "password", List.of("scm")))))
358 status().isBadRequest(),
359 content().json("{\"message\":\"IllegalArgumentException\"}"));
363 public void create_whenUserServiceReturnUser_shouldReturnIt() throws Exception {
364 userSession.logIn().setSystemAdministrator();
365 UserSearchResult userSearchResult = generateUserSearchResult("1", true, true, false, 1, 2);
366 UserDto userDto = userSearchResult.userDto();
367 when(userService.createUser(any())).thenReturn(userSearchResult);
368 when(responseGenerator.toRestUser(userSearchResult)).thenReturn(toRestUser(userSearchResult));
370 MvcResult mvcResult = mockMvc.perform(
372 .contentType(MediaType.APPLICATION_JSON_VALUE)
373 .content(gson.toJson(new UserCreateRestRequest(
374 userDto.getEmail(), userDto.isLocal(), userDto.getLogin(), userDto.getName(), "password", userDto.getSortedScmAccounts()))))
375 .andExpect(status().isOk())
377 RestUserForAdmins responseUser = gson.fromJson(mvcResult.getResponse().getContentAsString(), RestUserForAdmins.class);
378 assertThat(responseUser).isEqualTo(toRestUser(userSearchResult));