Browse Source

SONAR-19963 Add tests for API v2 users controller and refactor existing tests

tags/10.2.0.77647
Antoine Vigneau 11 months ago
parent
commit
68123ed58e
15 changed files with 365 additions and 365 deletions
  1. 2
    2
      server/sonar-webserver-webapi-v2/build.gradle
  2. 0
    135
      server/sonar-webserver-webapi-v2/src/it/java/org/sonar/server/v2/api/user/controller/DefaultUserControllerIT.java
  3. 0
    102
      server/sonar-webserver-webapi-v2/src/it/java/org/sonar/server/v2/config/MockConfigForControllers.java
  4. 0
    2
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
  5. 2
    2
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/model/RestPage.java
  6. 4
    6
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DefaultLivenessController.java
  7. 1
    1
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/HealthController.java
  8. 1
    1
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/LivenessController.java
  9. 1
    1
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/package-info.java
  10. 4
    4
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
  11. 4
    4
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java
  12. 8
    24
      server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/ControllerTester.java
  13. 21
    24
      server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DefaultLivenessControllerTest.java
  14. 41
    57
      server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/HealthControllerTest.java
  15. 276
    0
      server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/user/controller/DefaultUserControllerTest.java

+ 2
- 2
server/sonar-webserver-webapi-v2/build.gradle View File

@@ -20,9 +20,9 @@ dependencies {
testImplementation 'org.mockito:mockito-core'
testImplementation 'org.springframework:spring-test'
testImplementation 'org.skyscreamer:jsonassert:1.5.1'
testImplementation project(':sonar-testing-harness')

testImplementation testFixtures(project(':server:sonar-server-common'))

testImplementation project(':sonar-testing-harness')
testImplementation testFixtures(project(':server:sonar-webserver-auth'))
}


+ 0
- 135
server/sonar-webserver-webapi-v2/src/it/java/org/sonar/server/v2/api/user/controller/DefaultUserControllerIT.java View File

@@ -1,135 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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.sonar.server.v2.api.user.controller;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.sonar.db.DbClient;
import org.sonar.db.user.UserDao;
import org.sonar.server.common.user.service.UserService;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.user.AbstractUserSession;
import org.sonar.server.user.UserSession;
import org.sonar.server.v2.common.ControllerIT;

import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.sonar.server.v2.WebApiEndpoints.USER_ENDPOINT;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class DefaultUserControllerIT extends ControllerIT {

@After
public void resetUsedMocks() {
Mockito.reset(webAppContext.getBean(UserService.class));
Mockito.reset(webAppContext.getBean(UserSession.class));
}

@Before
public void setUp() {
UserSession userSession = webAppContext.getBean(UserSession.class);
when(userSession.checkLoggedIn()).thenReturn(userSession);
}

@Test
public void deactivate_whenUserIsNotLoggedIn_shouldReturnForbidden() throws Exception {
when(webAppContext.getBean(UserSession.class).checkLoggedIn()).thenThrow(new UnauthorizedException("unauthorized"));
mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
.andExpectAll(
status().isUnauthorized(),
content().string("{\"message\":\"unauthorized\"}"));
}

@Test
public void deactivate_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
when(webAppContext.getBean(UserSession.class).checkIsSystemAdministrator()).thenThrow(AbstractUserSession.insufficientPrivilegesException());
mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
.andExpectAll(
status().isForbidden(),
content().json("{\"message\":\"Insufficient privileges\"}"));
}

@Test
public void deactivate_whenUserServiceThrowsNotFoundException_shouldReturnNotFound() throws Exception {
doThrow(new NotFoundException("User not found.")).when(webAppContext.getBean(UserService.class)).deactivate("userToDelete", false);
mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
.andExpectAll(
status().isNotFound(),
content().json("{\"message\":\"User not found.\"}"));
}

@Test
public void deactivate_whenUserServiceThrowsBadRequestException_shouldReturnBadRequest() throws Exception {
doThrow(BadRequestException.create("Not allowed")).when(webAppContext.getBean(UserService.class)).deactivate("userToDelete", false);
mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
.andExpectAll(
status().isBadRequest(),
content().json("{\"message\":\"Not allowed\"}"));
}

@Test
public void deactivate_whenUserTryingToDeactivateThemself_shouldReturnBadRequest() throws Exception {
when(webAppContext.getBean(DbClient.class).userDao()).thenReturn(mock(UserDao.class));
when(webAppContext.getBean(UserSession.class).getLogin()).thenReturn("userToDelete");
mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
.andExpectAll(
status().isBadRequest(),
content().json("{\"message\":\"Self-deactivation is not possible\"}"));
}

@Test
public void deactivate_whenAnonymizeParameterIsNotBoolean_shouldReturnBadRequest() throws Exception {
mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete?anonymize=maybe"))
.andExpect(
status().isBadRequest());
}

@Test
public void deactivate_whenAnonymizeIsNotSpecified_shouldDeactivateUserWithoutAnonymization() throws Exception {
mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
.andExpect(status().isNoContent());

verify(webAppContext.getBean(UserService.class)).deactivate("userToDelete", false);
}

@Test
public void deactivate_whenAnonymizeFalse_shouldDeactivateUserWithoutAnonymization() throws Exception {
mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete?anonymize=false"))
.andExpect(status().isNoContent());

verify(webAppContext.getBean(UserService.class)).deactivate("userToDelete", false);
}

@Test
public void deactivate_whenAnonymizeTrue_shouldDeactivateUserWithAnonymization() throws Exception {
mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete?anonymize=true"))
.andExpect(status().isNoContent());

verify(webAppContext.getBean(UserService.class)).deactivate("userToDelete", true);
}
}

+ 0
- 102
server/sonar-webserver-webapi-v2/src/it/java/org/sonar/server/v2/config/MockConfigForControllers.java View File

@@ -1,102 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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.sonar.server.v2.config;

import org.sonar.db.DbClient;
import org.sonar.server.common.user.service.UserService;
import org.sonar.server.health.CeStatusNodeCheck;
import org.sonar.server.health.DbConnectionNodeCheck;
import org.sonar.server.health.EsStatusNodeCheck;
import org.sonar.server.health.HealthChecker;
import org.sonar.server.health.WebServerStatusNodeCheck;
import org.sonar.server.platform.NodeInformation;
import org.sonar.server.platform.ws.LivenessChecker;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.user.UserSession;
import org.sonar.server.user.UserUpdater;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import static org.mockito.Mockito.mock;

@Configuration
public class MockConfigForControllers {

@Bean
public DbClient dbClient() {
return mock(DbClient.class);
}

@Bean
public DbConnectionNodeCheck dbConnectionNodeCheck() {
return mock(DbConnectionNodeCheck.class);
}

@Bean
public WebServerStatusNodeCheck webServerStatusNodeCheck() {
return mock(WebServerStatusNodeCheck.class);
}

@Bean
public CeStatusNodeCheck ceStatusNodeCheck() {
return mock(CeStatusNodeCheck.class);
}

@Bean
public EsStatusNodeCheck esStatusNodeCheck() {
return mock(EsStatusNodeCheck.class);
}

@Bean
public LivenessChecker livenessChecker() {
return mock(LivenessChecker.class);
}

@Bean
public HealthChecker healthChecker() {
return mock(HealthChecker.class);
}

@Bean
public SystemPasscode systemPasscode() {
return mock(SystemPasscode.class);
}

@Bean
public NodeInformation nodeInformation() {
return mock(NodeInformation.class);
}

@Bean
public UserSession userSession() {
return mock(UserSession.class);
}

@Bean
UserUpdater userUpdater() {
return mock(UserUpdater.class);
}

@Bean
UserService userService() {
return mock(UserService.class);
}

}

+ 0
- 2
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java View File

@@ -21,9 +21,7 @@ package org.sonar.server.v2;

public class WebApiEndpoints {
private static final String SYSTEM_ENDPOINTS = "/system";

public static final String LIVENESS_ENDPOINT = SYSTEM_ENDPOINTS + "/liveness";

public static final String HEALTH_ENDPOINT = SYSTEM_ENDPOINTS + "/health";
public static final String USER_ENDPOINT = "/users";


+ 2
- 2
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/model/RestPage.java View File

@@ -42,9 +42,9 @@ public record RestPage(
) {

@VisibleForTesting
static final String DEFAULT_PAGE_SIZE = "50";
public static final String DEFAULT_PAGE_SIZE = "50";
@VisibleForTesting
static final String DEFAULT_PAGE_INDEX = "1";
public static final String DEFAULT_PAGE_INDEX = "1";

public RestPage(@Nullable Integer pageSize, @Nullable Integer pageIndex) {
this.pageSize = pageSize == null ? Integer.valueOf(DEFAULT_PAGE_SIZE) : pageSize;

server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/controller/DefautLivenessController.java → server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DefaultLivenessController.java View File

@@ -17,24 +17,21 @@
* 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.sonar.server.v2.controller;
package org.sonar.server.v2.api.system.controller;

import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.platform.ws.LivenessChecker;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.user.UserSession;

public class DefautLivenessController implements LivenessController {
public class DefaultLivenessController implements LivenessController {

private static final Logger LOGGER = LoggerFactory.getLogger(DefautLivenessController.class);
private final LivenessChecker livenessChecker;
private final UserSession userSession;
private final SystemPasscode systemPasscode;

public DefautLivenessController(LivenessChecker livenessChecker, SystemPasscode systemPasscode, @Nullable UserSession userSession) {
public DefaultLivenessController(LivenessChecker livenessChecker, SystemPasscode systemPasscode, @Nullable UserSession userSession) {
this.livenessChecker = livenessChecker;
this.userSession = userSession;
this.systemPasscode = systemPasscode;
@@ -57,4 +54,5 @@ public class DefautLivenessController implements LivenessController {
}
return userSession.isSystemAdministrator();
}

}

server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/controller/HealthController.java → server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/HealthController.java View File

@@ -17,7 +17,7 @@
* 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.sonar.server.v2.controller;
package org.sonar.server.v2.api.system.controller;

import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.ServerException;

server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/controller/LivenessController.java → server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/LivenessController.java View File

@@ -17,7 +17,7 @@
* 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.sonar.server.v2.controller;
package org.sonar.server.v2.api.system.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;

server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/controller/package-info.java → server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/package-info.java View File

@@ -18,6 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
@ParametersAreNonnullByDefault
package org.sonar.server.v2.controller;
package org.sonar.server.v2.api.system.controller;

import javax.annotation.ParametersAreNonnullByDefault;

+ 4
- 4
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java View File

@@ -31,12 +31,12 @@ import org.sonar.server.platform.ws.LivenessChecker;
import org.sonar.server.platform.ws.LivenessCheckerImpl;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.user.UserSession;
import org.sonar.server.v2.api.system.controller.DefaultLivenessController;
import org.sonar.server.v2.api.system.controller.HealthController;
import org.sonar.server.v2.api.system.controller.LivenessController;
import org.sonar.server.v2.api.user.controller.DefaultUserController;
import org.sonar.server.v2.api.user.controller.UserController;
import org.sonar.server.v2.api.user.converter.UsersSearchRestResponseGenerator;
import org.sonar.server.v2.controller.DefautLivenessController;
import org.sonar.server.v2.controller.HealthController;
import org.sonar.server.v2.controller.LivenessController;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -53,7 +53,7 @@ public class PlatformLevel4WebConfig {

@Bean
public LivenessController livenessController(LivenessChecker livenessChecker, UserSession userSession, SystemPasscode systemPasscode) {
return new DefautLivenessController(livenessChecker, systemPasscode, userSession);
return new DefaultLivenessController(livenessChecker, systemPasscode, userSession);
}

@Bean

+ 4
- 4
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java View File

@@ -24,9 +24,9 @@ import org.sonar.server.health.HealthChecker;
import org.sonar.server.platform.ws.LivenessChecker;
import org.sonar.server.platform.ws.SafeModeLivenessCheckerImpl;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.v2.controller.DefautLivenessController;
import org.sonar.server.v2.controller.HealthController;
import org.sonar.server.v2.controller.LivenessController;
import org.sonar.server.v2.api.system.controller.DefaultLivenessController;
import org.sonar.server.v2.api.system.controller.HealthController;
import org.sonar.server.v2.api.system.controller.LivenessController;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -44,7 +44,7 @@ public class SafeModeWebConfig {

@Bean
public LivenessController livenessController(LivenessChecker livenessChecker, SystemPasscode systemPasscode) {
return new DefautLivenessController(livenessChecker, systemPasscode, null);
return new DefaultLivenessController(livenessChecker, systemPasscode, null);
}

@Bean

server/sonar-webserver-webapi-v2/src/it/java/org/sonar/server/v2/common/ControllerIT.java → server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/ControllerTester.java View File

@@ -17,33 +17,17 @@
* 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.sonar.server.v2.common;
package org.sonar.server.v2.api;

import org.junit.Before;
import org.junit.runner.RunWith;
import org.sonar.server.v2.config.MockConfigForControllers;
import org.sonar.server.v2.config.PlatformLevel4WebConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.sonar.server.v2.common.RestResponseEntityExceptionHandler;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@WebAppConfiguration
@ContextConfiguration(
classes = {PlatformLevel4WebConfig.class, MockConfigForControllers.class}
)
@RunWith(SpringRunner.class)
public abstract class ControllerIT {
@Autowired
protected WebApplicationContext webAppContext;

protected MockMvc mockMvc;

@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext).build();
public class ControllerTester {
public static MockMvc getMockMvc(Object... controllers) {
return MockMvcBuilders
.standaloneSetup(controllers)
.setControllerAdvice(new RestResponseEntityExceptionHandler())
.build();
}
}

server/sonar-webserver-webapi-v2/src/it/java/org/sonar/server/v2/controller/DefaultLivenessControllerIT.java → server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DefaultLivenessControllerTest.java View File

@@ -17,17 +17,17 @@
* 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.sonar.server.v2.controller;
package org.sonar.server.v2.api.system.controller;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import org.sonar.server.platform.ws.LivenessChecker;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.user.UserSession;
import org.sonar.server.v2.common.ControllerIT;
import org.sonar.server.v2.api.ControllerTester;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.server.user.SystemPasscodeImpl.PASSCODE_HTTP_HEADER;
import static org.sonar.server.v2.WebApiEndpoints.LIVENESS_ENDPOINT;
@@ -35,26 +35,21 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class DefaultLivenessControllerIT extends ControllerIT {
public class DefaultLivenessControllerTest {

private static final String VALID_PASSCODE = "valid_passcode";
private static final String INVALID_PASSCODE = "invalid_passcode";

@Before
public void setup() {
super.setup();
when(webAppContext.getBean(LivenessChecker.class).liveness()).thenReturn(true);
}

@After
public void resetUsedMocks() {
Mockito.reset(webAppContext.getBean(SystemPasscode.class));
Mockito.reset(webAppContext.getBean(UserSession.class));
}
private final LivenessChecker livenessChecker = mock(LivenessChecker.class);
private final SystemPasscode systemPasscode = mock(SystemPasscode.class);
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
private final MockMvc mockMvc = ControllerTester.getMockMvc(new DefaultLivenessController(livenessChecker, systemPasscode, userSession));

@Test
public void getSystemLiveness_whenValidPasscode_shouldSucceed() throws Exception {
when(webAppContext.getBean(SystemPasscode.class).isValidPasscode(VALID_PASSCODE)).thenReturn(true);
when(systemPasscode.isValidPasscode(VALID_PASSCODE)).thenReturn(true);
when(livenessChecker.liveness()).thenReturn(true);

mockMvc.perform(get(LIVENESS_ENDPOINT).header(PASSCODE_HTTP_HEADER, VALID_PASSCODE))
.andExpect(status().isNoContent());
@@ -62,7 +57,8 @@ public class DefaultLivenessControllerIT extends ControllerIT {

@Test
public void getSystemLiveness_whenAdminCredential_shouldSucceed() throws Exception {
when(webAppContext.getBean(UserSession.class).isSystemAdministrator()).thenReturn(true);
userSession.logIn().setSystemAdministrator();
when(livenessChecker.liveness()).thenReturn(true);

mockMvc.perform(get(LIVENESS_ENDPOINT))
.andExpect(status().isNoContent());
@@ -78,8 +74,8 @@ public class DefaultLivenessControllerIT extends ControllerIT {

@Test
public void getSystemLiveness_whenInvalidPasscodeAndNoAdminCredentials_shouldReturnForbidden() throws Exception {
when(webAppContext.getBean(SystemPasscode.class).isValidPasscode(INVALID_PASSCODE)).thenReturn(false);
when(webAppContext.getBean(UserSession.class).isSystemAdministrator()).thenReturn(false);
when(systemPasscode.isValidPasscode(INVALID_PASSCODE)).thenReturn(false);
userSession.logIn();

mockMvc.perform(get(LIVENESS_ENDPOINT).header(PASSCODE_HTTP_HEADER, INVALID_PASSCODE))
.andExpectAll(
@@ -89,12 +85,13 @@ public class DefaultLivenessControllerIT extends ControllerIT {

@Test
public void getSystemLiveness_whenLivenessCheckFails_shouldReturnServerError() throws Exception {
when(webAppContext.getBean(SystemPasscode.class).isValidPasscode(VALID_PASSCODE)).thenReturn(true);
when(webAppContext.getBean(LivenessChecker.class).liveness()).thenReturn(false);
when(systemPasscode.isValidPasscode(VALID_PASSCODE)).thenReturn(true);
when(livenessChecker.liveness()).thenReturn(false);

mockMvc.perform(get(LIVENESS_ENDPOINT).header(PASSCODE_HTTP_HEADER, VALID_PASSCODE))
.andExpectAll(
status().isInternalServerError(),
content().json("{\"message\":\"Liveness check failed\"}"));
}

}

server/sonar-webserver-webapi-v2/src/it/java/org/sonar/server/v2/controller/HealthControllerIT.java → server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/HealthControllerTest.java View File

@@ -17,21 +17,23 @@
* 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.sonar.server.v2.controller;
package org.sonar.server.v2.api.system.controller;

import org.junit.After;
import org.junit.Before;
import com.google.gson.Gson;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.health.Health;
import org.sonar.server.health.HealthChecker;
import org.sonar.server.platform.NodeInformation;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.user.UserSession;
import org.sonar.server.v2.common.ControllerIT;
import org.sonar.server.v2.api.ControllerTester;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.server.user.SystemPasscodeImpl.PASSCODE_HTTP_HEADER;
import static org.sonar.server.v2.WebApiEndpoints.HEALTH_ENDPOINT;
@@ -40,7 +42,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


public class HealthControllerIT extends ControllerIT {
public class HealthControllerTest {

private static final String VALID_PASSCODE = "valid_passcode";
private static final String INVALID_PASSCODE = "invalid_passcode";
@@ -49,51 +51,42 @@ public class HealthControllerIT extends ControllerIT {
.addCause("One cause")
.build();

@Before
public void setup() {
super.setup();
when(webAppContext.getBean(HealthChecker.class).checkNode()).thenReturn(HEALTH_RESULT);
}
private final HealthChecker healthChecker = mock(HealthChecker.class);
private final SystemPasscode systemPasscode = mock(SystemPasscode.class);
private final NodeInformation nodeInformation = mock(NodeInformation.class);
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
private final MockMvc mockMvc = ControllerTester.getMockMvc(new HealthController(healthChecker, systemPasscode, nodeInformation,userSession));

@After
public void resetUsedMocks() {
Mockito.reset(webAppContext.getBean(SystemPasscode.class));
Mockito.reset(webAppContext.getBean(NodeInformation.class));
Mockito.reset(webAppContext.getBean(UserSession.class));
}

private static final Gson gson = new Gson();

@Test
public void getSystemHealth_whenValidPasscodeAndStandaloneMode_shouldSucceed() throws Exception {
when(webAppContext.getBean(SystemPasscode.class).isValidPasscode(VALID_PASSCODE)).thenReturn(true);
when(webAppContext.getBean(NodeInformation.class).isStandalone()).thenReturn(true);
when(systemPasscode.isValidPasscode(VALID_PASSCODE)).thenReturn(true);
when(nodeInformation.isStandalone()).thenReturn(true);
when(healthChecker.checkNode()).thenReturn(HEALTH_RESULT);

mockMvc.perform(get(HEALTH_ENDPOINT).header(PASSCODE_HTTP_HEADER, VALID_PASSCODE))
.andExpectAll(
status().isOk(),
content().json("""
{
"status":"YELLOW",
"causes":[
"One cause"
]
}"""));
MvcResult mvcResult = mockMvc.perform(get(HEALTH_ENDPOINT).header(PASSCODE_HTTP_HEADER, VALID_PASSCODE))
.andExpect(status().isOk())
.andReturn();

Health actualHealth = gson.fromJson(mvcResult.getResponse().getContentAsString(), Health.class);
assertThat(actualHealth).isEqualTo(HEALTH_RESULT);
}

@Test
public void getSystemHealth_whenAdminCredentialAndStandaloneMode_shouldSucceed() throws Exception {
when(webAppContext.getBean(UserSession.class).isSystemAdministrator()).thenReturn(true);
when(webAppContext.getBean(NodeInformation.class).isStandalone()).thenReturn(true);
userSession.logIn().setSystemAdministrator();
when(nodeInformation.isStandalone()).thenReturn(true);
when(healthChecker.checkNode()).thenReturn(HEALTH_RESULT);

mockMvc.perform(get(HEALTH_ENDPOINT))
.andExpectAll(
status().isOk(),
content().json("""
{
"status":"YELLOW",
"causes":[
"One cause"
]
}"""));
MvcResult mvcResult = mockMvc.perform(get(HEALTH_ENDPOINT))
.andExpect(status().isOk())
.andReturn();

Health actualHealth = gson.fromJson(mvcResult.getResponse().getContentAsString(), Health.class);
assertThat(actualHealth).isEqualTo(HEALTH_RESULT);
}

@Test
@@ -106,8 +99,8 @@ public class HealthControllerIT extends ControllerIT {

@Test
public void getSystemHealth_whenInvalidPasscodeAndNoAdminCredentials_shouldReturnForbidden() throws Exception {
when(webAppContext.getBean(SystemPasscode.class).isValidPasscode(INVALID_PASSCODE)).thenReturn(false);
when(webAppContext.getBean(UserSession.class).isSystemAdministrator()).thenReturn(false);
userSession.logIn();
when(systemPasscode.isValidPasscode(INVALID_PASSCODE)).thenReturn(false);

mockMvc.perform(get(HEALTH_ENDPOINT).header(PASSCODE_HTTP_HEADER, INVALID_PASSCODE))
.andExpectAll(
@@ -115,24 +108,15 @@ public class HealthControllerIT extends ControllerIT {
content().json("{\"message\":\"Insufficient privileges\"}"));
}

@Test
public void getSystemHealth_whenUnauthorizedExceptionThrown_shouldReturnUnauthorized() throws Exception {
when(webAppContext.getBean(UserSession.class).isSystemAdministrator()).thenThrow(new UnauthorizedException("UnauthorizedException"));

mockMvc.perform(get(HEALTH_ENDPOINT))
.andExpectAll(
status().isUnauthorized(),
content().json("{\"message\":\"UnauthorizedException\"}"));
}

@Test
public void getSystemHealth_whenValidPasscodeAndClusterMode_shouldReturnNotImplemented() throws Exception {
when(webAppContext.getBean(SystemPasscode.class).isValidPasscode(VALID_PASSCODE)).thenReturn(true);
when(webAppContext.getBean(NodeInformation.class).isStandalone()).thenReturn(false);
when(systemPasscode.isValidPasscode(VALID_PASSCODE)).thenReturn(true);
when(nodeInformation.isStandalone()).thenReturn(false);

mockMvc.perform(get(HEALTH_ENDPOINT).header(PASSCODE_HTTP_HEADER, VALID_PASSCODE))
.andExpectAll(
status().is(HTTP_NOT_IMPLEMENTED),
content().json("{\"message\":\"Unsupported in cluster mode\"}"));
}

}

+ 276
- 0
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/user/controller/DefaultUserControllerTest.java View File

@@ -0,0 +1,276 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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.sonar.server.v2.api.user.controller;

import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.sonar.db.user.UserDto;
import org.sonar.server.common.SearchResults;
import org.sonar.server.common.user.service.UserSearchResult;
import org.sonar.server.common.user.service.UserService;
import org.sonar.server.common.user.service.UsersSearchRequest;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.v2.api.ControllerTester;
import org.sonar.server.v2.api.user.converter.UsersSearchRestResponseGenerator;
import org.sonar.server.v2.api.user.model.RestUser;
import org.sonar.server.v2.api.user.response.UsersSearchRestResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.server.v2.WebApiEndpoints.USER_ENDPOINT;
import static org.sonar.server.v2.api.model.RestPage.DEFAULT_PAGE_INDEX;
import static org.sonar.server.v2.api.model.RestPage.DEFAULT_PAGE_SIZE;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class DefaultUserControllerTest {

@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
private final UserService userService = mock(UserService.class);
private final MockMvc mockMvc = ControllerTester.getMockMvc(new DefaultUserController(userSession, userService, new UsersSearchRestResponseGenerator(userSession)));

private static final Gson gson = new Gson();

@Test
public void search_whenNoParameters_shouldUseDefaultAndForwardToUserService() throws Exception {
when(userService.findUsers(any())).thenReturn(new SearchResults<>(List.of(), 0));

mockMvc.perform(get(USER_ENDPOINT))
.andExpect(status().isOk())
.andReturn();

ArgumentCaptor<UsersSearchRequest> requestCaptor = ArgumentCaptor.forClass(UsersSearchRequest.class);
verify(userService).findUsers(requestCaptor.capture());
assertThat(requestCaptor.getValue().getPageSize()).isEqualTo(Integer.valueOf(DEFAULT_PAGE_SIZE));
assertThat(requestCaptor.getValue().getPage()).isEqualTo(Integer.valueOf(DEFAULT_PAGE_INDEX));
assertThat(requestCaptor.getValue().isDeactivated()).isFalse();
}

@Test
public void search_whenParametersUsed_shouldForwardWithParameters() throws Exception {
when(userService.findUsers(any())).thenReturn(new SearchResults<>(List.of(), 0));
userSession.logIn().setSystemAdministrator();

mockMvc.perform(get(USER_ENDPOINT)
.param("active", "false")
.param("managed", "true")
.param("q", "q")
.param("sonarQubeLastConnectionDateFrom", "2020-01-01T00:00:00+0100")
.param("sonarQubeLastConnectionDateTo", "2020-01-01T00:00:00+0100")
.param("sonarLintLastConnectionDateFrom", "2020-01-01T00:00:00+0100")
.param("sonarLintLastConnectionDateTo", "2020-01-01T00:00:00+0100")
.param("pageSize", "100")
.param("pageIndex", "2"))
.andExpect(status().isOk())
.andReturn();

ArgumentCaptor<UsersSearchRequest> requestCaptor = ArgumentCaptor.forClass(UsersSearchRequest.class);
verify(userService).findUsers(requestCaptor.capture());
assertThat(requestCaptor.getValue().getPageSize()).isEqualTo(100);
assertThat(requestCaptor.getValue().getPage()).isEqualTo(2);
assertThat(requestCaptor.getValue().isDeactivated()).isTrue();
}

@Test
public void search_whenAdminParametersUsedButNotAdmin_shouldFail() throws Exception {
mockMvc.perform(get(USER_ENDPOINT)
.param("sonarQubeLastConnectionDateFrom", "2020-01-01T00:00:00+0100"))
.andExpectAll(
status().isForbidden(),
content().string("{\"message\":\"parameter sonarQubeLastConnectionDateFrom requires Administer System permission.\"}"));

mockMvc.perform(get(USER_ENDPOINT)
.param("sonarQubeLastConnectionDateTo", "2020-01-01T00:00:00+0100"))
.andExpectAll(
status().isForbidden(),
content().string("{\"message\":\"parameter sonarQubeLastConnectionDateTo requires Administer System permission.\"}"));

mockMvc.perform(get(USER_ENDPOINT)
.param("sonarLintLastConnectionDateFrom", "2020-01-01T00:00:00+0100"))
.andExpectAll(
status().isForbidden(),
content().string("{\"message\":\"parameter sonarLintLastConnectionDateFrom requires Administer System permission.\"}"));

mockMvc.perform(get(USER_ENDPOINT)
.param("sonarLintLastConnectionDateTo", "2020-01-01T00:00:00+0100"))
.andExpectAll(
status().isForbidden(),
content().string("{\"message\":\"parameter sonarLintLastConnectionDateTo requires Administer System permission.\"}"));
}

@Test
public void search_whenUserServiceReturnUsers_shouldReturnThem() throws Exception {
UserSearchResult user1 = generateUserSearchResult("user1", true, true, false, 2, 3);
UserSearchResult user2 = generateUserSearchResult("user2", true, false, false, 3, 0);
UserSearchResult user3 = generateUserSearchResult("user3", true, false, true, 1, 1);
UserSearchResult user4 = generateUserSearchResult("user4", false, true, false, 0, 0);
List<UserSearchResult> users = List.of(user1, user2, user3, user4);
when(userService.findUsers(any())).thenReturn(new SearchResults<>(users, users.size()));
userSession.logIn().setSystemAdministrator();

MvcResult mvcResult = mockMvc.perform(get(USER_ENDPOINT))
.andExpect(status().isOk())
.andReturn();

UsersSearchRestResponse actualUsersSearchRestResponse = gson.fromJson(mvcResult.getResponse().getContentAsString(), UsersSearchRestResponse.class);
assertThat(actualUsersSearchRestResponse.users())
.containsExactlyInAnyOrder(toRestUser(user1), toRestUser(user2), toRestUser(user3), toRestUser(user4));
assertThat(actualUsersSearchRestResponse.pageRestResponse().total()).isEqualTo(users.size());

}

private UserSearchResult generateUserSearchResult(String id, boolean active, boolean local, boolean managed, int groupsCount, int tokensCount) {
UserDto userDto = new UserDto()
.setLogin("login_" + id)
.setUuid("uuid_" + id)
.setName("name_" + id)
.setEmail(id + "@email.com")
.setActive(active)
.setLocal(local)
.setExternalLogin("externalLogin_" + id)
.setExternalId("externalId_" + id)
.setExternalIdentityProvider("externalIdentityProvider_" + id)
.setLastConnectionDate(0L)
.setLastSonarlintConnectionDate(1L);

List<String> groups = new ArrayList<>();
IntStream.range(1, groupsCount).forEach(i -> groups.add("group" + i));

return new UserSearchResult(userDto, managed, Optional.of("avatar_" + id), groups, tokensCount);
}

private RestUser toRestUser(UserSearchResult userSearchResult) {
return new RestUser(
userSearchResult.userDto().getLogin(),
userSearchResult.userDto().getLogin(),
userSearchResult.userDto().getName(),
userSearchResult.userDto().getEmail(),
userSearchResult.userDto().isActive(),
userSearchResult.userDto().isLocal(),
userSearchResult.managed(),
userSearchResult.userDto().getExternalLogin(),
userSearchResult.userDto().getExternalIdentityProvider(),
userSearchResult.avatar().orElse(""),
formatDateTime(userSearchResult.userDto().getLastConnectionDate()),
formatDateTime(userSearchResult.userDto().getLastSonarlintConnectionDate()),
userSearchResult.groups().size(),
userSearchResult.tokensCount()
);
}

@Test
public void deactivate_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
userSession.logIn().setNonSystemAdministrator();

mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
.andExpectAll(
status().isForbidden(),
content().json("{\"message\":\"Insufficient privileges\"}"));
}

@Test
public void deactivate_whenUserServiceThrowsNotFoundException_shouldReturnNotFound() throws Exception {
userSession.logIn().setSystemAdministrator();
doThrow(new NotFoundException("User not found.")).when(userService).deactivate("userToDelete", false);

mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
.andExpectAll(
status().isNotFound(),
content().json("{\"message\":\"User not found.\"}"));
}

@Test
public void deactivate_whenUserServiceThrowsBadRequestException_shouldReturnBadRequest() throws Exception {
userSession.logIn().setSystemAdministrator();
doThrow(BadRequestException.create("Not allowed")).when(userService).deactivate("userToDelete", false);

mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
.andExpectAll(
status().isBadRequest(),
content().json("{\"message\":\"Not allowed\"}"));
}

@Test
public void deactivate_whenUserTryingToDeactivateThemself_shouldReturnBadRequest() throws Exception {
userSession.logIn("userToDelete").setSystemAdministrator();

mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
.andExpectAll(
status().isBadRequest(),
content().json("{\"message\":\"Self-deactivation is not possible\"}"));
}

@Test
public void deactivate_whenAnonymizeParameterIsNotBoolean_shouldReturnBadRequest() throws Exception {
userSession.logIn().setSystemAdministrator();

mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete?anonymize=maybe"))
.andExpect(
status().isBadRequest());
}

@Test
public void deactivate_whenAnonymizeIsNotSpecified_shouldDeactivateUserWithoutAnonymization() throws Exception {
userSession.logIn().setSystemAdministrator();

mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete"))
.andExpect(status().isNoContent());

verify(userService).deactivate("userToDelete", false);
}

@Test
public void deactivate_whenAnonymizeFalse_shouldDeactivateUserWithoutAnonymization() throws Exception {
userSession.logIn().setSystemAdministrator();

mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete").param("anonymize", "false"))
.andExpect(status().isNoContent());

verify(userService).deactivate("userToDelete", false);
}

@Test
public void deactivate_whenAnonymizeTrue_shouldDeactivateUserWithAnonymization() throws Exception {
userSession.logIn().setSystemAdministrator();

mockMvc.perform(delete(USER_ENDPOINT + "/userToDelete").param("anonymize", "true"))
.andExpect(status().isNoContent());

verify(userService).deactivate("userToDelete", true);
}
}

Loading…
Cancel
Save