From 58c24e60f03e23a0c83273bc14c535ba5f18dba8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Mon, 28 Jan 2019 17:36:41 +0100 Subject: [PATCH] SONAR-11628 add api/project_analyses/unset_baseline --- .../sonar/db/component/ComponentDbTester.java | 20 ++ .../ProjectAnalysisModule.java | 4 +- .../ws/UnsetBaselineAction.java | 112 +++++++ .../ProjectAnalysisModuleTest.java | 3 +- .../ws/UnsetBaselineActionTest.java | 283 ++++++++++++++++++ 5 files changed, 420 insertions(+), 2 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/UnsetBaselineAction.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/UnsetBaselineActionTest.java diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDbTester.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDbTester.java index d0bd3db10ba..d8ed910582f 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDbTester.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDbTester.java @@ -306,6 +306,26 @@ public class ComponentDbTester { return branch; } + public final void setManualBaseline(ComponentDto longOrMainBranchOfProject, SnapshotDto analysis) { + checkArgument(longOrMainBranchOfProject.isRoot()); + + BranchDto branchDto = db.getDbClient().branchDao().selectByUuid(dbSession, longOrMainBranchOfProject.uuid()) + .orElseThrow(() -> new IllegalArgumentException("BranchDto not found for component " + longOrMainBranchOfProject)); + checkArgument(branchDto.getBranchType() == LONG, "must be a main or a Long Living branch"); + db.getDbClient().branchDao().updateManualBaseline(dbSession, longOrMainBranchOfProject.uuid(), analysis.getUuid()); + db.commit(); + } + + public final void unsetManualBaseline(ComponentDto longOrMainBranchOfProject) { + checkArgument(longOrMainBranchOfProject.isRoot()); + + BranchDto branchDto = db.getDbClient().branchDao().selectByUuid(dbSession, longOrMainBranchOfProject.uuid()) + .orElseThrow(() -> new IllegalArgumentException("BranchDto not found for component " + longOrMainBranchOfProject)); + checkArgument(branchDto.getBranchType() == LONG, "must be a main or a Long Living branch"); + db.getDbClient().branchDao().updateManualBaseline(dbSession, longOrMainBranchOfProject.uuid(), null); + db.commit(); + } + private static T firstNonNull(@Nullable T first, T second) { return (first != null) ? first : second; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java index c5cd2282b3c..bf6642e45b4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java @@ -26,6 +26,7 @@ import org.sonar.server.projectanalysis.ws.DeleteEventAction; import org.sonar.server.projectanalysis.ws.ProjectAnalysesWs; import org.sonar.server.projectanalysis.ws.SearchAction; import org.sonar.server.projectanalysis.ws.SetBaselineAction; +import org.sonar.server.projectanalysis.ws.UnsetBaselineAction; import org.sonar.server.projectanalysis.ws.UpdateEventAction; public class ProjectAnalysisModule extends Module { @@ -40,7 +41,8 @@ public class ProjectAnalysisModule extends Module { DeleteEventAction.class, DeleteAction.class, SearchAction.class, - SetBaselineAction.class); + SetBaselineAction.class, + UnsetBaselineAction.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/UnsetBaselineAction.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/UnsetBaselineAction.java new file mode 100644 index 00000000000..869f434f2bc --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/UnsetBaselineAction.java @@ -0,0 +1,112 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.projectanalysis.ws; + +import com.google.protobuf.Empty; +import javax.annotation.Nullable; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.user.UserSession; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; +import static org.apache.commons.lang.StringUtils.trimToNull; +import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_BRANCH; +import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_PROJECT; +import static org.sonar.server.ws.WsUtils.writeProtobuf; + +public class UnsetBaselineAction implements ProjectAnalysesWsAction { + private final DbClient dbClient; + private final UserSession userSession; + private final ComponentFinder componentFinder; + + public UnsetBaselineAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder) { + this.dbClient = dbClient; + this.userSession = userSession; + this.componentFinder = componentFinder; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("unset_baseline") + .setDescription("Unset any manually-set New Code Period baseline on a project or a long-lived branch.
" + + "Unsetting a manual baseline restores the use of the `sonar.leak.period` setting.
" + + "Requires one of the following permissions:" + + "") + .setSince("7.7") + .setPost(true) + .setHandler(this); + + action.createParam(PARAM_PROJECT) + .setDescription("Project key") + .setRequired(true); + + action.createParam(PARAM_BRANCH) + .setDescription("Branch key"); + } + + @Override + public void handle(Request httpRequest, Response httpResponse) throws Exception { + doHandle(httpRequest); + + writeProtobuf(Empty.newBuilder().build(), httpRequest, httpResponse); + } + + private void doHandle(Request request) { + String projectKey = request.mandatoryParam(PARAM_PROJECT); + String branchKey = trimToNull(request.param(PARAM_BRANCH)); + + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto projectBranch = getProjectBranch(dbSession, projectKey, branchKey); + userSession.checkComponentPermission(UserRole.ADMIN, projectBranch); + + dbClient.branchDao().updateManualBaseline(dbSession, projectBranch.uuid(), null); + dbSession.commit(); + } + } + + private ComponentDto getProjectBranch(DbSession dbSession, String projectKey, @Nullable String branchKey) { + if (branchKey == null) { + return componentFinder.getByKey(dbSession, projectKey); + } + ComponentDto project = componentFinder.getByKeyAndBranch(dbSession, projectKey, branchKey); + + BranchDto branchDto = dbClient.branchDao().selectByUuid(dbSession, project.uuid()) + .orElseThrow(() -> new NotFoundException(format("Branch '%s' is not found", branchKey))); + + checkArgument(branchDto.getBranchType() == BranchType.LONG, + "Not a long-living branch: '%s'", branchKey); + + return project; + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java index c4b740dc9f9..930926b701f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.sonar.core.platform.ComponentContainer; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER; public class ProjectAnalysisModuleTest { @@ -30,6 +31,6 @@ public class ProjectAnalysisModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new ProjectAnalysisModule().configure(container); - assertThat(container.size()).isEqualTo(2 + 7); + assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 8); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/UnsetBaselineActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/UnsetBaselineActionTest.java new file mode 100644 index 00000000000..d99009ee5b8 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/UnsetBaselineActionTest.java @@ -0,0 +1,283 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.projectanalysis.ws; + +import com.google.common.collect.ImmutableMap; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.SnapshotDto; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.server.component.TestComponentFinder; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.WsActionTester; + +import static java.util.Optional.ofNullable; +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.component.ComponentTesting.newBranchDto; +import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; +import static org.sonar.db.component.ComponentTesting.newProjectBranch; +import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_BRANCH; +import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_PROJECT; +import static org.sonarqube.ws.client.WsRequest.Method.POST; + +@RunWith(DataProviderRunner.class) +public class UnsetBaselineActionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + private DbClient dbClient = db.getDbClient(); + private DbSession dbSession = db.getSession(); + + private WsActionTester ws = new WsActionTester(new UnsetBaselineAction(dbClient, userSession, TestComponentFinder.from(db))); + + @Test + public void does_not_fail_and_has_no_effect_when_there_is_no_baseline_on_main_branch() { + ComponentDto project = db.components().insertMainBranch(db.organizations().insert()); + ComponentDto branch = db.components().insertProjectBranch(project); + SnapshotDto analysis = db.components().insertSnapshot(project); + logInAsProjectAdministrator(project); + + call(project.getKey(), null); + + verifyManualBaseline(project, null); + } + + @Test + public void does_not_fail_and_has_no_effect_when_there_is_no_baseline_on_long_living_branch() { + ComponentDto project = db.components().insertMainBranch(db.organizations().insert()); + ComponentDto branch = db.components().insertProjectBranch(project); + SnapshotDto analysis = db.components().insertSnapshot(project); + logInAsProjectAdministrator(project); + + call(project.getKey(), branch.getBranch()); + + verifyManualBaseline(branch, null); + } + + @Test + public void unset_baseline_when_it_is_set_on_main_branch() { + ComponentDto project = db.components().insertMainBranch(db.organizations().insert()); + ComponentDto branch = db.components().insertProjectBranch(project); + SnapshotDto projectAnalysis = db.components().insertSnapshot(project); + SnapshotDto branchAnalysis = db.components().insertSnapshot(project); + db.components().setManualBaseline(project, projectAnalysis); + logInAsProjectAdministrator(project); + + call(project.getKey(), null); + + verifyManualBaseline(project, null); + } + + @Test + public void unset_baseline_when_it_is_set_long_living_branch() { + ComponentDto project = db.components().insertMainBranch(db.organizations().insert()); + ComponentDto branch = db.components().insertProjectBranch(project); + SnapshotDto projectAnalysis = db.components().insertSnapshot(branch); + SnapshotDto branchAnalysis = db.components().insertSnapshot(project); + db.components().setManualBaseline(branch, branchAnalysis); + logInAsProjectAdministrator(project); + + call(project.getKey(), branch.getBranch()); + + verifyManualBaseline(branch, null); + } + + @Test + public void fail_when_user_is_not_admin_on_project() { + ComponentDto project = db.components().insertMainBranch(db.organizations().insert()); + db.components().insertProjectBranch(project); + + expectedException.expect(ForbiddenException.class); + expectedException.expectMessage("Insufficient privileges"); + + call(project.getKey(), null); + } + + @Test + public void fail_when_user_is_not_admin_on_project_of_branch() { + ComponentDto project = db.components().insertMainBranch(db.organizations().insert()); + ComponentDto branch = db.components().insertProjectBranch(project); + + expectedException.expect(ForbiddenException.class); + expectedException.expectMessage("Insufficient privileges"); + + call(project.getKey(), branch.getBranch()); + } + + @Test + @UseDataProvider("nullOrEmptyOrValue") + public void fail_with_IAE_when_missing_project_parameter(@Nullable String branchParam) { + ComponentDto project = db.components().insertMainBranch(db.organizations().insert()); + db.components().insertProjectBranch(project); + logInAsProjectAdministrator(project); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The 'project' parameter is missing"); + + call(null, branchParam); + } + + @Test + @UseDataProvider("nullOrEmptyOrValue") + public void fail_with_IAE_when_project_parameter_empty(@Nullable String branchParam) { + ComponentDto project = db.components().insertMainBranch(db.organizations().insert()); + db.components().insertProjectBranch(project); + logInAsProjectAdministrator(project); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The 'project' parameter is missing"); + + call("", branchParam); + } + + @DataProvider + public static Object[][] nullOrEmptyOrValue() { + return new Object[][] { + {null}, + {""}, + {randomAlphabetic(10)}, + }; + } + + @Test + @UseDataProvider("nullOrEmpty") + public void does_not_fail_with_IAE_when_missing_branch_parameter(@Nullable String branchParam) { + ComponentDto project = db.components().insertMainBranch(db.organizations().insert()); + db.components().insertProjectBranch(project); + logInAsProjectAdministrator(project); + + call(project.getKey(), branchParam); + } + + @DataProvider + public static Object[][] nullOrEmpty() { + return new Object[][] { + {null}, + {""}, + }; + } + + @DataProvider + public static Object[][] nonexistentParamsAndFailureMessage() { + return new Object[][] { + {ImmutableMap.of(PARAM_PROJECT, "nonexistent"), "Component 'nonexistent' on branch .* not found"}, + {ImmutableMap.of(PARAM_BRANCH, "nonexistent"), "Component .* on branch 'nonexistent' not found"} + }; + } + + @Test + public void fail_when_branch_does_not_belong_to_project() { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertMainBranch(organization); + ComponentDto branch = db.components().insertProjectBranch(project); + ComponentDto otherProject = db.components().insertMainBranch(organization); + ComponentDto otherBranch = db.components().insertProjectBranch(otherProject); + logInAsProjectAdministrator(project); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage(String.format("Component '%s' on branch '%s' not found", project.getKey(), otherBranch.getKey())); + + call(project.getKey(), otherBranch.getKey()); + } + + @Test + public void fail_with_IAE_when_branch_is_short() { + ComponentDto project = newPrivateProjectDto(db.organizations().insert()); + BranchDto branch = newBranchDto(project.projectUuid(), BranchType.SHORT); + db.components().insertProjectBranch(project, branch); + logInAsProjectAdministrator(project); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage(String.format("Not a long-living branch: '%s'", branch.getKey())); + + call(project.getKey(), branch.getKey()); + } + + @Test + public void fail_with_NotFoundException_when_branch_is_pull_request() { + ComponentDto project = newPrivateProjectDto(db.organizations().insert()); + BranchDto branch = newBranchDto(project.projectUuid(), BranchType.LONG); + db.components().insertProjectBranch(project, branch); + ComponentDto pullRequest = newProjectBranch(project, branch); + logInAsProjectAdministrator(project); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage(String.format("Component '%s' on branch '%s' not found", project.getKey(), pullRequest.getKey())); + + call(project.getKey(), pullRequest.getKey()); + } + + @Test + public void verify_ws_parameters() { + WebService.Action definition = ws.getDef(); + + assertThat(definition.isPost()).isTrue(); + assertThat(definition.key()).isEqualTo("unset_baseline"); + assertThat(definition.since()).isEqualTo("7.7"); + assertThat(definition.isInternal()).isFalse(); + } + + private void logInAsProjectAdministrator(ComponentDto project) { + userSession.logIn().addProjectPermission(UserRole.ADMIN, project); + } + + private void call(@Nullable String project, @Nullable String branchName) { + TestRequest httpRequest = ws.newRequest().setMethod(POST.name()); + ofNullable(project).ifPresent(t -> httpRequest.setParam(PARAM_PROJECT, t)); + ofNullable(branchName).ifPresent(t -> httpRequest.setParam(PARAM_BRANCH, t)); + + httpRequest.execute(); + } + + private void verifyManualBaseline(ComponentDto project, @Nullable SnapshotDto projectAnalysis) { + BranchDto branchDto = db.getDbClient().branchDao().selectByUuid(dbSession, project.uuid()).get(); + if (projectAnalysis == null) { + assertThat(branchDto.getManualBaseline()).isNull(); + } else { + assertThat(branchDto.getManualBaseline()).isEqualTo(projectAnalysis.getUuid()); + } + } + +} -- 2.39.5