Browse Source

SONAR-20824 make Tester open source

tags/10.4.0.87286
Benjamin Campomenosi 5 months ago
parent
commit
703ddf8c48
20 changed files with 2768 additions and 10 deletions
  1. 1
    0
      build.gradle
  2. 2
    1
      server/sonar-main/src/main/java/org/sonar/application/command/EsJvmOptions.java
  3. 15
    9
      sonar-ws/build.gradle
  4. 68
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/AlmSettingsTester.java
  5. 353
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ApplicationTester.java
  6. 84
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/Elasticsearch.java
  7. 106
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/GroupTester.java
  8. 105
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/MeasureTester.java
  9. 65
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/NewCodePeriodTester.java
  10. 128
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/PermissionTester.java
  11. 101
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ProjectTester.java
  12. 126
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QGateTester.java
  13. 63
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QModelTester.java
  14. 136
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QProfileTester.java
  15. 114
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/SettingTester.java
  16. 403
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/Tester.java
  17. 56
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/TesterSession.java
  18. 218
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java
  19. 517
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ViewTester.java
  20. 107
    0
      sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/WebhookTester.java

+ 1
- 0
build.gradle View File

@@ -700,6 +700,7 @@ subprojects {
bbtImplementation 'org.sonarsource.orchestrator:sonar-orchestrator-junit4'
bbtImplementation project(":sonar-testing-harness")
bbtImplementation project(":private:it-common")
bbtImplementation testFixtures(project(":sonar-ws"))
}
}


+ 2
- 1
server/sonar-main/src/main/java/org/sonar/application/command/EsJvmOptions.java View File

@@ -118,7 +118,8 @@ public class EsJvmOptions extends JvmOptions<EsJvmOptions> {
res.put("-Dlog4j2.disable.jmx=", "true");
res.put("-Dlog4j2.formatMsgNoLookups=", "true");
/*
* Due to internationalization enhancements in JDK 9 Elasticsearch need to set the provider to COMPAT otherwise time/date
* Due to internationalization enhancements in JDK 9 org.sonarqube.ws.tester.Elasticsearch need to set the provider to COMPAT otherwise
* time/date
* parsing will break in an incompatible way for some date patterns and locales.
*/
res.put("-Djava.locale.providers=", "COMPAT");

+ 15
- 9
sonar-ws/build.gradle View File

@@ -20,15 +20,21 @@ dependencies {
compileOnlyApi 'com.google.code.findbugs:jsr305'
compileOnlyApi 'javax.annotation:javax.annotation-api'

testImplementation 'com.squareup.okhttp3:mockwebserver'
testImplementation 'com.squareup.okio:okio'
testImplementation 'commons-io:commons-io'
testImplementation 'commons-lang:commons-lang'
testImplementation 'junit:junit'
testImplementation 'org.assertj:assertj-core'
testImplementation 'org.hamcrest:hamcrest-core'
testImplementation 'org.mockito:mockito-core'
testImplementation project(':sonar-testing-harness')
testImplementation 'com.squareup.okhttp3:mockwebserver'
testImplementation 'com.squareup.okio:okio'
testImplementation 'commons-io:commons-io'
testImplementation 'commons-lang:commons-lang'
testImplementation 'junit:junit'
testImplementation 'org.assertj:assertj-core'
testImplementation 'org.hamcrest:hamcrest-core'
testImplementation 'org.mockito:mockito-core'
testImplementation project(':sonar-testing-harness')

testFixturesApi 'junit:junit'
testFixturesApi 'com.google.guava:guava'
testFixturesApi 'org.assertj:assertj-core'
testFixturesApi 'org.sonarsource.orchestrator:sonar-orchestrator-junit4'
testFixturesApi 'commons-io:commons-io'
}

artifactoryPublish.skip = false

+ 68
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/AlmSettingsTester.java View File

@@ -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);
}
}
}

+ 353
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ApplicationTester.java View File

@@ -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;
}
}
}

}

+ 84
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/Elasticsearch.java View File

@@ -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 + "/";
}
}

+ 106
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/GroupTester.java View File

@@ -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;
}
}

+ 105
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/MeasureTester.java View File

@@ -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());
}

}

+ 65
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/NewCodePeriodTester.java View File

@@ -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));
}

}

+ 128
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/PermissionTester.java View File

@@ -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();
}

}

+ 101
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ProjectTester.java View File

@@ -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;
}
}

+ 126
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QGateTester.java View File

@@ -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;
}
}
}
}

+ 63
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QModelTester.java View File

@@ -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);
}
}

+ 136
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/QProfileTester.java View File

@@ -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;
}
}

+ 114
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/SettingTester.java View File

@@ -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));
}
}

}

+ 403
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/Tester.java View File

@@ -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);
}

}
}

+ 56
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/TesterSession.java View File

@@ -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();

}

+ 218
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java View File

@@ -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);
}
}

+ 517
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/ViewTester.java View File

@@ -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;
}
}
}

+ 107
- 0
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/WebhookTester.java View File

@@ -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);
}
}
}

Loading…
Cancel
Save