123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- /*
- * 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.sonar.server.webhook;
-
- import com.tngtech.java.junit.dataprovider.DataProvider;
- import com.tngtech.java.junit.dataprovider.DataProviderRunner;
- import com.tngtech.java.junit.dataprovider.UseDataProvider;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Optional;
- import java.util.Random;
- import java.util.Set;
- import java.util.function.Supplier;
- import java.util.stream.Collectors;
- import javax.annotation.Nullable;
- import org.junit.Rule;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.mockito.ArgumentCaptor;
- import org.mockito.Mockito;
- import org.sonar.api.config.Configuration;
- import org.sonar.api.measures.Metric;
- import org.sonar.api.utils.System2;
- import org.sonar.core.util.UuidFactoryFast;
- import org.sonar.db.DbClient;
- import org.sonar.db.DbTester;
- import org.sonar.db.component.AnalysisPropertyDto;
- import org.sonar.db.component.BranchDto;
- import org.sonar.db.component.BranchType;
- import org.sonar.db.component.ProjectData;
- import org.sonar.db.component.SnapshotDto;
- import org.sonar.db.project.ProjectDto;
- import org.sonar.server.qualitygate.EvaluatedQualityGate;
- import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
- import org.sonar.server.qualitygate.changeevent.QGChangeEventListener;
-
- import static java.util.Arrays.stream;
- import static java.util.Collections.emptySet;
- import static java.util.stream.Stream.concat;
- import static java.util.stream.Stream.of;
- import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
- import static org.assertj.core.api.Assertions.assertThat;
- import static org.mockito.ArgumentMatchers.any;
- import static org.mockito.ArgumentMatchers.eq;
- import static org.mockito.Mockito.mock;
- import static org.mockito.Mockito.verify;
- import static org.mockito.Mockito.verifyNoInteractions;
- import static org.mockito.Mockito.when;
- import static org.sonar.db.component.BranchType.BRANCH;
-
- @RunWith(DataProviderRunner.class)
- public class WebhookQGChangeEventListenerIT {
-
- private static final Set<QGChangeEventListener.ChangedIssue> CHANGED_ISSUES_ARE_IGNORED = emptySet();
-
- @Rule
- public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
- private DbClient dbClient = dbTester.getDbClient();
-
- private EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class);
- private WebHooks webHooks = mock(WebHooks.class);
- private WebhookPayloadFactory webhookPayloadFactory = mock(WebhookPayloadFactory.class);
- private DbClient spiedOnDbClient = Mockito.spy(dbClient);
- private WebhookQGChangeEventListener underTest = new WebhookQGChangeEventListener(webHooks, webhookPayloadFactory, spiedOnDbClient);
- private DbClient mockedDbClient = mock(DbClient.class);
- private WebhookQGChangeEventListener mockedUnderTest = new WebhookQGChangeEventListener(webHooks, webhookPayloadFactory, mockedDbClient);
-
- @Test
- @UseDataProvider("allCombinationsOfStatuses")
- public void onIssueChanges_has_no_effect_if_no_webhook_is_configured(Metric.Level previousStatus, Metric.Level newStatus) {
- Configuration configuration1 = mock(Configuration.class);
- when(newQualityGate.getStatus()).thenReturn(newStatus);
- QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration1, previousStatus, newQualityGate);
- mockWebhookDisabled(qualityGateEvent.getProject());
-
- mockedUnderTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
-
- verify(webHooks).isEnabled(qualityGateEvent.getProject());
- verifyNoInteractions(webhookPayloadFactory, mockedDbClient);
- }
-
- @DataProvider
- public static Object[][] allCombinationsOfStatuses() {
- Metric.Level[] levelsAndNull = concat(of((Metric.Level) null), stream(Metric.Level.values()))
- .toArray(Metric.Level[]::new);
- Object[][] res = new Object[levelsAndNull.length * levelsAndNull.length][2];
- int i = 0;
- for (Metric.Level previousStatus : levelsAndNull) {
- for (Metric.Level newStatus : levelsAndNull) {
- res[i][0] = previousStatus;
- res[i][1] = newStatus;
- i++;
- }
- }
- return res;
- }
-
- @Test
- public void onIssueChanges_has_no_effect_if_event_has_neither_previousQGStatus_nor_qualityGate() {
- Configuration configuration = mock(Configuration.class);
- QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration, null, null);
- mockWebhookEnabled(qualityGateEvent.getProject());
-
- underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
-
- verifyNoInteractions(webhookPayloadFactory, mockedDbClient);
- }
-
- @Test
- public void onIssueChanges_has_no_effect_if_event_has_same_status_in_previous_and_new_QG() {
- Configuration configuration = mock(Configuration.class);
- Metric.Level previousStatus = randomLevel();
- when(newQualityGate.getStatus()).thenReturn(previousStatus);
- QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration, previousStatus, newQualityGate);
- mockWebhookEnabled(qualityGateEvent.getProject());
-
- underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
-
- verifyNoInteractions(webhookPayloadFactory, mockedDbClient);
- }
-
- @Test
- @UseDataProvider("newQGorNot")
- public void onIssueChanges_calls_webhook_for_changeEvent_with_webhook_enabled(@Nullable EvaluatedQualityGate newQualityGate) {
- ProjectAndBranch projectBranch = insertBranch(BRANCH, "foo");
- SnapshotDto analysis = insertAnalysisTask(projectBranch);
- Configuration configuration = mock(Configuration.class);
- mockPayloadSupplierConsumedByWebhooks();
- Map<String, String> properties = new HashMap<>();
- properties.put("sonar.analysis.test1", randomAlphanumeric(50));
- properties.put("sonar.analysis.test2", randomAlphanumeric(5000));
- insertPropertiesFor(analysis.getUuid(), properties);
- QGChangeEvent qualityGateEvent = newQGChangeEvent(projectBranch, analysis, configuration, newQualityGate);
- mockWebhookEnabled(qualityGateEvent.getProject());
-
- underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
-
- ProjectAnalysis projectAnalysis = verifyWebhookCalledAndExtractPayloadFactoryArgument(projectBranch, analysis, qualityGateEvent.getProject());
- assertThat(projectAnalysis).isEqualTo(
- new ProjectAnalysis(
- new Project(projectBranch.project.getUuid(), projectBranch.project.getKey(), projectBranch.project.getName()),
- null,
- new Analysis(analysis.getUuid(), analysis.getCreatedAt(), analysis.getRevision()),
- new Branch(false, "foo", Branch.Type.BRANCH),
- newQualityGate,
- null,
- properties));
- }
-
- @Test
- @UseDataProvider("newQGorNot")
- public void onIssueChanges_calls_webhook_on_main_branch(@Nullable EvaluatedQualityGate newQualityGate) {
- ProjectAndBranch mainBranch = insertMainBranch();
- SnapshotDto analysis = insertAnalysisTask(mainBranch);
- Configuration configuration = mock(Configuration.class);
- QGChangeEvent qualityGateEvent = newQGChangeEvent(mainBranch, analysis, configuration, newQualityGate);
- mockWebhookEnabled(qualityGateEvent.getProject());
-
- underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
-
- verifyWebhookCalled(mainBranch, analysis, qualityGateEvent.getProject());
- }
-
- @Test
- public void onIssueChanges_calls_webhook_on_branch() {
- onIssueChangesCallsWebhookOnBranch(BRANCH);
- }
-
- @Test
- public void onIssueChanges_calls_webhook_on_pr() {
- onIssueChangesCallsWebhookOnBranch(BranchType.PULL_REQUEST);
- }
-
- public void onIssueChangesCallsWebhookOnBranch(BranchType branchType) {
- ProjectAndBranch nonMainBranch = insertBranch(branchType, "foo");
- SnapshotDto analysis = insertAnalysisTask(nonMainBranch);
- Configuration configuration = mock(Configuration.class);
- QGChangeEvent qualityGateEvent = newQGChangeEvent(nonMainBranch, analysis, configuration, null);
- mockWebhookEnabled(qualityGateEvent.getProject());
-
- underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
-
- verifyWebhookCalled(nonMainBranch, analysis, qualityGateEvent.getProject());
- }
-
- @DataProvider
- public static Object[][] newQGorNot() {
- EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class);
- return new Object[][] {
- {null},
- {newQualityGate}
- };
- }
-
- private void mockWebhookEnabled(ProjectDto... projects) {
- for (ProjectDto dto : projects) {
- when(webHooks.isEnabled(dto)).thenReturn(true);
- }
- }
-
- private void mockWebhookDisabled(ProjectDto... projects) {
- for (ProjectDto dto : projects) {
- when(webHooks.isEnabled(dto)).thenReturn(false);
- }
- }
-
- private void mockPayloadSupplierConsumedByWebhooks() {
- Mockito.doAnswer(invocationOnMock -> {
- Supplier<WebhookPayload> supplier = (Supplier<WebhookPayload>) invocationOnMock.getArguments()[1];
- supplier.get();
- return null;
- }).when(webHooks)
- .sendProjectAnalysisUpdate(any(), any());
- }
-
- private void insertPropertiesFor(String snapshotUuid, Map<String, String> properties) {
- List<AnalysisPropertyDto> analysisProperties = properties.entrySet().stream()
- .map(entry -> new AnalysisPropertyDto()
- .setUuid(UuidFactoryFast.getInstance().create())
- .setAnalysisUuid(snapshotUuid)
- .setKey(entry.getKey())
- .setValue(entry.getValue()))
- .toList();
- dbTester.getDbClient().analysisPropertiesDao().insert(dbTester.getSession(), analysisProperties);
- dbTester.getSession().commit();
- }
-
- private SnapshotDto insertAnalysisTask(ProjectAndBranch projectAndBranch) {
- return dbTester.components().insertSnapshot(projectAndBranch.getBranch());
- }
-
- private ProjectAnalysis verifyWebhookCalledAndExtractPayloadFactoryArgument(ProjectAndBranch projectAndBranch, SnapshotDto analysis, ProjectDto project) {
- verifyWebhookCalled(projectAndBranch, analysis, project);
-
- return extractPayloadFactoryArguments(1).iterator().next();
- }
-
- private void verifyWebhookCalled(ProjectAndBranch projectAndBranch, SnapshotDto analysis, ProjectDto project) {
- verify(webHooks).isEnabled(project);
- verify(webHooks).sendProjectAnalysisUpdate(
- eq(new WebHooks.Analysis(projectAndBranch.uuid(), analysis.getUuid(), null)),
- any());
- }
-
- private List<ProjectAnalysis> extractPayloadFactoryArguments(int time) {
- ArgumentCaptor<ProjectAnalysis> projectAnalysisCaptor = ArgumentCaptor.forClass(ProjectAnalysis.class);
- verify(webhookPayloadFactory, Mockito.times(time)).create(projectAnalysisCaptor.capture());
- return projectAnalysisCaptor.getAllValues();
- }
-
- public ProjectAndBranch insertMainBranch() {
- ProjectData project = dbTester.components().insertPrivateProject();
- return new ProjectAndBranch(project.getProjectDto(), project.getMainBranchDto());
- }
-
- public ProjectAndBranch insertBranch(BranchType type, String branchKey) {
- ProjectDto project = dbTester.components().insertPrivateProject().getProjectDto();
- BranchDto branch = dbTester.components().insertProjectBranch(project, b -> b.setKey(branchKey).setBranchType(type));
- return new ProjectAndBranch(project, branch);
- }
-
- public ProjectAndBranch insertBranch(ProjectDto project, BranchType type, String branchKey) {
- BranchDto branch = dbTester.components().insertProjectBranch(project, b -> b.setKey(branchKey).setBranchType(type));
- return new ProjectAndBranch(project, branch);
- }
-
- private static class ProjectAndBranch {
- private final ProjectDto project;
- private final BranchDto branch;
-
- private ProjectAndBranch(ProjectDto project, BranchDto branch) {
- this.project = project;
- this.branch = branch;
- }
-
- public ProjectDto getProject() {
- return project;
- }
-
- public BranchDto getBranch() {
- return branch;
- }
-
- public String uuid() {
- return project.getUuid();
- }
-
- }
-
- private static QGChangeEvent newQGChangeEvent(Configuration configuration, @Nullable Metric.Level previousQQStatus, @Nullable EvaluatedQualityGate evaluatedQualityGate) {
- return new QGChangeEvent(new ProjectDto(), new BranchDto(), new SnapshotDto(), configuration, previousQQStatus, () -> Optional.ofNullable(evaluatedQualityGate));
- }
-
- private static QGChangeEvent newQGChangeEvent(ProjectAndBranch branch, SnapshotDto analysis, Configuration configuration, @Nullable EvaluatedQualityGate evaluatedQualityGate) {
- Metric.Level previousStatus = randomLevel();
- if (evaluatedQualityGate != null) {
- Metric.Level otherLevel = stream(Metric.Level.values())
- .filter(s -> s != previousStatus)
- .toArray(Metric.Level[]::new)[new Random().nextInt(Metric.Level.values().length - 1)];
- when(evaluatedQualityGate.getStatus()).thenReturn(otherLevel);
- }
- return new QGChangeEvent(branch.project, branch.branch, analysis, configuration, previousStatus, () -> Optional.ofNullable(evaluatedQualityGate));
- }
-
- private static Metric.Level randomLevel() {
- return Metric.Level.values()[new Random().nextInt(Metric.Level.values().length)];
- }
-
- }
|