aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJanos Gyerik <janos.gyerik@sonarsource.com>2019-01-17 07:32:31 +0100
committersonartech <sonartech@sonarsource.com>2019-02-11 09:11:46 +0100
commitdced64bda1caa518354c12c05a70c167ad280a15 (patch)
tree383988f1c17c4acab2d547ad95bb3bcf0e409eb0
parent96e54a89a4cfa87e304d0978d3a829b30deb99ed (diff)
downloadsonarqube-dced64bda1caa518354c12c05a70c167ad280a15.tar.gz
sonarqube-dced64bda1caa518354c12c05a70c167ad280a15.zip
SONAR-11626 Add new WS project_analyses/set_baseline
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SetBaselineAction.java148
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/SetBaselineActionTest.java307
-rw-r--r--sonar-testing-harness/src/main/java/org/sonar/test/Matchers.java48
5 files changed, 507 insertions, 2 deletions
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 158013a5e0f..c5cd2282b3c 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
@@ -25,6 +25,7 @@ import org.sonar.server.projectanalysis.ws.DeleteAction;
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.UpdateEventAction;
public class ProjectAnalysisModule extends Module {
@@ -38,7 +39,8 @@ public class ProjectAnalysisModule extends Module {
UpdateEventAction.class,
DeleteEventAction.class,
DeleteAction.class,
- SearchAction.class);
+ SearchAction.class,
+ SetBaselineAction.class);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SetBaselineAction.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SetBaselineAction.java
new file mode 100644
index 00000000000..7694ae9b9a1
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SetBaselineAction.java
@@ -0,0 +1,148 @@
+/*
+ * 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.core.util.Uuids;
+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.db.component.SnapshotDto;
+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.sonar.server.component.ComponentFinder.ParamNames.PROJECT_ID_AND_KEY;
+import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_ANALYSIS;
+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 SetBaselineAction implements ProjectAnalysesWsAction {
+ private final DbClient dbClient;
+ private final UserSession userSession;
+ private final ComponentFinder componentFinder;
+
+ public SetBaselineAction(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("set_baseline")
+ .setDescription("Set an analysis as the baseline of the New Code Period on a project or a long-lived branch.<br/>" +
+ "This manually set baseline overrides the `sonar.leak.period` setting.<br/>" +
+ "Requires one of the following permissions:" +
+ "<ul>" +
+ " <li>'Administer System'</li>" +
+ " <li>'Administer' rights on the specified project</li>" +
+ "</ul>")
+ .setSince("7.7")
+ .setPost(true)
+ .setHandler(this);
+
+ action.createParam(PARAM_PROJECT)
+ .setDescription("Project key")
+ .setRequired(true);
+
+ action.createParam(PARAM_BRANCH)
+ .setDescription("Branch key");
+
+ action.createParam(PARAM_ANALYSIS)
+ .setDescription("Analysis key")
+ .setExampleValue(Uuids.UUID_EXAMPLE_01)
+ .setRequired(true);
+ }
+
+ @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 = mandatoryNonEmptyParam(request, PARAM_PROJECT);
+ String branchKey = request.param(PARAM_BRANCH);
+ checkArgument(branchKey == null || !branchKey.isEmpty(), "The '%s' parameter must not be empty", PARAM_BRANCH);
+ String analysisUuid = mandatoryNonEmptyParam(request, PARAM_ANALYSIS);
+
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ ComponentDto projectBranch = getProjectBranch(dbSession, projectKey, branchKey);
+ SnapshotDto analysis = getAnalysis(dbSession, analysisUuid);
+ checkRequest(dbSession, projectBranch, branchKey, analysis);
+ dbClient.branchDao().updateManualBaseline(dbSession, projectBranch.uuid(), analysis.getUuid());
+ dbSession.commit();
+ }
+ }
+
+ private static String mandatoryNonEmptyParam(Request request, String param) {
+ String value = request.mandatoryParam(param);
+ checkArgument(!value.isEmpty(), "The '%s' parameter must not be empty", param);
+ return value;
+ }
+
+ private ComponentDto getProjectBranch(DbSession dbSession, String projectKey, @Nullable String branchKey) {
+ if (branchKey == null) {
+ return componentFinder.getByUuidOrKey(dbSession, null, projectKey, PROJECT_ID_AND_KEY);
+ }
+ 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;
+ }
+
+ private SnapshotDto getAnalysis(DbSession dbSession, String analysisUuid) {
+ return dbClient.snapshotDao().selectByUuid(dbSession, analysisUuid)
+ .orElseThrow(() -> new NotFoundException(format("Analysis '%s' is not found", analysisUuid)));
+ }
+
+ private void checkRequest(DbSession dbSession, ComponentDto projectBranch, @Nullable String branchKey, SnapshotDto analysis) {
+ userSession.checkComponentPermission(UserRole.ADMIN, projectBranch);
+ ComponentDto project = dbClient.componentDao().selectByUuid(dbSession, analysis.getComponentUuid()).orElse(null);
+
+ boolean analysisMatchesProjectBranch = project != null && projectBranch.uuid().equals(project.uuid());
+ if (branchKey != null) {
+ checkArgument (analysisMatchesProjectBranch,
+ "Analysis '%s' does not belong to branch '%s' of project '%s'",
+ analysis.getUuid(), branchKey, projectBranch.getKey());
+ } else {
+ checkArgument (analysisMatchesProjectBranch,
+ "Analysis '%s' does not belong to project '%s'",
+ analysis.getUuid(), projectBranch.getKey());
+ }
+ }
+}
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 beee3fa54cb..c4b740dc9f9 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
@@ -30,6 +30,6 @@ public class ProjectAnalysisModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new ProjectAnalysisModule().configure(container);
- assertThat(container.size()).isEqualTo(2 + 6);
+ assertThat(container.size()).isEqualTo(2 + 7);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/SetBaselineActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/SetBaselineActionTest.java
new file mode 100644
index 00000000000..6ea8758481c
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/SetBaselineActionTest.java
@@ -0,0 +1,307 @@
+/*
+ * 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 java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+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.ComponentTesting;
+import org.sonar.db.component.SnapshotDto;
+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 org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_ANALYSIS;
+import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_BRANCH;
+import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_PROJECT;
+import static org.sonar.test.Matchers.regexMatcher;
+import static org.sonarqube.ws.client.WsRequest.Method.POST;
+
+@RunWith(DataProviderRunner.class)
+public class SetBaselineActionTest {
+
+ @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 SetBaselineAction(dbClient, userSession, TestComponentFinder.from(db)));
+
+ @Test
+ public void set_baseline_on_main_branch() {
+ ComponentDto project = ComponentTesting.newPrivateProjectDto(db.organizations().insert());
+ BranchDto branch = new BranchDto()
+ .setBranchType(BranchType.LONG)
+ .setProjectUuid(project.uuid())
+ .setUuid(project.uuid())
+ .setKey("master");
+ db.components().insertComponent(project);
+ db.getDbClient().branchDao().insert(dbSession, branch);
+ SnapshotDto analysis = db.components().insertSnapshot(project);
+ logInAsProjectAdministrator(project);
+
+ call(ImmutableMap.of(PARAM_PROJECT, project.getKey(), PARAM_ANALYSIS, analysis.getUuid()));
+
+ BranchDto loaded = dbClient.branchDao().selectByUuid(dbSession, branch.getUuid()).get();
+ assertThat(loaded.getManualBaseline()).isEqualTo(analysis.getUuid());
+ }
+
+ @Test
+ public void set_baseline_on_long_living_branch() {
+ ComponentDto project = ComponentTesting.newPrivateProjectDto(db.organizations().insert());
+ BranchDto branch = ComponentTesting.newBranchDto(project.projectUuid(), BranchType.LONG);
+ db.components().insertProjectBranch(project, branch);
+ ComponentDto branchComponentDto = ComponentTesting.newProjectBranch(project, branch);
+ SnapshotDto analysis = db.components().insertSnapshot(branchComponentDto);
+ logInAsProjectAdministrator(project);
+
+ call(project.getKey(), branch.getKey(), analysis.getUuid());
+
+ BranchDto loaded = dbClient.branchDao().selectByUuid(dbSession, branch.getUuid()).get();
+ assertThat(loaded.getManualBaseline()).isEqualTo(analysis.getUuid());
+ }
+
+ @Test
+ public void fail_when_user_is_not_admin() {
+ ComponentDto project = ComponentTesting.newPrivateProjectDto(db.organizations().insert());
+ BranchDto branch = ComponentTesting.newBranchDto(project.projectUuid(), BranchType.LONG);
+ db.components().insertProjectBranch(project, branch);
+ ComponentDto branchComponentDto = ComponentTesting.newProjectBranch(project, branch);
+ SnapshotDto analysis = db.components().insertSnapshot(branchComponentDto);
+
+ expectedException.expect(ForbiddenException.class);
+ expectedException.expectMessage("Insufficient privileges");
+
+ call(project.getKey(), branch.getKey(), analysis.getUuid());
+ }
+
+ @Test
+ @UseDataProvider("missingOrEmptyParamsAndFailureMessage")
+ public void fail_with_IAE_when_required_param_missing_or_empty(Map<String, String> params, String message) {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(message);
+
+ call(params);
+ }
+
+ @DataProvider
+ public static Object[][] missingOrEmptyParamsAndFailureMessage() {
+ MapBuilder builder = new MapBuilder()
+ .put(PARAM_PROJECT, "project key")
+ .put(PARAM_BRANCH, "branch key")
+ .put(PARAM_ANALYSIS, "analysis uuid");
+
+ return new Object[][] {
+ {builder.put(PARAM_PROJECT, null).map, "The 'project' parameter is missing"},
+ {builder.put(PARAM_PROJECT, "").map, "The 'project' parameter must not be empty"},
+ {builder.put(PARAM_BRANCH, "").map, "The 'branch' parameter must not be empty"},
+ {builder.put(PARAM_ANALYSIS, null).map, "The 'analysis' parameter is missing"},
+ {builder.put(PARAM_ANALYSIS, "").map, "The 'analysis' parameter must not be empty"},
+ };
+ }
+
+ @Test
+ @UseDataProvider("nonexistentParamsAndFailureMessage")
+ public void fail_with_IAE_when_required_param_nonexistent(Map<String, String> nonexistentParams, String regex) {
+ ComponentDto project = ComponentTesting.newPrivateProjectDto(db.organizations().insert());
+ BranchDto branch = ComponentTesting.newBranchDto(project.projectUuid(), BranchType.LONG);
+ db.components().insertProjectBranch(project, branch);
+ ComponentDto branchComponentDto = ComponentTesting.newProjectBranch(project, branch);
+ SnapshotDto analysis = db.components().insertSnapshot(branchComponentDto);
+ logInAsProjectAdministrator(project);
+
+ Map<String, String> params = new HashMap<>();
+ params.put(PARAM_PROJECT, project.getKey());
+ params.put(PARAM_BRANCH, branch.getKey());
+ params.put(PARAM_ANALYSIS, analysis.getUuid());
+ params.putAll(nonexistentParams);
+
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage(regexMatcher(regex));
+
+ call(params);
+ }
+
+ @DataProvider
+ public static Object[][] nonexistentParamsAndFailureMessage() {
+ MapBuilder builder = new MapBuilder();
+
+ return new Object[][] {
+ {builder.put(PARAM_PROJECT, "nonexistent").map, "Component 'nonexistent' on branch .* not found"},
+ {builder.put(PARAM_BRANCH, "nonexistent").map, "Component .* on branch 'nonexistent' not found"},
+ {builder.put(PARAM_ANALYSIS, "nonexistent").map, "Analysis 'nonexistent' is not found"},
+ };
+ }
+
+ @Test
+ public void fail_when_branch_does_not_belong_to_project() {
+ ComponentDto project = ComponentTesting.newPrivateProjectDto(db.organizations().insert());
+ BranchDto branch = ComponentTesting.newBranchDto(project.projectUuid(), BranchType.LONG);
+ db.components().insertProjectBranch(project, branch);
+ ComponentDto branchComponentDto = ComponentTesting.newProjectBranch(project, branch);
+ SnapshotDto analysis = db.components().insertSnapshot(branchComponentDto);
+ logInAsProjectAdministrator(project);
+
+ ComponentDto otherProject = ComponentTesting.newPrivateProjectDto(db.organizations().insert());
+ BranchDto otherBranch = ComponentTesting.newBranchDto(otherProject.projectUuid(), BranchType.LONG);
+ db.components().insertProjectBranch(otherProject, otherBranch);
+ ComponentTesting.newProjectBranch(otherProject, otherBranch);
+
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage(String.format("Component '%s' on branch '%s' not found", project.getKey(), otherBranch.getKey()));
+
+ call(project.getKey(), otherBranch.getKey(), analysis.getUuid());
+ }
+
+ @Test
+ public void fail_when_analysis_does_not_belong_to_main_branch_of_project() {
+ ComponentDto project = ComponentTesting.newPrivateProjectDto(db.organizations().insert());
+ BranchDto branch = new BranchDto()
+ .setBranchType(BranchType.LONG)
+ .setProjectUuid(project.uuid())
+ .setUuid(project.uuid())
+ .setKey("master");
+ db.components().insertComponent(project);
+ db.getDbClient().branchDao().insert(dbSession, branch);
+ logInAsProjectAdministrator(project);
+
+ ComponentDto otherProject = ComponentTesting.newPrivateProjectDto(db.organizations().insert());
+ SnapshotDto otherAnalysis = db.components().insertSnapshot(otherProject);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(String.format("Analysis '%s' does not belong to project '%s'",
+ otherAnalysis.getUuid(), project.getKey()));
+
+ call(ImmutableMap.of(PARAM_PROJECT, project.getKey(), PARAM_ANALYSIS, otherAnalysis.getUuid()));
+ }
+
+ @Test
+ public void fail_when_analysis_does_not_belong_to_non_main_branch_of_project() {
+ ComponentDto project = ComponentTesting.newPrivateProjectDto(db.organizations().insert());
+ BranchDto branch = ComponentTesting.newBranchDto(project.projectUuid(), BranchType.LONG);
+ db.components().insertProjectBranch(project, branch);
+ logInAsProjectAdministrator(project);
+
+ ComponentDto otherProject = ComponentTesting.newPrivateProjectDto(db.organizations().insert());
+ SnapshotDto otherAnalysis = db.components().insertProjectAndSnapshot(otherProject);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(String.format("Analysis '%s' does not belong to branch '%s' of project '%s'",
+ otherAnalysis.getUuid(), branch.getKey(), project.getKey()));
+
+ call(project.getKey(), branch.getKey(), otherAnalysis.getUuid());
+ }
+
+ @Test
+ public void fail_when_branch_is_not_long() {
+ ComponentDto project = ComponentTesting.newPrivateProjectDto(db.organizations().insert());
+ BranchDto branch = ComponentTesting.newBranchDto(project.projectUuid(), BranchType.SHORT);
+ db.components().insertProjectBranch(project, branch);
+ ComponentDto branchComponentDto = ComponentTesting.newProjectBranch(project, branch);
+ SnapshotDto analysis = db.components().insertSnapshot(branchComponentDto);
+ logInAsProjectAdministrator(project);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(String.format("Not a long-living branch: '%s'", branch.getKey()));
+
+ call(project.getKey(), branch.getKey(), analysis.getUuid());
+ }
+
+ @Test
+ public void ws_parameters() {
+ WebService.Action definition = ws.getDef();
+
+ assertThat(definition.isPost()).isTrue();
+ assertThat(definition.key()).isEqualTo("set_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(Map<String, String> params) {
+ TestRequest httpRequest = ws.newRequest().setMethod(POST.name());
+
+ for (Map.Entry<String, String> param : params.entrySet()) {
+ httpRequest.setParam(param.getKey(), param.getValue());
+ }
+
+ httpRequest.execute();
+ }
+
+ private void call(String projectKey, String branchKey, String analysisUuid) {
+ call(ImmutableMap.of(
+ PARAM_PROJECT, projectKey,
+ PARAM_BRANCH, branchKey,
+ PARAM_ANALYSIS, analysisUuid));
+ }
+
+ private static class MapBuilder {
+ private final Map<String, String> map;
+
+ private MapBuilder() {
+ this.map = Collections.emptyMap();
+ }
+
+ private MapBuilder(Map<String, String> map) {
+ this.map = map;
+ }
+
+ public MapBuilder put(String key, @Nullable String value) {
+ Map<String, String> copy = new HashMap<>(map);
+ if (value == null) {
+ copy.remove(key);
+ } else {
+ copy.put(key, value);
+ }
+ return new MapBuilder(copy);
+ }
+ }
+}
diff --git a/sonar-testing-harness/src/main/java/org/sonar/test/Matchers.java b/sonar-testing-harness/src/main/java/org/sonar/test/Matchers.java
new file mode 100644
index 00000000000..216683f8273
--- /dev/null
+++ b/sonar-testing-harness/src/main/java/org/sonar/test/Matchers.java
@@ -0,0 +1,48 @@
+/*
+ * 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.test;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * Utility class to provide various Matchers to use in ExpectedException.expectMessage.
+ */
+public class Matchers {
+
+ private Matchers() {
+ // utility class, forbidden constructor
+ }
+
+ public static Matcher<String> regexMatcher(String regex) {
+ return new TypeSafeMatcher<String>() {
+ @Override
+ protected boolean matchesSafely(String item) {
+ return item.matches(regex);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("matching regex ").appendValue(regex);
+ }
+ };
+ }
+}