aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester
diff options
context:
space:
mode:
authorBenjamin Campomenosi <109955405+benjamin-campomenosi-sonarsource@users.noreply.github.com>2023-11-22 14:43:42 +0100
committersonartech <sonartech@sonarsource.com>2023-11-22 20:02:41 +0000
commit703ddf8c4872ae82b70e96d7f3f060a2733e41fd (patch)
tree5a43fd2703b5bb668779e5bd9fc3853441bfb0ba /sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester
parent17072178fa9a64aa99fde55cf3efeae75593a84e (diff)
downloadsonarqube-703ddf8c4872ae82b70e96d7f3f060a2733e41fd.tar.gz
sonarqube-703ddf8c4872ae82b70e96d7f3f060a2733e41fd.zip
SONAR-20824 make Tester open source
Diffstat (limited to 'sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester')
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/AlmSettingsTester.java68
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ApplicationTester.java353
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/Elasticsearch.java84
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/GroupTester.java106
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/MeasureTester.java105
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/NewCodePeriodTester.java65
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/PermissionTester.java128
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ProjectTester.java101
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QGateTester.java126
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QModelTester.java63
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QProfileTester.java136
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/SettingTester.java114
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/Tester.java403
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/TesterSession.java56
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java218
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ViewTester.java517
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/WebhookTester.java107
17 files changed, 2750 insertions, 0 deletions
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/AlmSettingsTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/AlmSettingsTester.java
new file mode 100644
index 00000000000..71679136ccf
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/AlmSettingsTester.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+/*
+* Copyright (C) 2009-2023 SonarSource SA
+* All rights reserved
+* mailto:info AT sonarsource DOT com
+*/
+package org.sonarqube.ws.tester;
+
+import org.junit.rules.ExternalResource;
+import org.sonarqube.ws.AlmSettings;
+import org.sonarqube.ws.client.HttpException;
+import org.sonarqube.ws.client.almsettings.AlmSettingsService;
+import org.sonarqube.ws.client.almsettings.CreateGithubRequest;
+import org.sonarqube.ws.client.almsettings.DeleteRequest;
+
+public class AlmSettingsTester extends ExternalResource {
+
+ private final TesterSession session;
+
+ AlmSettingsTester(TesterSession session) {
+ this.session = session;
+ }
+
+ public void addGitHubAlmSettings(String key) {
+ session.wsClient().almSettings().createGithub(new CreateGithubRequest()
+ .setClientId("id1")
+ .setAppId("app1")
+ .setClientSecret("shhh")
+ .setKey(key).setPrivateKey("PRIV")
+ .setUrl("http://example.org"));
+ }
+
+ void deleteAll() {
+ AlmSettingsService almSettingsService = session.wsClient().almSettings();
+ try {
+ AlmSettings.ListDefinitionsWsResponse response = almSettingsService.listDefinitions();
+ response.getGithubList().forEach(e -> almSettingsService.delete(new DeleteRequest(e.getKey())));
+ response.getAzureList().forEach(e -> almSettingsService.delete(new DeleteRequest(e.getKey())));
+ response.getBitbucketList().forEach(e -> almSettingsService.delete(new DeleteRequest(e.getKey())));
+ response.getBitbucketcloudList().forEach(e -> almSettingsService.delete(new DeleteRequest(e.getKey())));
+ response.getGitlabList().forEach(e -> almSettingsService.delete(new DeleteRequest(e.getKey())));
+ } catch (HttpException e) {
+ // If server is not at least a developer edition, the ws is not available, nothing to do
+ if (e.code() == 404) {
+ return;
+ }
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ApplicationTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ApplicationTester.java
new file mode 100644
index 00000000000..e24e5aeeb31
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ApplicationTester.java
@@ -0,0 +1,353 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import com.google.common.util.concurrent.Uninterruptibles;
+import com.google.gson.Gson;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.junit.rules.ExternalResource;
+import org.sonarqube.ws.Ce;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.applications.ApplicationsService;
+import org.sonarqube.ws.client.applications.CreateRequest;
+import org.sonarqube.ws.client.applications.DeleteRequest;
+import org.sonarqube.ws.client.applications.SearchProjectsRequest;
+import org.sonarqube.ws.client.applications.ShowRequest;
+import org.sonarqube.ws.client.applications.UpdateRequest;
+import org.sonarqube.ws.client.ce.ActivityStatusRequest;
+import org.sonarqube.ws.client.projects.ProjectsService;
+import org.sonarqube.ws.client.projects.SearchRequest;
+
+import static java.util.Arrays.stream;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ApplicationTester extends ExternalResource {
+
+ private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
+
+ private final TesterSession session;
+
+ ApplicationTester(TesterSession session) {
+ this.session = session;
+ }
+
+ public ApplicationsService service() {
+ return session.wsClient().applications();
+ }
+
+ public void deleteAll() {
+ ProjectsService service = session.wsClient().projects();
+ service.search(new SearchRequest().setQualifiers(singletonList("APP"))).getComponentsList()
+ .forEach(p -> {
+ waitForCeQueueEmpty();
+ session.wsClient().applications().delete(new DeleteRequest().setApplication(p.getKey()));
+ });
+ waitForCeQueueEmpty();
+
+ org.sonarqube.ws.client.components.SearchRequest searchRequest = new org.sonarqube.ws.client.components.SearchRequest().setQualifiers(singletonList("APP"));
+ assertThat(session.wsClient().components().search(searchRequest).getComponentsList()).isEmpty();
+ }
+
+ public void updateName(String applicationKey, String name) {
+ service().update(new UpdateRequest().setApplication(applicationKey).setName(name));
+ }
+
+ public ApplicationTester waitForCeQueueEmpty() {
+ Ce.ActivityStatusWsResponse status;
+ boolean empty;
+ do {
+ status = session.wsClient().ce().activityStatus(new ActivityStatusRequest());
+ empty = status.getInProgress() + status.getPending() == 0;
+ if (!empty) {
+ Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
+ }
+
+ } while (!empty);
+
+ return this;
+ }
+
+ @SafeVarargs
+ public final Application generate(Consumer<CreateRequest>... populators) {
+ int id = ID_GENERATOR.getAndIncrement();
+ CreateRequest request = new CreateRequest()
+ .setKey("applicationKey" + id)
+ .setName("applicationName" + id)
+ .setDescription("applicationDescription" + id);
+ stream(populators).forEach(p -> p.accept(request));
+ return CreateResponse.parse(session.wsClient().applications().create(request)).getApplication();
+ }
+
+ public ShowResponse show(ShowRequest showRequest) {
+ return ShowResponse.parse(session.wsClient().applications().show(showRequest));
+ }
+
+ public void refresh() {
+ session.wsClient().wsConnector().call(new PostRequest("/api/applications/refresh")).failIfNotSuccessful();
+ waitForCeQueueEmpty();
+ }
+
+ public SearchProjectsResponse searchProjects(SearchProjectsRequest searchProjectsRequest) {
+ return SearchProjectsResponse.parse(session.wsClient().applications().searchProjects(searchProjectsRequest));
+ }
+
+ public static class CreateResponse {
+ private final Application application;
+
+ public CreateResponse(Application application) {
+ this.application = application;
+ }
+
+ public Application getApplication() {
+ return application;
+ }
+
+ public static CreateResponse parse(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, CreateResponse.class);
+ }
+ }
+
+ public static class ShowResponse {
+ private final Application application;
+
+ public ShowResponse(Application application) {
+ this.application = application;
+ }
+
+ public Application getApplication() {
+ return application;
+ }
+
+ public static ShowResponse parse(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, ShowResponse.class);
+ }
+ }
+
+ public static class SearchProjectsResponse {
+ private final Paging paging;
+ private final List<Project> projects;
+
+ public SearchProjectsResponse(Paging paging, List<Project> projects) {
+ this.paging = paging;
+ this.projects = projects;
+ }
+
+ public Paging getPaging() {
+ return paging;
+ }
+
+ public List<Project> getProjects() {
+ return projects;
+ }
+
+ public static SearchProjectsResponse parse(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, SearchProjectsResponse.class);
+ }
+
+ public static class Project {
+ private final String key;
+ private final String name;
+ private final boolean enabled;
+ private final boolean selected;
+
+ public Project(String key, String name, boolean enabled, boolean selected) {
+ this.key = key;
+ this.name = name;
+ this.enabled = enabled;
+ this.selected = selected;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public boolean isSelected() {
+ return selected;
+ }
+ }
+ }
+
+ public static class Paging {
+ public final int pageIndex;
+ public final int pageSize;
+ public final int total;
+
+ public Paging(int pageIndex, int pageSize, int total) {
+ this.pageIndex = pageIndex;
+ this.pageSize = pageSize;
+ this.total = total;
+ }
+
+ public int getPageIndex() {
+ return pageIndex;
+ }
+
+ public int getPageSize() {
+ return pageSize;
+ }
+
+ public int getTotal() {
+ return total;
+ }
+ }
+
+ public static class Application {
+ private final String key;
+ private final String branch;
+ private final boolean isMain;
+ private final String name;
+ private final String description;
+ private final String visibility;
+ private final List<Project> projects;
+ private final List<Application.Branch> branches;
+ private final List<String> tags;
+
+ public Application(String key, String branch, boolean isMain, String name, String description, String visibility, List<Project> projects, List<Branch> branches,
+ List<String> tags) {
+ this.key = key;
+ this.branch = branch;
+ this.isMain = isMain;
+ this.name = name;
+ this.description = description;
+ this.visibility = visibility;
+ this.projects = projects;
+ this.branches = branches;
+ this.tags = tags;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getBranch() {
+ return branch;
+ }
+
+ public boolean isMain() {
+ return isMain;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getVisibility() {
+ return visibility;
+ }
+
+ public List<Application.Project> getProjects() {
+ return projects;
+ }
+
+ public List<String> getTags() {
+ return tags;
+ }
+
+ @CheckForNull
+ public List<Application.Branch> getBranches() {
+ return branches;
+ }
+
+ public static class Project {
+ private final String key;
+ private final String branch;
+ private final Boolean isMain;
+ private final String name;
+ private final boolean enabled;
+ private final Boolean selected;
+
+ public Project(String key, String branch, @Nullable Boolean isMain, String name, boolean enabled, @Nullable Boolean selected) {
+ this.key = key;
+ this.branch = branch;
+ this.isMain = isMain;
+ this.name = name;
+ this.enabled = enabled;
+ this.selected = selected;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ @CheckForNull
+ public String getBranch() {
+ return branch;
+ }
+
+ @CheckForNull
+ public Boolean isMain() {
+ return isMain;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ @CheckForNull
+ public Boolean isSelected() {
+ return selected;
+ }
+ }
+
+ public static class Branch {
+ private final String name;
+ private final boolean isMain;
+
+ public Branch(String name, boolean isMain) {
+ this.name = name;
+ this.isMain = isMain;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isMain() {
+ return isMain;
+ }
+ }
+ }
+
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/Elasticsearch.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/Elasticsearch.java
new file mode 100644
index 00000000000..7383326393f
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/Elasticsearch.java
@@ -0,0 +1,84 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Helper to directly access Elasticsearch. It requires the HTTP port
+ * to be open.
+ */
+public class Elasticsearch {
+
+ private final int httpPort;
+
+ public Elasticsearch(int httpPort) {
+ this.httpPort = httpPort;
+ }
+
+ /**
+ * Forbid indexing requests on the specified index. Index becomes read-only.
+ */
+ public void lockWrites(String index) throws IOException {
+ putIndexSetting(httpPort, index, "blocks.write", "true");
+ }
+
+ /**
+ * Enable indexing requests on the specified index.
+ * @see #lockWrites(String)
+ */
+ public void unlockWrites(String index) throws IOException {
+ putIndexSetting(httpPort, index, "blocks.write", "false");
+ }
+
+ public void makeYellow() throws IOException {
+ putIndexSetting(httpPort, "issues", "number_of_replicas", "5");
+ }
+
+ public void makeGreen() throws IOException {
+ putIndexSetting(httpPort, "issues", "number_of_replicas", "0");
+ }
+
+ private static void putIndexSetting(int searchHttpPort, String index, String key, String value) throws IOException {
+ Request.Builder request = new Request.Builder()
+ .url(baseUrl(searchHttpPort) + index + "/_settings")
+ .put(RequestBody.create(MediaType.parse("application/json"), "{" +
+ " \"index\" : {" +
+ " \"" + key + "\" : \"" + value + "\"" +
+ " }" +
+ "}"));
+ OkHttpClient okClient = new OkHttpClient.Builder().build();
+ try (Response response = okClient.newCall(request.build()).execute()) {
+ assertThat(response.isSuccessful()).isTrue();
+ }
+ }
+
+ private static String baseUrl(int searchHttpPort) {
+ return "http://" + InetAddress.getLoopbackAddress().getHostAddress() + ":" + searchHttpPort + "/";
+ }
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/GroupTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/GroupTester.java
new file mode 100644
index 00000000000..bf36e3b4d8e
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/GroupTester.java
@@ -0,0 +1,106 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.assertj.core.api.Assertions;
+import org.sonarqube.ws.UserGroups;
+import org.sonarqube.ws.Users;
+import org.sonarqube.ws.Users.GroupsWsResponse.Group;
+import org.sonarqube.ws.client.usergroups.AddUserRequest;
+import org.sonarqube.ws.client.usergroups.CreateRequest;
+import org.sonarqube.ws.client.usergroups.DeleteRequest;
+import org.sonarqube.ws.client.usergroups.SearchRequest;
+import org.sonarqube.ws.client.users.GroupsRequest;
+
+import static java.util.Arrays.stream;
+
+public class GroupTester {
+
+ private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
+
+ private final TesterSession session;
+
+ GroupTester(TesterSession session) {
+ this.session = session;
+ }
+
+ @SafeVarargs
+ public final UserGroups.Group generate(Consumer<CreateRequest>... populators) {
+ int id = ID_GENERATOR.getAndIncrement();
+ CreateRequest request = new CreateRequest()
+ .setName("Group" + id)
+ .setDescription("Description " + id);
+ stream(populators).forEach(p -> p.accept(request));
+ return session.wsClient().userGroups().create(request).getGroup();
+ }
+
+ public List<UserGroups.Group> getGroups(String partialGroupName) {
+ SearchRequest searchRequest = new SearchRequest();
+ searchRequest.setQ(partialGroupName);
+ return session.wsClient().userGroups().search(searchRequest).getGroupsList();
+ }
+
+ public List<Group> getGroupsOfUser(String userLogin) {
+ GroupsRequest request = new GroupsRequest().setLogin(userLogin);
+ Users.GroupsWsResponse response = session.users().service().groups(request);
+ return response.getGroupsList();
+ }
+
+ public GroupTester addMemberToGroups(String userLogin, String... groups) {
+ for (String group : groups) {
+ AddUserRequest request = new AddUserRequest()
+ .setLogin(userLogin)
+ .setName(group);
+ session.wsClient().userGroups().addUser(request);
+ }
+ return this;
+ }
+
+ public GroupTester assertThatUserIsOnlyMemberOf(String userLogin, String... expectedGroups) {
+ Set<String> groups = getGroupsOfUser(userLogin).stream()
+ .map(Group::getName)
+ .collect(Collectors.toSet());
+ Assertions.assertThat(groups).containsExactlyInAnyOrder(expectedGroups);
+ return this;
+ }
+
+ public GroupTester deleteAllGenerated() {
+ List<String> allGroups = session.wsClient().userGroups().search(new SearchRequest()).getGroupsList().stream().map(UserGroups.Group::getName)
+ .collect(Collectors.toList());
+ allGroups.stream()
+ .filter(g -> g.matches("Group\\d+$"))
+ .forEach(g -> session.wsClient().userGroups().delete(new DeleteRequest().setName(g)));
+ return this;
+ }
+
+ public GroupTester delete(UserGroups.Group... groups) {
+ List<String> allGroups = session.wsClient().userGroups().search(new SearchRequest()).getGroupsList().stream().map(UserGroups.Group::getName)
+ .collect(Collectors.toList());
+ stream(groups)
+ .filter(g -> allGroups.contains(g.getName()))
+ .forEach(g -> session.wsClient().userGroups().delete(new DeleteRequest().setName(g.getName())));
+ return this;
+ }
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/MeasureTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/MeasureTester.java
new file mode 100644
index 00000000000..43d7cae98a5
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/MeasureTester.java
@@ -0,0 +1,105 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.annotation.CheckForNull;
+import org.sonarqube.ws.Measures;
+import org.sonarqube.ws.Measures.Measure;
+import org.sonarqube.ws.client.measures.ComponentRequest;
+
+import static java.lang.Double.parseDouble;
+import static java.util.Collections.singletonList;
+
+public class MeasureTester {
+
+ private final TesterSession session;
+
+ MeasureTester(TesterSession session) {
+ this.session = session;
+ }
+
+
+ @CheckForNull
+ public Measure getMeasure(String componentKey, String metricKey) {
+ return getMeasuresByMetricKey(componentKey, metricKey).get(metricKey);
+ }
+
+ @CheckForNull
+ public Double getMeasureAsDouble(String componentKey, String metricKey) {
+ Measure measure = getMeasure(componentKey, metricKey);
+ return (measure == null) ? null : parseDouble(measure.getValue());
+ }
+
+ public Map<String, Measure> getMeasuresByMetricKey(String componentKey, String... metricKeys) {
+ return getStreamMeasures(componentKey, metricKeys)
+ .filter(Measure::hasValue)
+ .collect(Collectors.toMap(Measure::getMetric, Function.identity()));
+ }
+
+ public Map<String, Double> getMeasuresAsDoubleByMetricKey(String componentKey, String... metricKeys) {
+ return getStreamMeasures(componentKey, metricKeys)
+ .filter(Measure::hasValue)
+ .collect(Collectors.toMap(Measure::getMetric, measure -> parseDouble(measure.getValue())));
+ }
+
+ private Stream<Measure> getStreamMeasures(String componentKey, String... metricKeys) {
+ return session.wsClient().measures().component(new ComponentRequest()
+ .setComponent(componentKey)
+ .setMetricKeys(Arrays.asList(metricKeys)))
+ .getComponent().getMeasuresList()
+ .stream();
+ }
+
+ @CheckForNull
+ public Measure getMeasureWithVariation(String componentKey, String metricKey) {
+ Measures.ComponentWsResponse response = session.wsClient().measures().component(new ComponentRequest()
+ .setComponent(componentKey)
+ .setMetricKeys(singletonList(metricKey))
+ .setAdditionalFields(singletonList("period")));
+ List<Measure> measures = response.getComponent().getMeasuresList();
+ return measures.size() == 1 ? measures.get(0) : null;
+ }
+
+ @CheckForNull
+ public Map<String, Measure> getMeasuresWithVariationsByMetricKey(String componentKey, String... metricKeys) {
+ return session.wsClient().measures().component(new ComponentRequest()
+ .setComponent(componentKey)
+ .setMetricKeys(Arrays.asList(metricKeys))
+ .setAdditionalFields(singletonList("period"))).getComponent().getMeasuresList()
+ .stream()
+ .collect(Collectors.toMap(Measure::getMetric, Function.identity()));
+ }
+
+ /**
+ * Return leak period value
+ */
+ @CheckForNull
+ public Double getLeakPeriodValue(String componentKey, String metricKey) {
+ Measures.PeriodValue periodsValue = getMeasureWithVariation(componentKey, metricKey).getPeriod();
+ return parseDouble(periodsValue.getValue());
+ }
+
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/NewCodePeriodTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/NewCodePeriodTester.java
new file mode 100644
index 00000000000..3f1ae914495
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/NewCodePeriodTester.java
@@ -0,0 +1,65 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import javax.annotation.Nullable;
+import org.sonarqube.ws.NewCodePeriods;
+import org.sonarqube.ws.client.newcodeperiods.NewCodePeriodsService;
+import org.sonarqube.ws.client.newcodeperiods.SetRequest;
+import org.sonarqube.ws.client.newcodeperiods.ShowRequest;
+import org.sonarqube.ws.client.newcodeperiods.UnsetRequest;
+
+public class NewCodePeriodTester {
+
+ private final TesterSession session;
+
+ NewCodePeriodTester(TesterSession session) {
+ this.session = session;
+ }
+
+ public NewCodePeriodsService service() {
+ return session.wsClient().newCodePeriods();
+ }
+
+ public void setGlobal(String type, @Nullable String value) {
+ set(null, null, type, value);
+ }
+
+ public void set(@Nullable String projectKey, @Nullable String branchKey, String type, @Nullable String value) {
+ session.wsClient().newCodePeriods().set(new SetRequest()
+ .setProject(projectKey)
+ .setBranch(branchKey)
+ .setType(type)
+ .setValue(value));
+ }
+
+ public void unset(@Nullable String projectKey, @Nullable String branchKey) {
+ session.wsClient().newCodePeriods().unset(new UnsetRequest()
+ .setProject(projectKey)
+ .setBranch(branchKey));
+ }
+
+ public NewCodePeriods.ShowWSResponse show(@Nullable String projectKey, @Nullable String branchKey) {
+ return session.wsClient().newCodePeriods().show(new ShowRequest()
+ .setProject(projectKey)
+ .setBranch(branchKey));
+ }
+
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/PermissionTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/PermissionTester.java
new file mode 100644
index 00000000000..8f1f630c7dd
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/PermissionTester.java
@@ -0,0 +1,128 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import org.sonarqube.ws.Permissions.PermissionTemplate;
+import org.sonarqube.ws.Permissions.SearchTemplatesWsResponse.TemplateIdQualifier;
+import org.sonarqube.ws.Projects.CreateWsResponse.Project;
+import org.sonarqube.ws.Users;
+import org.sonarqube.ws.client.permissions.AddGroupRequest;
+import org.sonarqube.ws.client.permissions.AddGroupToTemplateRequest;
+import org.sonarqube.ws.client.permissions.AddProjectCreatorToTemplateRequest;
+import org.sonarqube.ws.client.permissions.AddUserRequest;
+import org.sonarqube.ws.client.permissions.AddUserToTemplateRequest;
+import org.sonarqube.ws.client.permissions.ApplyTemplateRequest;
+import org.sonarqube.ws.client.permissions.CreateTemplateRequest;
+import org.sonarqube.ws.client.permissions.PermissionsService;
+import org.sonarqube.ws.client.permissions.SearchTemplatesRequest;
+import org.sonarqube.ws.client.permissions.SetDefaultTemplateRequest;
+
+import static com.sonar.orchestrator.container.Server.ADMIN_LOGIN;
+import static java.util.Arrays.stream;
+
+public class PermissionTester {
+
+ private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
+
+ private final TesterSession session;
+
+ PermissionTester(TesterSession session) {
+ this.session = session;
+ }
+
+ @SafeVarargs
+ public final PermissionTemplate generateTemplate(Consumer<CreateTemplateRequest>... populators) {
+ int id = ID_GENERATOR.getAndIncrement();
+ String name = "template" + id;
+ CreateTemplateRequest request = new CreateTemplateRequest()
+ .setName(name);
+ stream(populators).forEach(p -> p.accept(request));
+ PermissionTemplate template = service().createTemplate(request).getPermissionTemplate();
+ // Give browse and admin permissions to admin in order to allow admin wsclient to perform any operation on created projects
+ addUserToTemplate(ADMIN_LOGIN, template, "user");
+ addUserToTemplate(ADMIN_LOGIN, template, "admin");
+ return template;
+ }
+
+ public void addUserToTemplate(Users.CreateWsResponse.User user, PermissionTemplate template, String permission) {
+ addUserToTemplate(user.getLogin(), template, permission);
+ }
+
+ public void addUserToTemplate(String login, PermissionTemplate template, String permission) {
+ service().addUserToTemplate(new AddUserToTemplateRequest()
+ .setLogin(login)
+ .setTemplateName(template.getName())
+ .setPermission(permission));
+ }
+
+ public void addGroup(String groupName, String permission) {
+ service().addGroup(new AddGroupRequest().setGroupName(groupName).setPermission(permission));
+ }
+
+ public void addUser(String login, String permission) {
+ service().addUser(new AddUserRequest().setLogin(login).setPermission(permission));
+ }
+
+ public void addGroupToTemplate(String groupName, PermissionTemplate template, String permission) {
+ service().addGroupToTemplate(new AddGroupToTemplateRequest()
+ .setGroupName(groupName)
+ .setTemplateName(template.getName())
+ .setPermission(permission));
+ }
+
+ public void addCreatorToTemplate(PermissionTemplate template, String permission) {
+ this.service().addProjectCreatorToTemplate(
+ new AddProjectCreatorToTemplateRequest()
+ .setPermission(permission)
+ .setTemplateId(template.getId()));
+ }
+
+ public void applyTemplate(PermissionTemplate template, Project project) {
+ service().applyTemplate(
+ new ApplyTemplateRequest()
+ .setTemplateName(template.getName())
+ .setProjectKey(project.getKey()));
+ }
+
+ public TemplateIdQualifier getDefaultTemplateForProject() {
+ return service().searchTemplates(new SearchTemplatesRequest()).getDefaultTemplatesList()
+ .stream()
+ .filter(t -> t.getQualifier().equals("TRK"))
+ .findFirst()
+ .orElseThrow(() -> {
+ throw new IllegalStateException("Cannot find default template for project");
+ });
+ }
+
+ public void setDefaultTemplate(TemplateIdQualifier template) {
+ service().setDefaultTemplate(new SetDefaultTemplateRequest().setTemplateId(template.getTemplateId()));
+ }
+
+ public void setDefaultTemplate(PermissionTemplate template) {
+ service().setDefaultTemplate(new SetDefaultTemplateRequest().setTemplateId(template.getId()));
+ }
+
+ public PermissionsService service() {
+ return session.wsClient().permissions();
+ }
+
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ProjectTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ProjectTester.java
new file mode 100644
index 00000000000..4eb52272bb8
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ProjectTester.java
@@ -0,0 +1,101 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+import org.sonarqube.ws.Components;
+import org.sonarqube.ws.Projects;
+import org.sonarqube.ws.client.HttpException;
+import org.sonarqube.ws.client.WsResponse;
+import org.sonarqube.ws.client.components.ShowRequest;
+import org.sonarqube.ws.client.projects.CreateRequest;
+import org.sonarqube.ws.client.projects.DeleteRequest;
+import org.sonarqube.ws.client.projects.ExportFindingsRequest;
+import org.sonarqube.ws.client.projects.ProjectsService;
+import org.sonarqube.ws.client.projects.SearchRequest;
+
+import static java.util.Arrays.stream;
+import static java.util.Collections.singletonList;
+
+public class ProjectTester {
+
+ private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
+
+ private final TesterSession session;
+
+ ProjectTester(TesterSession session) {
+ this.session = session;
+ }
+
+ void deleteAll() {
+ ProjectsService service = session.wsClient().projects();
+ service.search(new SearchRequest().setQualifiers(singletonList("TRK"))).getComponentsList().forEach(p -> service.delete(new DeleteRequest().setProject(p.getKey())));
+ }
+
+ public ProjectsService service() {
+ return session.wsClient().projects();
+ }
+
+ public WsResponse exportFindings(String projectKey, @Nullable String branchKey) {
+ ProjectsService service = session.wsClient().projects();
+ return service.exportFindings(new ExportFindingsRequest(projectKey, branchKey));
+ }
+
+ @SafeVarargs
+ public final Projects.CreateWsResponse.Project provision(Consumer<CreateRequest>... populators) {
+ String key = generateKey();
+ CreateRequest request = new CreateRequest()
+ .setProject(key)
+ .setName("Name " + key);
+ stream(populators).forEach(p -> p.accept(request));
+
+ return session.wsClient().projects().create(request).getProject();
+ }
+
+ public Components.Component getComponent(String componentKey) {
+ try {
+ return session.wsClient().components().show(new ShowRequest().setComponent((componentKey))).getComponent();
+ } catch (org.sonarqube.ws.client.HttpException e) {
+ if (e.code() == 404) {
+ return null;
+ }
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public boolean exists(String projectKey) {
+ try {
+ Components.ShowWsResponse response = session.wsClient().components().show(new ShowRequest().setComponent(projectKey));
+ return response.getComponent() != null;
+ } catch (HttpException e) {
+ if (e.code() == 404) {
+ return false;
+ }
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public String generateKey() {
+ int id = ID_GENERATOR.getAndIncrement();
+ return "key" + id;
+ }
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QGateTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QGateTester.java
new file mode 100644
index 00000000000..232949dffa5
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QGateTester.java
@@ -0,0 +1,126 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import com.google.gson.Gson;
+import com.google.gson.annotations.SerializedName;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.sonarqube.ws.Projects.CreateWsResponse.Project;
+import org.sonarqube.ws.client.qualitygates.CreateRequest;
+import org.sonarqube.ws.client.qualitygates.DestroyRequest;
+import org.sonarqube.ws.client.qualitygates.ListRequest;
+import org.sonarqube.ws.client.qualitygates.QualitygatesService;
+import org.sonarqube.ws.client.qualitygates.SelectRequest;
+import org.sonarqube.ws.client.qualitygates.SetAsDefaultRequest;
+
+import static java.util.Arrays.stream;
+import static org.sonarqube.ws.Qualitygates.CreateResponse;
+import static org.sonarqube.ws.Qualitygates.ListWsResponse;
+
+public class QGateTester {
+ private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
+
+ private final TesterSession session;
+
+ QGateTester(TesterSession session) {
+ this.session = session;
+ }
+
+ public QualitygatesService service() {
+ return session.wsClient().qualitygates();
+ }
+
+ void deleteAll() {
+ List<ListWsResponse.QualityGate> builtInQualityGates = session.wsClient().qualitygates().list(new ListRequest()).getQualitygatesList().stream()
+ .filter(ListWsResponse.QualityGate::getIsBuiltIn)
+ .collect(Collectors.toList());
+ if (builtInQualityGates.size() == 1) {
+ session.wsClient().qualitygates().setAsDefault(new SetAsDefaultRequest().setName(builtInQualityGates.get(0).getName()));
+ }
+ session.wsClient().qualitygates().list(new ListRequest()).getQualitygatesList().stream()
+ .filter(qualityGate -> !qualityGate.getIsDefault())
+ .filter(qualityGate -> !qualityGate.getIsBuiltIn())
+ .forEach(qualityGate -> session.wsClient().qualitygates().destroy(new DestroyRequest().setName(qualityGate.getName())));
+ }
+
+ @SafeVarargs
+ public final CreateResponse generate(Consumer<CreateRequest>... populators) {
+ int id = ID_GENERATOR.getAndIncrement();
+ CreateRequest request = new CreateRequest()
+ .setName("QualityGate " + id);
+ stream(populators).forEach(p -> p.accept(request));
+ return session.wsClient().qualitygates().create(request);
+ }
+
+ public void associateProject(CreateResponse qualityGate, Project project) {
+ service().select(new SelectRequest()
+ .setGateName(qualityGate.getName())
+ .setProjectKey(project.getKey()));
+ }
+
+ public static class ListResponse {
+
+ @SerializedName("default")
+ private final String defaultQGate;
+ @SerializedName("qualitygates")
+ private final List<QGate> qualityGates = new ArrayList<>();
+
+ public ListResponse(String defaultQGate) {
+ this.defaultQGate = defaultQGate;
+ }
+
+ public List<QGate> getQualityGates() {
+ return qualityGates;
+ }
+
+ public String getDefault() {
+ return defaultQGate;
+ }
+
+ public static ListResponse parse(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, ListResponse.class);
+ }
+
+ public static class QGate {
+ @SerializedName("id")
+ private final String id;
+ @SerializedName("name")
+ private final String name;
+
+ public QGate(String id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+ }
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QModelTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QModelTester.java
new file mode 100644
index 00000000000..10822e828ef
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QModelTester.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+/*
+* Copyright (C) 2009-2023 SonarSource SA
+* All rights reserved
+* mailto:info AT sonarsource DOT com
+*/
+package org.sonarqube.ws.tester;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+
+public class QModelTester {
+
+ private static final String DEV_COST_PROPERTY = "sonar.technicalDebt.developmentCost";
+ private static final String RATING_GRID_PROPERTY = "sonar.technicalDebt.ratingGrid";
+ private static final String DEV_COST_LANGUAGE_PROPERTY = "languageSpecificParameters";
+ private static final String DEV_COST_LANGUAGE_NAME_PROPERTY = DEV_COST_LANGUAGE_PROPERTY + ".0.language";
+ private static final String DEV_COST_LANGUAGE_COST_PROPERTY = DEV_COST_LANGUAGE_PROPERTY + ".0.man_days";
+ private static final Joiner COMMA_JOINER = Joiner.on(",");
+
+ private final TesterSession session;
+
+ QModelTester(TesterSession session) {
+ this.session = session;
+ }
+
+ public void updateDevelopmentCost(int developmentCost) {
+ session.settings().setGlobalSettings(DEV_COST_PROPERTY, Integer.toString(developmentCost));
+ }
+
+ public void updateLanguageDevelopmentCost(String language, int developmentCost) {
+ session.settings().setGlobalSettings(DEV_COST_LANGUAGE_PROPERTY, "0");
+ session.settings().setGlobalSettings(DEV_COST_LANGUAGE_NAME_PROPERTY, language);
+ session.settings().setGlobalSettings(DEV_COST_LANGUAGE_COST_PROPERTY, Integer.toString(developmentCost));
+ }
+
+ public void updateRatingGrid(Double... ratingGrid) {
+ Preconditions.checkState(ratingGrid.length == 4, "Rating grid must contains 4 values");
+ session.settings().setGlobalSettings(RATING_GRID_PROPERTY, COMMA_JOINER.join(ratingGrid));
+ }
+
+ public void reset() {
+ session.settings().resetSettings(DEV_COST_LANGUAGE_PROPERTY, DEV_COST_LANGUAGE_NAME_PROPERTY, DEV_COST_LANGUAGE_COST_PROPERTY, RATING_GRID_PROPERTY, DEV_COST_PROPERTY);
+ }
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QProfileTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QProfileTester.java
new file mode 100644
index 00000000000..99202a15657
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QProfileTester.java
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+/*
+* Copyright (C) 2009-2023 SonarSource SA
+* All rights reserved
+* mailto:info AT sonarsource DOT com
+*/
+package org.sonarqube.ws.tester;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.sonarqube.ws.Projects.CreateWsResponse.Project;
+import org.sonarqube.ws.Qualityprofiles;
+import org.sonarqube.ws.Qualityprofiles.CreateWsResponse.QualityProfile;
+import org.sonarqube.ws.client.qualityprofiles.ActivateRuleRequest;
+import org.sonarqube.ws.client.qualityprofiles.AddProjectRequest;
+import org.sonarqube.ws.client.qualityprofiles.CreateRequest;
+import org.sonarqube.ws.client.qualityprofiles.DeactivateRuleRequest;
+import org.sonarqube.ws.client.qualityprofiles.DeleteRequest;
+import org.sonarqube.ws.client.qualityprofiles.QualityprofilesService;
+import org.sonarqube.ws.client.qualityprofiles.SearchRequest;
+
+import static java.util.Arrays.stream;
+
+public class QProfileTester {
+ private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
+
+ private final TesterSession session;
+
+ QProfileTester(TesterSession session) {
+ this.session = session;
+ }
+
+ public QualityprofilesService service() {
+ return session.wsClient().qualityprofiles();
+ }
+
+ void deleteAll() {
+ List<Qualityprofiles.SearchWsResponse.QualityProfile> qualityProfiles = session.wsClient().qualityprofiles().search(new SearchRequest()).getProfilesList().stream()
+ .filter(qp -> !qp.getIsDefault())
+ .filter(qp -> !qp.getIsBuiltIn())
+ .filter(qp -> qp.getParentKey() == null || qp.getParentKey().equals(""))
+ .collect(Collectors.toList());
+
+ qualityProfiles.forEach(
+ qp -> session.wsClient().qualityprofiles().delete(new DeleteRequest().setQualityProfile(qp.getName()).setLanguage(qp.getLanguage())));
+ }
+
+ @SafeVarargs
+ public final QualityProfile createXooProfile(Consumer<CreateRequest>... populators) {
+ CreateRequest request = new CreateRequest()
+ .setLanguage("xoo")
+ .setName(generateName());
+ stream(populators).forEach(p -> p.accept(request));
+ return service().create(request).getProfile();
+ }
+
+ public QProfileTester activateRule(QualityProfile profile, String ruleKey) {
+ return activateRule(profile.getKey(), ruleKey);
+ }
+
+ public QProfileTester activateRule(String profileKey, String ruleKey) {
+ ActivateRuleRequest request = new ActivateRuleRequest()
+ .setKey(profileKey)
+ .setRule(ruleKey);
+ service().activateRule(request);
+ return this;
+ }
+
+ public QProfileTester activateRule(QualityProfile profile, String ruleKey, String severity, List<String> params) {
+ return activateRule(profile.getKey(), ruleKey, severity, params);
+ }
+
+ public QProfileTester activateRule(QualityProfile profile, String ruleKey, String severity) {
+ return activateRule(profile.getKey(), ruleKey, severity, null);
+ }
+
+ public QProfileTester activateRule(String profileKey, String ruleKey, String severity) {
+ return activateRule(profileKey, ruleKey, severity, null);
+ }
+
+ public QProfileTester activateRule(String profileKey, String ruleKey, String severity, @Nullable List<String> params) {
+ service().activateRule(new ActivateRuleRequest()
+ .setKey(profileKey)
+ .setRule(ruleKey)
+ .setSeverity(severity)
+ .setParams(params));
+ return this;
+ }
+
+ public QProfileTester deactivateRule(QualityProfile profile, String ruleKey) {
+ service().deactivateRule(new DeactivateRuleRequest().setKey(profile.getKey()).setRule(ruleKey));
+ return this;
+ }
+
+ public QProfileTester assignQProfileToProject(QualityProfile profile, Project project) {
+ return assignQProfileToProject(profile, project.getKey());
+ }
+
+ public QProfileTester assignQProfileToProject(QualityProfile profile, String projectKey) {
+ return assignQProfileToProject(profile.getName(), profile.getLanguage(), projectKey);
+ }
+
+ public QProfileTester assignQProfileToProject(String profileName, String profileLanguage, String projectKey) {
+ service().addProject(new AddProjectRequest()
+ .setProject(projectKey)
+ .setQualityProfile(profileName)
+ .setLanguage(profileLanguage));
+ return this;
+ }
+
+ public String generateName() {
+ int id = ID_GENERATOR.getAndIncrement();
+ return "Profile" + id;
+ }
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/SettingTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/SettingTester.java
new file mode 100644
index 00000000000..abc45cacac0
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/SettingTester.java
@@ -0,0 +1,114 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.sonarqube.ws.Settings;
+import org.sonarqube.ws.Settings.Setting;
+import org.sonarqube.ws.client.settings.ListDefinitionsRequest;
+import org.sonarqube.ws.client.settings.ResetRequest;
+import org.sonarqube.ws.client.settings.SetRequest;
+import org.sonarqube.ws.client.settings.SettingsService;
+import org.sonarqube.ws.client.settings.ValuesRequest;
+
+import static java.util.Arrays.asList;
+
+public class SettingTester {
+
+ private static final Set<String> EMAIL_SETTINGS = ImmutableSet.of("email.smtp_host.secured", "email.smtp_port.secured", "email.smtp_secure_connection.secured",
+ "email.smtp_username.secured", "email.smtp_password.secured", "email.from", "email.prefix");
+
+ private final TesterSession session;
+
+ SettingTester(TesterSession session) {
+ this.session = session;
+ }
+
+ public SettingsService service() {
+ return session.wsClient().settings();
+ }
+
+ public void deleteAll() {
+ List<String> settingKeys = Stream.concat(
+ session.wsClient().settings().listDefinitions(new ListDefinitionsRequest()).getDefinitionsList()
+ .stream()
+ .filter(def -> def.getType() != Settings.Type.LICENSE)
+ .map(Settings.Definition::getKey)
+ .filter(key -> !key.equals(Tester.FORCE_AUTHENTICATION_PROPERTY_NAME)),
+ EMAIL_SETTINGS.stream())
+ .collect(Collectors.toList());
+ session.wsClient().settings().reset(new ResetRequest().setKeys(settingKeys));
+ }
+
+ public void resetSettings(String... keys) {
+ session.wsClient().settings().reset(new ResetRequest().setKeys(asList(keys)));
+ }
+
+ public List<Setting> getGlobalSettings(String... keys) {
+ return session.wsClient().settings().values(new ValuesRequest().setKeys(asList(keys)))
+ .getSettingsList();
+ }
+
+ public void resetProjectSettings(String projectKey, String... keys) {
+ session.wsClient().settings().reset(new ResetRequest().setComponent(projectKey).setKeys(asList(keys)));
+ }
+
+ public void setGlobalSetting(String key, @Nullable String value) {
+ setSetting(null, key, value);
+ }
+
+ public void setMultiValuesGlobalSetting(String key, String... values) {
+ session.wsClient().settings().set(new SetRequest()
+ .setKey(key)
+ .setValues(Arrays.asList(values))
+ .setComponent(null));
+ }
+
+ public void setGlobalSettings(String... keyValues) {
+ for (int i = 0; i < keyValues.length; i += 2) {
+ setSetting(null, keyValues[i], keyValues[i + 1]);
+ }
+ }
+
+ public void setProjectSetting(String componentKey, String key, @Nullable String value) {
+ setSetting(componentKey, key, value);
+ }
+
+ public void setProjectSettings(String componentKey, String... properties) {
+ for (int i = 0; i < properties.length; i += 2) {
+ setSetting(componentKey, properties[i], properties[i + 1]);
+ }
+ }
+
+ private void setSetting(@Nullable String componentKey, String key, @Nullable String value) {
+ if (value == null) {
+ session.wsClient().settings().reset(new ResetRequest().setKeys(asList(key)).setComponent(componentKey));
+ } else {
+ session.wsClient().settings().set(new SetRequest().setKey(key).setValue(value).setComponent(componentKey));
+ }
+ }
+
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/Tester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/Tester.java
new file mode 100644
index 00000000000..e5c26ee45c1
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/Tester.java
@@ -0,0 +1,403 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import com.google.common.base.Preconditions;
+import com.sonar.orchestrator.container.Edition;
+import com.sonar.orchestrator.container.Server;
+import com.sonar.orchestrator.junit4.OrchestratorRule;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.junit.rules.ExternalResource;
+import org.sonarqube.ws.Ce;
+import org.sonarqube.ws.client.HttpConnector;
+import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.WsClientFactories;
+import org.sonarqube.ws.client.ce.ActivityRequest;
+
+import static com.sonar.orchestrator.container.Edition.DEVELOPER;
+import static com.sonar.orchestrator.container.Edition.ENTERPRISE;
+import static java.lang.String.format;
+import static java.util.Arrays.stream;
+import static org.sonarqube.ws.client.HttpConnector.DEFAULT_READ_TIMEOUT_MILLISECONDS;
+
+/**
+ * This JUnit rule wraps an {@link OrchestratorRule} instance and provides :
+ * <ul>
+ * <li>clean-up of users between tests</li>
+ * <li>clean-up of session when opening a browser (cookies, local storage)</li>
+ * <li>quick access to {@link WsClient} instances</li>
+ * <li>clean-up of defined settings. Properties that are not defined by a plugin are not reset.</li>
+ * <li>helpers to generate users</li>
+ * </ul>
+ * <p>
+ * Recommendation is to define a {@code @Rule} instance. If not possible, then
+ * {@code @ClassRule} must be used through a {@link org.junit.rules.RuleChain}
+ * around {@link OrchestratorRule}.
+ * <p>
+ * Not supported:
+ * <ul>
+ * <li>clean-up global settings</li>
+ * <li>clean-up system administrators/roots</li>
+ * <li>clean-up the properties that are not defined (no PropertyDefinition)</li>
+ * </ul>
+ */
+public class Tester extends ExternalResource implements TesterSession {
+ static final String FORCE_AUTHENTICATION_PROPERTY_NAME = "sonar.forceAuthentication";
+
+ private final OrchestratorRule orchestrator;
+
+ private boolean enableForceAuthentication = false;
+ private Elasticsearch elasticsearch = null;
+
+ // initialized in #before()
+ private boolean beforeCalled = false;
+ private TesterSession rootSession;
+
+ private final int readTimeoutMilliseconds;
+
+ public Tester(OrchestratorRule orchestrator) {
+ this(orchestrator, DEFAULT_READ_TIMEOUT_MILLISECONDS);
+ }
+
+ public Tester(OrchestratorRule orchestrator, int readTimeoutMilliseconds) {
+ this.orchestrator = orchestrator;
+ this.readTimeoutMilliseconds = readTimeoutMilliseconds;
+ }
+
+ public Tester enableForceAuthentication() {
+ verifyNotStarted();
+ enableForceAuthentication = true;
+ return this;
+ }
+
+ @Override
+ public void before() {
+ verifyNotStarted();
+ rootSession = new TesterSessionImpl(orchestrator,
+ httpConnectorBuilder -> httpConnectorBuilder.readTimeoutMilliseconds(readTimeoutMilliseconds),
+ httpConnectorBuilder -> httpConnectorBuilder.credentials("admin", "admin"));
+
+ setForceAuthentication(enableForceAuthentication);
+
+ beforeCalled = true;
+ }
+
+ @Override
+ public void after() {
+ waitForCeTasksToFinish();
+
+ deactivateScim();
+
+ users().deleteAll();
+ projects().deleteAll();
+ settings().deleteAll();
+ qGates().deleteAll();
+ qProfiles().deleteAll();
+ webhooks().deleteAllGlobal();
+ almSettings().deleteAll();
+ groups().deleteAllGenerated();
+
+ Edition edition = orchestrator.getDistribution().getEdition();
+ if (edition.equals(ENTERPRISE)) {
+ applications().deleteAll();
+ views().deleteAll();
+ applications().deleteAll();
+ } else if (edition.equals(DEVELOPER)) {
+ applications().deleteAll();
+ }
+
+ setForceAuthentication(enableForceAuthentication);
+ }
+
+ public void deactivateScim() {
+ try (Connection connection = orchestrator.getDatabase().openConnection();
+ PreparedStatement preparedStatement = connection.prepareStatement("delete from internal_properties where kee = ?")) {
+ preparedStatement.setString(1, "sonar.scim.enabled");
+ preparedStatement.execute();
+ } catch (SQLException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private void setForceAuthentication(boolean enableForceAuthentication) {
+ String serverProperty = orchestrator.getDistribution().getServerProperty(FORCE_AUTHENTICATION_PROPERTY_NAME);
+ if (serverProperty != null) {
+ Preconditions.checkArgument(enableForceAuthentication == Boolean.parseBoolean(serverProperty),
+ "This test was expecting to have authentication configured, but server property configuration has mismatched.");
+ return;
+ }
+
+ if (enableForceAuthentication) {
+ settings().resetSettings(FORCE_AUTHENTICATION_PROPERTY_NAME);
+ } else {
+ settings().setGlobalSetting(FORCE_AUTHENTICATION_PROPERTY_NAME, Boolean.toString(false));
+ }
+ }
+
+ private void waitForCeTasksToFinish() {
+ // Let's try to wait for 30s for in progress or pending tasks to finish
+ int counter = 60;
+ while (counter > 0 &&
+ wsClient().ce().activity(new ActivityRequest().setStatus(List.of("PENDING", "IN_PROGRESS"))).getTasksCount() != 0) {
+ counter--;
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ }
+
+ Ce.ActivityResponse activity = wsClient().ce().activity(new ActivityRequest().setStatus(List.of("PENDING", "IN_PROGRESS")));
+ if (activity.getTasksCount() != 0) {
+ throw new IllegalStateException(format("Waiting for 30 seconds for tasks to finish but there are still ce tasks : %n %s",
+ activity.getTasksList().stream()
+ .map(t -> format("analysisId: [%s] type: [%s] componentName: [%s]", t.getAnalysisId(), t.getType(), t.getComponentName()))
+ .collect(Collectors.joining("\n"))));
+ }
+ }
+
+ public TesterSession asAnonymous() {
+ return as(null, null);
+ }
+
+ public TesterSession as(String login) {
+ return as(login, login);
+ }
+
+ public TesterSession as(String login, String password) {
+ verifyStarted();
+ return new TesterSessionImpl(orchestrator, login, password);
+ }
+
+ public TesterSession withSystemPassCode(String systemPassCode) {
+ verifyStarted();
+ return new TesterSessionImpl(orchestrator, systemPassCode);
+ }
+
+ public Elasticsearch elasticsearch() {
+ if (elasticsearch != null) {
+ return elasticsearch;
+ }
+ elasticsearch = new Elasticsearch(orchestrator.getServer().getSearchPort());
+ return elasticsearch;
+ }
+
+ private void verifyNotStarted() {
+ if (beforeCalled) {
+ throw new IllegalStateException("org.sonarqube.ws.tester.Tester should not be already started");
+ }
+ }
+
+ private void verifyStarted() {
+ if (!beforeCalled) {
+ throw new IllegalStateException("org.sonarqube.ws.tester.Tester is not started yet");
+ }
+ }
+
+ /**
+ * Web service client configured with root access
+ */
+ @Override
+ public WsClient wsClient() {
+ verifyStarted();
+ return rootSession.wsClient();
+ }
+
+ @Override
+ public GroupTester groups() {
+ return rootSession.groups();
+ }
+
+ @Override
+ public ProjectTester projects() {
+ return rootSession.projects();
+ }
+
+ @Override
+ public QModelTester qModel() {
+ return rootSession.qModel();
+ }
+
+ @Override
+ public QProfileTester qProfiles() {
+ return rootSession.qProfiles();
+ }
+
+ @Override
+ public UserTester users() {
+ return rootSession.users();
+ }
+
+ @Override
+ public SettingTester settings() {
+ return rootSession.settings();
+ }
+
+ @Override
+ public NewCodePeriodTester newCodePeriods() {
+ return rootSession.newCodePeriods();
+ }
+
+ @Override
+ public QGateTester qGates() {
+ return rootSession.qGates();
+ }
+
+ @Override
+ public WebhookTester webhooks() {
+ return rootSession.webhooks();
+ }
+
+ @Override
+ public PermissionTester permissions() {
+ return rootSession.permissions();
+ }
+
+ @Override
+ public ViewTester views() {
+ return rootSession.views();
+ }
+
+ @Override
+ public ApplicationTester applications() {
+ return rootSession.applications();
+ }
+
+ @Override
+ public MeasureTester measures() {
+ return rootSession.measures();
+ }
+
+ @Override
+ public AlmSettingsTester almSettings() {
+ return rootSession.almSettings();
+ }
+
+ private static class TesterSessionImpl implements TesterSession {
+ private final WsClient client;
+
+ private TesterSessionImpl(OrchestratorRule orchestrator, @Nullable String login, @Nullable String password) {
+ Server server = orchestrator.getServer();
+ this.client = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()
+ .url(server.getUrl())
+ .credentials(login, password)
+ .build());
+ }
+
+ private TesterSessionImpl(OrchestratorRule orchestrator, @Nullable String systemPassCode) {
+ Server server = orchestrator.getServer();
+ this.client = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()
+ .systemPassCode(systemPassCode)
+ .url(server.getUrl())
+ .build());
+ }
+
+ private TesterSessionImpl(OrchestratorRule orchestrator, Consumer<HttpConnector.Builder>... httpConnectorPopulators) {
+ Server server = orchestrator.getServer();
+ HttpConnector.Builder httpConnectorBuilder = HttpConnector.newBuilder()
+ .url(server.getUrl());
+ stream(httpConnectorPopulators).forEach(populator -> populator.accept(httpConnectorBuilder));
+ this.client = WsClientFactories.getDefault().newClient(httpConnectorBuilder.build());
+ }
+
+ @Override
+ public WsClient wsClient() {
+ return client;
+ }
+
+ @Override
+ public GroupTester groups() {
+ return new GroupTester(this);
+ }
+
+ @Override
+ public ProjectTester projects() {
+ return new ProjectTester(this);
+ }
+
+ @Override
+ public QModelTester qModel() {
+ return new QModelTester(this);
+ }
+
+ @Override
+ public QProfileTester qProfiles() {
+ return new QProfileTester(this);
+ }
+
+ @Override
+ public UserTester users() {
+ return new UserTester(this);
+ }
+
+ @Override
+ public SettingTester settings() {
+ return new SettingTester(this);
+ }
+
+ @Override
+ public NewCodePeriodTester newCodePeriods() {
+ return new NewCodePeriodTester(this);
+ }
+
+ @Override
+ public QGateTester qGates() {
+ return new QGateTester(this);
+ }
+
+ @Override
+ public WebhookTester webhooks() {
+ return new WebhookTester(this);
+ }
+
+ @Override
+ public PermissionTester permissions() {
+ return new PermissionTester(this);
+ }
+
+ @Override
+ public ViewTester views() {
+ return new ViewTester(this);
+ }
+
+ @Override
+ public ApplicationTester applications() {
+ return new ApplicationTester(this);
+ }
+
+ @Override
+ public MeasureTester measures() {
+ return new MeasureTester(this);
+ }
+
+ @Override
+ public AlmSettingsTester almSettings() {
+ return new AlmSettingsTester(this);
+ }
+
+ }
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/TesterSession.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/TesterSession.java
new file mode 100644
index 00000000000..762ac260d56
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/TesterSession.java
@@ -0,0 +1,56 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import org.sonarqube.ws.client.WsClient;
+
+public interface TesterSession {
+
+ WsClient wsClient();
+
+ GroupTester groups();
+
+ ProjectTester projects();
+
+ QModelTester qModel();
+
+ QProfileTester qProfiles();
+
+ UserTester users();
+
+ SettingTester settings();
+
+ NewCodePeriodTester newCodePeriods();
+
+ QGateTester qGates();
+
+ WebhookTester webhooks();
+
+ PermissionTester permissions();
+
+ ViewTester views();
+
+ ApplicationTester applications();
+
+ MeasureTester measures();
+
+ AlmSettingsTester almSettings();
+
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java
new file mode 100644
index 00000000000..7966560052b
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java
@@ -0,0 +1,218 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import com.google.common.collect.MoreCollectors;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.sonarqube.ws.UserTokens;
+import org.sonarqube.ws.Users;
+import org.sonarqube.ws.Users.CreateWsResponse.User;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.WsResponse;
+import org.sonarqube.ws.client.usergroups.AddUserRequest;
+import org.sonarqube.ws.client.users.ChangePasswordRequest;
+import org.sonarqube.ws.client.users.CreateRequest;
+import org.sonarqube.ws.client.users.SearchRequest;
+import org.sonarqube.ws.client.users.UpdateIdentityProviderRequest;
+import org.sonarqube.ws.client.users.UpdateRequest;
+import org.sonarqube.ws.client.users.UsersService;
+import org.sonarqube.ws.client.usertokens.GenerateRequest;
+
+import static java.util.Arrays.stream;
+
+public class UserTester {
+
+ private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
+
+ private final TesterSession session;
+
+ UserTester(TesterSession session) {
+ this.session = session;
+ }
+
+ void deleteAll() {
+ session.wsClient().users().search(new SearchRequest()).getUsersList()
+ .stream()
+ .filter(u -> !"admin".equals(u.getLogin()))
+ .forEach(u -> {
+ PostRequest request = new PostRequest("api/users/deactivate").setParam("login", u.getLogin());
+ try (final WsResponse response = session.wsClient().wsConnector().call(request)) {
+ response.failIfNotSuccessful();
+ }
+ });
+ }
+
+ public final String generateToken(String login) {
+ int id = ID_GENERATOR.getAndIncrement();
+ String name = "token" + id;
+ session.wsClient().userTokens().generate(new GenerateRequest().setLogin(login).setName(name));
+ return name;
+ }
+
+ public final String generateToken(String login, String type, @Nullable String projectKey) {
+ int id = ID_GENERATOR.getAndIncrement();
+ String name = "token" + id;
+ UserTokens.GenerateWsResponse response = session.wsClient().userTokens()
+ .generate(new GenerateRequest().setLogin(login).setName(name).setType(type).setProjectKey(projectKey));
+ return response.getToken();
+ }
+
+ public final String generateToken(String login, Consumer<GenerateRequest>... populators) {
+ int id = ID_GENERATOR.getAndIncrement();
+ String name = "token" + id;
+
+ GenerateRequest generateRequest = new GenerateRequest()
+ .setName(name)
+ .setLogin(login);
+ stream(populators).forEach(p -> p.accept(generateRequest));
+ UserTokens.GenerateWsResponse response = session.wsClient().userTokens()
+ .generate(generateRequest);
+ return response.getToken();
+ }
+
+ @SafeVarargs
+ public final User generate(Consumer<CreateRequest>... populators) {
+ int id = ID_GENERATOR.getAndIncrement();
+ String login = "login" + id;
+ CreateRequest request = new CreateRequest()
+ .setLogin(login)
+ .setPassword(login)
+ .setName("name" + id)
+ .setEmail(id + "@test.com");
+ stream(populators).forEach(p -> p.accept(request));
+ return service().create(request).getUser();
+ }
+
+ @SafeVarargs
+ public final User generateApplicationCreator(Consumer<CreateRequest>... populators) {
+ User u = generate(populators);
+ session.wsClient().permissions().addUser(
+ new org.sonarqube.ws.client.permissions.AddUserRequest()
+ .setLogin(u.getLogin())
+ .setPermission("applicationcreator"));
+ return u;
+ }
+
+ @SafeVarargs
+ public final User generatePortfolioCreator(Consumer<CreateRequest>... populators) {
+ User u = generate(populators);
+ session.wsClient().permissions().addUser(
+ new org.sonarqube.ws.client.permissions.AddUserRequest()
+ .setLogin(u.getLogin())
+ .setPermission("portfoliocreator"));
+ return u;
+ }
+
+ /**
+ * For standalone mode only
+ */
+ @SafeVarargs
+ public final User generateAdministrator(Consumer<CreateRequest>... populators) {
+ User user = generate(populators);
+ session.wsClient().permissions().addUser(new org.sonarqube.ws.client.permissions.AddUserRequest().setLogin(user.getLogin()).setPermission("admin"));
+ session.wsClient().userGroups().addUser(new AddUserRequest().setLogin(user.getLogin()).setName("sonar-administrators"));
+ return user;
+ }
+
+ public UsersService service() {
+ return session.wsClient().users();
+ }
+
+ public Optional<Users.SearchWsResponse.User> getByExternalLogin(String externalLogin) {
+ return getAllUsers().stream()
+ .filter(user -> user.getExternalIdentity().equals(externalLogin))
+ .collect(MoreCollectors.toOptional());
+ }
+
+ public List<Users.SearchWsResponse.User> getAllUsers() {
+ return service().search(new SearchRequest()).getUsersList();
+ }
+
+ public Optional<Users.SearchWsResponse.User> getDeactivatedUserByExternalLogin(String externalLogin) {
+ return getAllDeactivatedUsers().stream()
+ .filter(user -> user.getExternalIdentity().equals(externalLogin))
+ .collect(MoreCollectors.toOptional());
+ }
+
+ public List<Users.SearchWsResponse.User> getAllDeactivatedUsers() {
+ return service().search(new SearchRequest().setDeactivated(true)).getUsersList();
+ }
+
+ public Optional<Users.SearchWsResponse.User> getByLogin(String login) {
+ return queryForUser(login, t -> t.getLogin().equals(login));
+ }
+
+ public Optional<Users.SearchWsResponse.User> getByEmail(String email) {
+ return queryForUser(email, t -> t.getEmail().equals(email));
+ }
+
+ public Optional<Users.SearchWsResponse.User> getByName(String name) {
+ return queryForUser(name, t -> t.getName().equals(name));
+ }
+
+ public void changePassword(String login, String previousPassword, String newPassword) {
+ service().changePassword(new ChangePasswordRequest().setLogin(login).setPreviousPassword(previousPassword).setPassword(newPassword));
+ }
+
+ private Optional<Users.SearchWsResponse.User> queryForUser(String login, Predicate<Users.SearchWsResponse.User> predicate) {
+ List<Users.SearchWsResponse.User> users = session.wsClient().users().search(new SearchRequest().setQ(login)).getUsersList().stream()
+ .filter(predicate).collect(Collectors.toList());
+ if (users.size() == 1) {
+ return Optional.of(users.get(0));
+ }
+ return Optional.empty();
+ }
+
+ public final String generateLogin() {
+ int id = ID_GENERATOR.getAndIncrement();
+ return "login" + id;
+ }
+
+ public final String generateProviderId() {
+ int id = ID_GENERATOR.getAndIncrement();
+ return "providerId" + id;
+ }
+
+ public final String generateEmail() {
+ int id = ID_GENERATOR.getAndIncrement();
+ return "email" + id + "@test.com";
+ }
+
+ public void updateIdentityProvider(String login, String externalProvider, @Nullable String externalIdentity) {
+ session.wsClient().users().updateIdentityProvider(
+ new UpdateIdentityProviderRequest()
+ .setLogin(login)
+ .setNewExternalProvider(externalProvider)
+ .setNewExternalIdentity(externalIdentity));
+ }
+
+ public void update(String login, Consumer<UpdateRequest>... updaters) {
+ UpdateRequest updateRequest = new UpdateRequest();
+ updateRequest.setLogin(login);
+ stream(updaters).forEach(p -> p.accept(updateRequest));
+ session.wsClient().users().update(updateRequest);
+ }
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ViewTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ViewTester.java
new file mode 100644
index 00000000000..5bd15ca9bc5
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ViewTester.java
@@ -0,0 +1,517 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import com.google.common.util.concurrent.Uninterruptibles;
+import com.google.gson.Gson;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.junit.rules.ExternalResource;
+import org.sonarqube.ws.Ce;
+import org.sonarqube.ws.Projects.CreateWsResponse.Project;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.ce.ActivityStatusRequest;
+import org.sonarqube.ws.client.projects.ProjectsService;
+import org.sonarqube.ws.client.projects.SearchRequest;
+import org.sonarqube.ws.client.views.CreateRequest;
+import org.sonarqube.ws.client.views.DeleteRequest;
+import org.sonarqube.ws.client.views.ProjectsRequest;
+import org.sonarqube.ws.client.views.RefreshRequest;
+import org.sonarqube.ws.client.views.ShowRequest;
+import org.sonarqube.ws.client.views.ViewsService;
+
+import static java.util.Arrays.asList;
+import static java.util.Arrays.stream;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ViewTester extends ExternalResource {
+
+ private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
+
+ private final TesterSession session;
+
+ ViewTester(TesterSession session) {
+ this.session = session;
+ }
+
+ public ViewsService service() {
+ return session.wsClient().views();
+ }
+
+ public void deleteAll() {
+ ProjectsService service = session.wsClient().projects();
+ service.search(new SearchRequest().setQualifiers(asList("VW"))).getComponentsList()
+ .forEach(p -> {
+ waitForCeQueueEmpty();
+ session.wsClient().views().delete(new DeleteRequest().setKey(p.getKey()));
+ });
+ waitForCeQueueEmpty();
+
+ org.sonarqube.ws.client.components.SearchRequest searchRequest = new org.sonarqube.ws.client.components.SearchRequest().setQualifiers(asList("VW", "SVW"));
+ assertThat(session.wsClient().components().search(searchRequest).getComponentsList()).isEmpty();
+
+ assertNotViewInDef();
+ }
+
+ @SafeVarargs
+ public final CreateRequest generate(Consumer<CreateRequest>... populators) {
+ String key = generateKey();
+ CreateRequest request = new CreateRequest()
+ .setKey(key)
+ .setName("Name " + key)
+ .setDescription("Description " + key);
+ stream(populators).forEach(p -> p.accept(request));
+ service().create(request);
+ return request;
+ }
+
+ @SafeVarargs
+ public final String createSubPortfolio(String parentKey, Consumer<CreateRequest>... populators) {
+ String key = generateKey();
+ CreateRequest request = new CreateRequest()
+ .setParent(parentKey)
+ .setKey(key)
+ .setName("Sub view name " + key)
+ .setDescription("Sub view description " + key);
+ stream(populators).forEach(p -> p.accept(request));
+ service().create(request);
+ return request.getKey();
+ }
+
+ public void addProject(String viewKey, Project project) {
+ addProject(viewKey, project.getKey());
+ }
+
+ public void addProject(String viewKey, String projectKey) {
+ session.wsClient().wsConnector().call(
+ new PostRequest("/api/views/add_project")
+ .setParam("key", viewKey)
+ .setParam("project", projectKey))
+ .failIfNotSuccessful();
+ }
+
+ public void addProjectBranch(String viewKey, String projectKey, String branch) {
+ session.wsClient().wsConnector().call(
+ new PostRequest("/api/views/add_project_branch")
+ .setParam("key", viewKey)
+ .setParam("project", projectKey)
+ .setParam("branch", branch))
+ .failIfNotSuccessful();
+ }
+
+ public AddLocalReferenceResponse addPortfolio(String key, String portfolioRefKey) {
+ return AddLocalReferenceResponse.parse(session.wsClient().wsConnector().call(
+ new PostRequest("/api/views/add_portfolio")
+ .setParam("portfolio", key)
+ .setParam("reference", portfolioRefKey))
+ .failIfNotSuccessful().content());
+ }
+
+ public MoveResponse move(String key, String destinationKey) {
+ return MoveResponse.parse(session.wsClient().wsConnector().call(
+ new PostRequest("/api/views/move")
+ .setParam("key", key)
+ .setParam("destination", destinationKey))
+ .failIfNotSuccessful().content());
+ }
+
+ public String generateKey() {
+ int id = ID_GENERATOR.getAndIncrement();
+ return "viewKey" + id;
+ }
+
+ public void refresh() {
+ service().refresh(new RefreshRequest());
+ waitForCeQueueEmpty();
+ }
+
+ public ViewTester waitForCeQueueEmpty() {
+ Ce.ActivityStatusWsResponse status;
+ boolean empty;
+ do {
+ status = session.wsClient().ce().activityStatus(new ActivityStatusRequest());
+ empty = status.getInProgress() + status.getPending() == 0;
+ if (!empty) {
+ Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
+ }
+
+ } while (!empty);
+
+ return this;
+ }
+
+ public ListResponse list() {
+ return ListResponse.parse(service().list());
+ }
+
+ public ShowResponse show(ShowRequest showRequest) {
+ return ShowResponse.parse(service().show(showRequest));
+ }
+
+ public ProjectsResponse projects(ProjectsRequest request) {
+ return ProjectsResponse.parse(service().projects(request));
+ }
+
+ public SearchResponse search(org.sonarqube.ws.client.views.SearchRequest searchRequest) {
+ return SearchResponse.parse(service().search(searchRequest));
+ }
+
+ private void assertNotViewInDef() {
+ assertThat(ListResponse.parse(service().list()).getViews()).isEmpty();
+ }
+
+ public static class ShowResponse {
+ private final String key;
+ private final String name;
+ private final String desc;
+ private final String qualifier;
+ private final String selectionMode;
+ private final String regexp;
+ private final List<SelectedProject> selectedProjects;
+ private final List<SubView> subViews;
+ private final List<String> tags;
+
+ public ShowResponse(String key, String name, String desc, String qualifier, String selectionMode, String regexp,
+ List<SelectedProject> selectedProjects, @Nullable List<SubView> subViews, List<String> tags) {
+ this.key = key;
+ this.name = name;
+ this.desc = desc;
+ this.qualifier = qualifier;
+ this.selectionMode = selectionMode;
+ this.regexp = regexp;
+ this.selectedProjects = selectedProjects;
+ this.subViews = subViews;
+ this.tags = tags;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public String getQualifier() {
+ return qualifier;
+ }
+
+ public List<SelectedProject> getSelectedProjects() {
+ return selectedProjects;
+ }
+
+ public String getSelectionMode() {
+ return selectionMode;
+ }
+
+ public String getRegexp() {
+ return regexp;
+ }
+
+ public List<String> getTags() {
+ return tags;
+ }
+
+ @CheckForNull
+ public List<SubView> getSubViews() {
+ return subViews;
+ }
+
+ public static ShowResponse parse(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, ShowResponse.class);
+ }
+
+ public static class SelectedProject {
+ private final String projectKey;
+ private final List<String> selectedBranches;
+
+ public SelectedProject(String projectKey, List<String> selectedBranches) {
+ this.projectKey = projectKey;
+ this.selectedBranches = selectedBranches;
+ }
+
+ public String getProjectKey() {
+ return projectKey;
+ }
+
+ public List<String> getSelectedBranches() {
+ return selectedBranches;
+ }
+ }
+
+ public static class SubView {
+ private final String key;
+ private final String name;
+ private final String desc;
+ private final String selectionMode;
+ private final String originalKey;
+ private final String manual_measure_key;
+ private final String manual_measure_value;
+ private final List<SubView> subViews;
+
+ public SubView(String key, String name, String desc, String selectionMode, String originalKey, String manual_measure_key, String manual_measure_value,
+ List<SubView> subViews) {
+ this.key = key;
+ this.name = name;
+ this.desc = desc;
+ this.selectionMode = selectionMode;
+ this.originalKey = originalKey;
+ this.manual_measure_key = manual_measure_key;
+ this.manual_measure_value = manual_measure_value;
+ this.subViews = subViews;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public String getSelectionMode() {
+ return selectionMode;
+ }
+
+ public String getOriginalKey() {
+ return originalKey;
+ }
+
+ public String getManual_measure_key() {
+ return manual_measure_key;
+ }
+
+ public String getManual_measure_value() {
+ return manual_measure_value;
+ }
+
+ public List<SubView> getSubViews() {
+ return subViews;
+ }
+ }
+ }
+
+ public static class ListResponse {
+
+ private List<View> views;
+
+ private ListResponse(List<View> views) {
+ this.views = views;
+ }
+
+ public List<View> getViews() {
+ return views;
+ }
+
+ public static ListResponse parse(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, ListResponse.class);
+ }
+
+ public static class View {
+ private final String key;
+ private final String name;
+ private final String qualifier;
+
+ private View(String key, String name, String qualifier) {
+ this.key = key;
+ this.name = name;
+ this.qualifier = qualifier;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getQualifier() {
+ return qualifier;
+ }
+
+ public static View parse(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, View.class);
+ }
+ }
+ }
+
+ public static class AddLocalReferenceResponse {
+ private final String key;
+ private final String name;
+
+ public AddLocalReferenceResponse(String key, String name) {
+ this.key = key;
+ this.name = name;
+ }
+
+ public static AddLocalReferenceResponse parse(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, AddLocalReferenceResponse.class);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ public static class ProjectsResponse {
+
+ private List<Project> results;
+ private boolean more;
+
+ public ProjectsResponse(List<Project> results, boolean more) {
+ this.results = results;
+ this.more = more;
+ }
+
+ public List<Project> getProjects() {
+ return results;
+ }
+
+ public boolean isMore() {
+ return more;
+ }
+
+ public static ProjectsResponse parse(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, ProjectsResponse.class);
+ }
+
+ public static class Project {
+ private final String key;
+ private final String name;
+ private final boolean selected;
+ private final boolean enabled;
+
+ private Project(String key, String name, boolean selected, boolean enabled) {
+ this.key = key;
+ this.name = name;
+ this.selected = selected;
+ this.enabled = enabled;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isSelected() {
+ return selected;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+ }
+ }
+
+ public static class SearchResponse {
+
+ private List<Component> components;
+
+ private SearchResponse(List<Component> components) {
+ this.components = components;
+ }
+
+ public List<Component> getComponents() {
+ return components;
+ }
+
+ public static SearchResponse parse(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, SearchResponse.class);
+ }
+
+ public static class Component {
+ private final String key;
+ private final String name;
+ private final String qualifier;
+
+ private Component(String key, String name, String qualifier) {
+ this.key = key;
+ this.name = name;
+ this.qualifier = qualifier;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getQualifier() {
+ return qualifier;
+ }
+
+ public static Component parse(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, Component.class);
+ }
+ }
+ }
+
+ public static class MoveResponse {
+ private final String key;
+ private final String name;
+
+ public MoveResponse(String key, String name) {
+ this.key = key;
+ this.name = name;
+ }
+
+ public static MoveResponse parse(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, MoveResponse.class);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/WebhookTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/WebhookTester.java
new file mode 100644
index 00000000000..59833d97ef5
--- /dev/null
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/WebhookTester.java
@@ -0,0 +1,107 @@
+/*
+ * 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.sonarqube.ws.tester;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+import org.assertj.core.api.Assertions;
+import org.sonarqube.ws.Projects.CreateWsResponse.Project;
+import org.sonarqube.ws.Webhooks.CreateWsResponse.Webhook;
+import org.sonarqube.ws.Webhooks.Delivery;
+import org.sonarqube.ws.client.webhooks.CreateRequest;
+import org.sonarqube.ws.client.webhooks.DeleteRequest;
+import org.sonarqube.ws.client.webhooks.DeliveriesRequest;
+import org.sonarqube.ws.client.webhooks.DeliveryRequest;
+import org.sonarqube.ws.client.webhooks.ListRequest;
+import org.sonarqube.ws.client.webhooks.WebhooksService;
+
+import static java.util.Arrays.stream;
+import static java.util.Objects.requireNonNull;
+
+public class WebhookTester {
+ private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
+
+ private final TesterSession session;
+
+ WebhookTester(TesterSession session) {
+ this.session = session;
+ }
+
+ public WebhooksService service() {
+ return session.wsClient().webhooks();
+ }
+
+ @SafeVarargs
+ public final Webhook generate(Consumer<CreateRequest>... populators) {
+ return generate(null, populators);
+ }
+
+ @SafeVarargs
+ public final Webhook generate(
+ @Nullable Project project,
+ Consumer<CreateRequest>... populators) {
+ int id = ID_GENERATOR.getAndIncrement();
+ CreateRequest request = new CreateRequest()
+ .setName("Webhook " + id)
+ .setUrl("https://webhook-" + id)
+ .setProject(project != null ? project.getKey() : null);
+ stream(populators).forEach(p -> p.accept(request));
+ return service().create(request).getWebhook();
+ }
+
+ public void deleteAllGlobal() {
+ service().list(new ListRequest()).getWebhooksList().forEach(p -> service().delete(new DeleteRequest().setWebhook(p.getKey())));
+ }
+
+ public List<Delivery> getPersistedDeliveries(Project project) {
+ DeliveriesRequest deliveriesReq = new DeliveriesRequest().setComponentKey(project.getKey());
+ return service().deliveries(deliveriesReq).getDeliveriesList();
+ }
+
+ public Delivery getPersistedDeliveryByName(Project project, String webhookName) {
+ List<Delivery> deliveries = getPersistedDeliveries(project);
+ Optional<Delivery> delivery = deliveries.stream().filter(d -> d.getName().equals(webhookName)).findFirst();
+ Assertions.assertThat(delivery).isPresent();
+ return delivery.get();
+ }
+
+ public Delivery getDetailOfPersistedDelivery(Delivery delivery) {
+ Delivery detail = service().delivery(new DeliveryRequest().setDeliveryId(delivery.getId())).getDelivery();
+ return requireNonNull(detail);
+ }
+
+ public void assertThatPersistedDeliveryIsValid(Delivery delivery, @Nullable Project project, @Nullable String url) {
+ Assertions.assertThat(delivery.getId()).isNotEmpty();
+ Assertions.assertThat(delivery.getName()).isNotEmpty();
+ Assertions.assertThat(delivery.hasSuccess()).isTrue();
+ Assertions.assertThat(delivery.getHttpStatus()).isGreaterThanOrEqualTo(200);
+ Assertions.assertThat(delivery.getDurationMs()).isGreaterThanOrEqualTo(0);
+ Assertions.assertThat(delivery.getAt()).isNotEmpty();
+ if (project != null) {
+ Assertions.assertThat(delivery.getComponentKey()).isEqualTo(project.getKey());
+ }
+ if (url != null) {
+ Assertions.assertThat(delivery.getUrl()).startsWith(url);
+ }
+ }
+}