diff options
author | Benjamin Campomenosi <109955405+benjamin-campomenosi-sonarsource@users.noreply.github.com> | 2023-11-22 14:43:42 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-11-22 20:02:41 +0000 |
commit | 703ddf8c4872ae82b70e96d7f3f060a2733e41fd (patch) | |
tree | 5a43fd2703b5bb668779e5bd9fc3853441bfb0ba /sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester | |
parent | 17072178fa9a64aa99fde55cf3efeae75593a84e (diff) | |
download | sonarqube-703ddf8c4872ae82b70e96d7f3f060a2733e41fd.tar.gz sonarqube-703ddf8c4872ae82b70e96d7f3f060a2733e41fd.zip |
SONAR-20824 make Tester open source
Diffstat (limited to 'sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester')
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); + } + } +} |