From a1f28e5df23117517fb9656af36119cdeb7b23b1 Mon Sep 17 00:00:00 2001 From: Michael Clarke Date: Sun, 11 Dec 2022 09:15:17 +0000 Subject: [PATCH] #674: Add support for Sonarqube 9.7 Sonarqube has removed the use of the same external component key and differing database IDs for different branches on the same projects and now use different IDs in all cases. The pull request web service endpoints have also been removed from community edition. To allow the plugin to work with the new version of Sonarqube, the component key generation for branches has been modified to save branch DTOs whenever a new branch is created, and to remove the conditions around re-using the same branch if the target branch details matched an existing branch. The Pull Request endpoint actions have been copied from the old community edition sources, and tidied up to use a cleaner abstraction model. As the front-end only shows branch features if the implementation of `BranchFeatureExtension` returns the name 'branch-support', the `CommunityBranchFeatureExtension` has been altered to follow this requirement, and an additional `MonoRepoFeature` has been implemented to allow the mono-repo switches to be shown against the front-end. Includes the migration of any altered unit tests to JUnit 5. --- build.gradle | 4 +- .../plugin/CommunityBranchAgent.java | 12 +- .../plugin/CommunityBranchPlugin.java | 25 +- .../sonarqube/plugin/ce/CommunityBranch.java | 23 +- .../DiscussionAwarePullRequestDecorator.java | 6 +- .../CommunityBranchFeatureExtension.java | 7 +- .../CommunityBranchSupportDelegate.java | 73 +++-- .../plugin/server/CommunityComponentKey.java | 19 +- .../plugin/server/MonoRepoFeature.java | 38 +++ .../pullrequest/ws/action/ProtoBufWriter.java | 13 - .../action/DeleteBindingAction.java | 8 +- .../{ => binding}/action/ProjectWsAction.java | 31 +- .../action/SetAzureBindingAction.java | 8 +- .../action/SetBindingAction.java | 16 +- .../action/SetBitbucketBindingAction.java | 8 +- .../SetBitbucketCloudBindingAction.java | 9 +- .../action/SetGithubBindingAction.java | 8 +- .../action/SetGitlabBindingAction.java | 8 +- .../action/ValidateBindingAction.java | 6 +- .../ws/pullrequest/PullRequestWs.java | 42 +++ .../ws/pullrequest/action/DeleteAction.java | 68 +++++ .../ws/pullrequest/action/ListAction.java | 163 ++++++++++ .../pullrequest/action/ProjectWsAction.java | 70 +++++ .../ws/pullrequest/action/ProtoBufWriter.java | 30 ++ .../action/PullRequestWsAction.java | 25 ++ .../plugin/CommunityBranchAgentTest.java | 103 ++++--- .../plugin/CommunityBranchPluginTest.java | 75 ++--- .../plugin/ce/CommunityBranchTest.java | 66 ++--- ...bMergeRequestDecoratorIntegrationTest.java | 2 +- .../CommunityBranchFeatureExtensionTest.java | 19 +- .../CommunityBranchSupportDelegateTest.java | 279 ++++++------------ .../plugin/server/MonoRepoFeatureTest.java | 39 +++ .../action/DeleteBindingActionTest.java | 58 ++-- .../action/SetAzureBindingActionTest.java | 24 +- .../action/SetBindingActionTest.java | 73 +++-- .../action/SetBitBucketBindingActionTest.java | 25 +- .../SetBitBucketCloudBindingActionTest.java | 25 +- .../action/SetGithubBindingActionTest.java | 24 +- .../action/SetGitlabBindingActionTest.java | 24 +- .../action/ValidateBindingActionTest.java | 6 +- .../ws/pullrequest/PullRequestWsTest.java | 49 +++ .../pullrequest/action/DeleteActionTest.java | 142 +++++++++ .../ws/pullrequest/action/ListActionTest.java | 209 +++++++++++++ 43 files changed, 1390 insertions(+), 572 deletions(-) create mode 100644 src/main/java/com/github/mc1arke/sonarqube/plugin/server/MonoRepoFeature.java delete mode 100644 src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ProtoBufWriter.java rename src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/DeleteBindingAction.java (94%) rename src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/ProjectWsAction.java (70%) rename src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/SetAzureBindingAction.java (91%) rename src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/SetBindingAction.java (82%) rename src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/SetBitbucketBindingAction.java (91%) rename src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/SetBitbucketCloudBindingAction.java (90%) rename src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/SetGithubBindingAction.java (92%) rename src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/SetGitlabBindingAction.java (90%) rename src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/ValidateBindingAction.java (97%) create mode 100644 src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/PullRequestWs.java create mode 100644 src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/DeleteAction.java create mode 100644 src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ListAction.java create mode 100644 src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ProjectWsAction.java create mode 100644 src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ProtoBufWriter.java create mode 100644 src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/PullRequestWsAction.java create mode 100644 src/test/java/com/github/mc1arke/sonarqube/plugin/server/MonoRepoFeatureTest.java rename src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/DeleteBindingActionTest.java (57%) rename src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/SetAzureBindingActionTest.java (78%) rename src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/SetBindingActionTest.java (67%) rename src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/SetBitBucketBindingActionTest.java (78%) rename src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/SetBitBucketCloudBindingActionTest.java (76%) rename src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/SetGithubBindingActionTest.java (78%) rename src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/SetGitlabBindingActionTest.java (75%) rename src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/{ => binding}/action/ValidateBindingActionTest.java (98%) create mode 100644 src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/PullRequestWsTest.java create mode 100644 src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/DeleteActionTest.java create mode 100644 src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ListActionTest.java diff --git a/build.gradle b/build.gradle index 9e261e0fa..d55ccae6a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -40,7 +40,7 @@ repositories { } } -def sonarqubeVersion = '9.1.0.47736' +def sonarqubeVersion = '9.7.0.61563' def sonarqubeLibDir = "${projectDir}/sonarqube-lib" def sonarLibraries = "${sonarqubeLibDir}/sonarqube-${sonarqubeVersion}/lib" diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchAgent.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchAgent.java index 456a8db45..d9f5cbe1b 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchAgent.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchAgent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Michael Clarke + * Copyright (C) 2021-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -52,8 +52,9 @@ public static void premain(String args, Instrumentation instrumentation) throws if (component == Component.CE) { redefineEdition(instrumentation, "org.sonar.core.platform.PlatformEditionProvider", redefineOptionalEditionGetMethod()); + redefineEdition(instrumentation, "org.sonar.server.almsettings.MultipleAlmFeature", redefineIsEnabledFlag()); } else if (component == Component.WEB) { - redefineEdition(instrumentation, "org.sonar.server.almsettings.MultipleAlmFeatureProvider", redefineConstructorEditionProviderField(EditionProvider.Edition.ENTERPRISE)); + redefineEdition(instrumentation, "org.sonar.server.almsettings.MultipleAlmFeature", redefineIsEnabledFlag()); redefineEdition(instrumentation, "org.sonar.server.newcodeperiod.ws.SetAction", redefineConstructorEditionProviderField(EditionProvider.Edition.DEVELOPER)); redefineEdition(instrumentation, "org.sonar.server.newcodeperiod.ws.UnsetAction", redefineConstructorEditionProviderField(EditionProvider.Edition.DEVELOPER)); } @@ -101,6 +102,13 @@ private static Redefiner redefineOptionalEditionGetMethod() { }; } + private static Redefiner redefineIsEnabledFlag() { + return ctClass -> { + CtMethod ctMethod = ctClass.getDeclaredMethod("isEnabled"); + ctMethod.setBody("return true;"); + }; + } + private static Redefiner redefineConstructorEditionProviderField(EditionProvider.Edition edition) { return ctClass -> { CtConstructor ctConstructor = ctClass.getDeclaredConstructors()[0]; diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java index e464feaa6..dda863b13 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java @@ -43,17 +43,22 @@ import com.github.mc1arke.sonarqube.plugin.scanner.autoconfiguration.JenkinsAutoConfigurer; import com.github.mc1arke.sonarqube.plugin.server.CommunityBranchFeatureExtension; import com.github.mc1arke.sonarqube.plugin.server.CommunityBranchSupportDelegate; +import com.github.mc1arke.sonarqube.plugin.server.MonoRepoFeature; import com.github.mc1arke.sonarqube.plugin.server.pullrequest.validator.AzureDevopsValidator; import com.github.mc1arke.sonarqube.plugin.server.pullrequest.validator.BitbucketValidator; import com.github.mc1arke.sonarqube.plugin.server.pullrequest.validator.GithubValidator; import com.github.mc1arke.sonarqube.plugin.server.pullrequest.validator.GitlabValidator; -import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.DeleteBindingAction; -import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.SetAzureBindingAction; -import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.SetBitbucketBindingAction; -import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.SetBitbucketCloudBindingAction; -import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.SetGithubBindingAction; -import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.SetGitlabBindingAction; -import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.ValidateBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action.DeleteBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action.SetAzureBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action.SetBitbucketBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action.SetBitbucketCloudBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action.SetGithubBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action.SetGitlabBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action.ValidateBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.PullRequestWs; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action.DeleteAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action.ListAction; + import org.sonar.api.CoreProperties; import org.sonar.api.Plugin; import org.sonar.api.PropertyType; @@ -88,6 +93,9 @@ public void load(CoreExtension.Context context) { SetBitbucketCloudBindingAction.class, SetGitlabBindingAction.class, ValidateBindingAction.class, + DeleteAction.class, + ListAction.class, + PullRequestWs.class, GithubValidator.class, DefaultGraphqlProvider.class, @@ -147,7 +155,8 @@ public void load(CoreExtension.Context context) { .name("Images base URL") .description("Base URL used to load the images for the PR comments (please use this only if images are not displayed properly).") .type(PropertyType.STRING) - .build()); + .build(), + MonoRepoFeature.class); } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranch.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranch.java index f6fa50450..a8393f364 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranch.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranch.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,11 +18,8 @@ */ package com.github.mc1arke.sonarqube.plugin.ce; -import org.apache.commons.lang.StringUtils; import org.sonar.ce.task.projectanalysis.analysis.Branch; -import org.sonar.core.component.ComponentKeys; import org.sonar.db.component.BranchType; -import org.sonar.db.component.ComponentDto; /** * @author Michael Clarke @@ -80,24 +77,6 @@ public String getPullRequestKey() { return pullRequestKey; } - @Override - public String generateKey(String projectKey, String fileOrDirPath) { - String effectiveKey; - if (null == fileOrDirPath) { - effectiveKey = projectKey; - } else { - effectiveKey = ComponentKeys.createEffectiveKey(projectKey, StringUtils.trimToNull(fileOrDirPath)); - } - - if (main) { - return effectiveKey; - } else if (BranchType.PULL_REQUEST == branchType) { - return ComponentDto.generatePullRequestKey(effectiveKey, pullRequestKey); - } else { - return ComponentDto.generateBranchKey(effectiveKey, name); - } - } - @Override public String getTargetBranchName() { return targetBranchName; diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/DiscussionAwarePullRequestDecorator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/DiscussionAwarePullRequestDecorator.java index 627f11fc2..40687e623 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/DiscussionAwarePullRequestDecorator.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/DiscussionAwarePullRequestDecorator.java @@ -71,16 +71,16 @@ public DecorationResult decorateQualityGateStatus(AnalysisDetails analysis, AlmS U user = getCurrentUser(client); List openSonarqubeIssues = analysis.getScmReportableIssues(); - List>> currentProjectSonarqueComments = findOpenSonarqubeComments(client, + List>> currentProjectSonarqubeComments = findOpenSonarqubeComments(client, pullRequest, user) .stream() - .filter(comment -> isCommentFromCurrentProject(comment, analysis.getAnalysisProjectKey())) + .filter(comment -> !projectAlmSettingDto.getMonorepo() || isCommentFromCurrentProject(comment, analysis.getAnalysisProjectKey())) .collect(Collectors.toList()); List commentKeysForOpenComments = closeOldDiscussionsAndExtractRemainingKeys(client, user, - currentProjectSonarqueComments, + currentProjectSonarqubeComments, openSonarqubeIssues, pullRequest); diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchFeatureExtension.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchFeatureExtension.java index 02668429c..8c69fc6f2 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchFeatureExtension.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchFeatureExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * Copyright (C) 2019-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -27,6 +27,11 @@ */ public class CommunityBranchFeatureExtension implements BranchFeatureExtension { + @Override + public String getName() { + return "branch-support"; + } + @Override public boolean isEnabled() { return true; diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegate.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegate.java index f8bf9eff2..999d9f25a 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegate.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,7 +18,15 @@ */ package com.github.mc1arke.sonarqube.plugin.server; +import java.time.Clock; +import java.util.Arrays; +import java.util.Date; +import java.util.Map; +import java.util.regex.Pattern; + import org.apache.commons.lang.StringUtils; +import org.sonar.api.config.Configuration; +import org.sonar.core.config.PurgeConstants; import org.sonar.core.util.UuidFactory; import org.sonar.db.DbClient; import org.sonar.db.DbSession; @@ -28,11 +36,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.server.ce.queue.BranchSupport; import org.sonar.server.ce.queue.BranchSupportDelegate; - -import java.time.Clock; -import java.util.Date; -import java.util.Map; -import java.util.Optional; +import org.sonar.server.setting.ProjectConfigurationLoader; /** * @author Michael Clarke @@ -42,12 +46,15 @@ public class CommunityBranchSupportDelegate implements BranchSupportDelegate { private final UuidFactory uuidFactory; private final DbClient dbClient; private final Clock clock; + private final ProjectConfigurationLoader projectConfigurationLoader; - public CommunityBranchSupportDelegate(UuidFactory uuidFactory, DbClient dbClient, Clock clock) { + public CommunityBranchSupportDelegate(UuidFactory uuidFactory, DbClient dbClient, Clock clock, + ProjectConfigurationLoader projectConfigurationLoader) { super(); this.uuidFactory = uuidFactory; this.dbClient = dbClient; this.clock = clock; + this.projectConfigurationLoader = projectConfigurationLoader; } @Override @@ -61,9 +68,7 @@ public CommunityComponentKey createComponentKey(String projectKey, Map branchOptional = componentKey.getBranchName(); - if (branchOptional.isPresent() && branchOptional.get().equals(mainComponentBranchDto.getKey())) { - return mainComponentDto; - } - String branchUuid = uuidFactory.create(); - // borrowed from https://github.com/SonarSource/sonarqube/blob/e80c0f3d1e5cd459f88b7e0c41a2d9a7519e260f/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImpl.java - ComponentDto branchDto = mainComponentDto.copy(); - branchDto.setUuid(branchUuid); - branchDto.setProjectUuid(branchUuid); - branchDto.setRootUuid(branchUuid); - branchDto.setUuidPath(ComponentDto.UUID_PATH_OF_ROOT); - branchDto.setModuleUuidPath(ComponentDto.UUID_PATH_SEPARATOR + branchUuid + ComponentDto.UUID_PATH_SEPARATOR); - branchDto.setMainBranchProjectUuid(mainComponentDto.uuid()); - branchDto.setDbKey(componentKey.getDbKey()); - branchDto.setCreatedAt(new Date(clock.millis())); - dbClient.componentDao().insert(dbSession, branchDto); - return branchDto; + ComponentDto componentDto = mainComponentDto.copy() + .setUuid(branchUuid) + .setRootUuid(branchUuid) + .setBranchUuid(branchUuid) + .setUuidPath(ComponentDto.UUID_PATH_OF_ROOT) + .setModuleUuidPath(ComponentDto.UUID_PATH_SEPARATOR + branchUuid + ComponentDto.UUID_PATH_SEPARATOR) + .setMainBranchProjectUuid(mainComponentDto.uuid()) + .setCreatedAt(new Date(clock.millis())); + dbClient.componentDao().insert(dbSession, componentDto); + + BranchDto branchDto = new BranchDto() + .setProjectUuid(mainComponentDto.uuid()) + .setUuid(branchUuid); + componentKey.getPullRequestKey().ifPresent(pullRequestKey -> branchDto.setBranchType(BranchType.PULL_REQUEST) + .setExcludeFromPurge(false) + .setKey(pullRequestKey)); + componentKey.getBranchName().ifPresent(branchName -> branchDto.setBranchType(BranchType.BRANCH) + .setExcludeFromPurge(isBranchExcludedFromPurge(projectConfigurationLoader.loadProjectConfiguration(dbSession, mainComponentDto), branchName)) + .setKey(branchName)); + dbClient.branchDao().insert(dbSession, branchDto); + + return componentDto; + } + + private static boolean isBranchExcludedFromPurge(Configuration projectConfiguration, String branchName) { + return Arrays.stream(projectConfiguration.getStringArray(PurgeConstants.BRANCHES_TO_KEEP_WHEN_INACTIVE)) + .map(Pattern::compile) + .map(Pattern::asMatchPredicate) + .anyMatch(p -> p.test(branchName)); } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityComponentKey.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityComponentKey.java index 6c7c4ae1c..9cf667eae 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityComponentKey.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityComponentKey.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -28,13 +28,11 @@ /*package*/ class CommunityComponentKey extends BranchSupport.ComponentKey { private final String key; - private final String dbKey; private final String branchName; private final String pullRequestKey; - /*package*/ CommunityComponentKey(String key, String dbKey, String branchName, String pullRequestKey) { + /*package*/ CommunityComponentKey(String key, String branchName, String pullRequestKey) { this.key = key; - this.dbKey = dbKey; this.branchName = branchName; this.pullRequestKey = pullRequestKey; } @@ -44,25 +42,14 @@ public String getKey() { return key; } - @Override - public String getDbKey() { - return dbKey; - } - @Override public Optional getBranchName() { return Optional.ofNullable(branchName); } + @Override public Optional getPullRequestKey() { return Optional.ofNullable(pullRequestKey); } - @Override - public CommunityComponentKey getMainBranchComponentKey() { - if (key.equals(dbKey)) { - return this; - } - return new CommunityComponentKey(key, key, null, null); - } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/MonoRepoFeature.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/MonoRepoFeature.java new file mode 100644 index 000000000..7a455ac49 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/MonoRepoFeature.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server; + +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.server.ServerSide; +import org.sonar.server.feature.SonarQubeFeature; + +@ServerSide +@ComputeEngineSide +public class MonoRepoFeature implements SonarQubeFeature { + + @Override + public String getName() { + return "monorepo"; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ProtoBufWriter.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ProtoBufWriter.java deleted file mode 100644 index a3d37aa59..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ProtoBufWriter.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; - -import org.sonar.api.server.ws.Request; -import org.sonar.api.server.ws.Response; - -import com.google.protobuf.Message; - -@FunctionalInterface -interface ProtoBufWriter { - - void write(Message message, Request request, Response response); - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/DeleteBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/DeleteBindingAction.java similarity index 94% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/DeleteBindingAction.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/DeleteBindingAction.java index f65e77f69..a94ddf811 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/DeleteBindingAction.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/DeleteBindingAction.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; @@ -32,14 +32,14 @@ public class DeleteBindingAction extends ProjectWsAction { private final DbClient dbClient; public DeleteBindingAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder) { - super("delete_binding", dbClient, componentFinder, userSession, true); + super("delete_binding", dbClient, componentFinder, userSession); this.dbClient = dbClient; } @Override protected void configureAction(WebService.NewAction action) { - //no-op + action.setPost(true); } @Override diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ProjectWsAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/ProjectWsAction.java similarity index 70% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ProjectWsAction.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/ProjectWsAction.java index 8d43bcd70..b20fc8538 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ProjectWsAction.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/ProjectWsAction.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; @@ -29,8 +29,6 @@ import org.sonar.server.component.ComponentFinder; import org.sonar.server.user.UserSession; -import java.util.Optional; - public abstract class ProjectWsAction implements AlmSettingsWsAction { private static final String PROJECT_PARAMETER = "project"; @@ -39,27 +37,25 @@ public abstract class ProjectWsAction implements AlmSettingsWsAction { private final DbClient dbClient; private final ComponentFinder componentFinder; private final UserSession userSession; - private final boolean projectParameterRequired; private final String permission; - protected ProjectWsAction(String actionName, DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, boolean projectParameterRequired) { - this(actionName, dbClient, componentFinder, userSession, projectParameterRequired, UserRole.ADMIN); + protected ProjectWsAction(String actionName, DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) { + this(actionName, dbClient, componentFinder, userSession, UserRole.ADMIN); } - protected ProjectWsAction(String actionName, DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, boolean projectParameterRequired, String permission) { + protected ProjectWsAction(String actionName, DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, String permission) { super(); this.actionName = actionName; this.dbClient = dbClient; this.componentFinder = componentFinder; this.userSession = userSession; - this.projectParameterRequired = projectParameterRequired; this.permission = permission; } @Override public void define(WebService.NewController context) { WebService.NewAction action = context.createAction(actionName).setHandler(this); - action.createParam(PROJECT_PARAMETER).setRequired(projectParameterRequired); + action.createParam(PROJECT_PARAMETER).setRequired(true); configureAction(action); } @@ -69,20 +65,11 @@ public void define(WebService.NewController context) { @Override public void handle(Request request, Response response) { - Optional projectKey = Optional.ofNullable(request.param(PROJECT_PARAMETER)); + String projectKey = request.mandatoryParam(PROJECT_PARAMETER); try (DbSession dbSession = dbClient.openSession(false)) { - ProjectDto project; - if (projectKey.isPresent()) { - project = componentFinder.getProjectByKey(dbSession, projectKey.get()); - userSession.checkProjectPermission(permission, project); - } else { - if (projectParameterRequired) { - throw new IllegalArgumentException("The 'project' parameter is missing"); - } else { - project = null; - } - } + ProjectDto project = componentFinder.getProjectByKey(dbSession, projectKey); + userSession.checkProjectPermission(permission, project); handleProjectRequest(project, request, response, dbSession); } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetAzureBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetAzureBindingAction.java similarity index 91% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetAzureBindingAction.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetAzureBindingAction.java index bf04affb1..297d1bdf1 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetAzureBindingAction.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetAzureBindingAction.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; @@ -44,13 +44,13 @@ protected void configureAction(WebService.NewAction action) { @Override protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, - Request request) { + boolean monoRepo, Request request) { return new ProjectAlmSettingDto() .setProjectUuid(projectUuid) .setAlmSettingUuid(settingsUuid) .setAlmRepo(request.mandatoryParam(REPOSITORY_NAME_PARAMETER)) .setAlmSlug(request.mandatoryParam(PROJECT_NAME_PARAMETER)) - .setMonorepo(false); + .setMonorepo(monoRepo); } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBindingAction.java similarity index 82% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBindingAction.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBindingAction.java index fb4565b66..32d77c521 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBindingAction.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBindingAction.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; @@ -35,23 +35,27 @@ public abstract class SetBindingAction extends ProjectWsAction { private static final String ALM_SETTING_PARAMETER = "almSetting"; + private static final String MONOREPO_PARAMETER = "monorepo"; protected SetBindingAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, String actionName) { - super(actionName, dbClient, componentFinder, userSession, true); + super(actionName, dbClient, componentFinder, userSession); } @Override protected void configureAction(WebService.NewAction action) { - action.createParam(ALM_SETTING_PARAMETER).setRequired(true); + action.setPost(true) + .createParam(ALM_SETTING_PARAMETER).setRequired(true); + action.createParam(MONOREPO_PARAMETER).setRequired(true).setBooleanPossibleValues(); } @Override protected void handleProjectRequest(ProjectDto project, Request request, Response response, DbSession dbSession) { String almSetting = request.mandatoryParam(ALM_SETTING_PARAMETER); + boolean monoRepo = request.mandatoryParamAsBoolean(MONOREPO_PARAMETER); DbClient dbClient = getDbClient(); AlmSettingDto almSettingDto = getAlmSetting(dbClient, dbSession, almSetting); - dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, createProjectAlmSettingDto(project.getUuid(), almSettingDto.getUuid(), request), almSettingDto.getUuid(), project.getName(), project.getKey()); + dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, createProjectAlmSettingDto(project.getUuid(), almSettingDto.getUuid(), monoRepo, request), almSettingDto.getUuid(), project.getName(), project.getKey()); dbSession.commit(); response.noContent(); @@ -63,6 +67,6 @@ private static AlmSettingDto getAlmSetting(DbClient dbClient, DbSession dbSessio } - protected abstract ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, Request request); + protected abstract ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, boolean monoRepo, Request request); } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBitbucketBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBitbucketBindingAction.java similarity index 91% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBitbucketBindingAction.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBitbucketBindingAction.java index 6d85e9f8b..18049dd5b 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBitbucketBindingAction.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBitbucketBindingAction.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; @@ -43,13 +43,13 @@ protected void configureAction(WebService.NewAction action) { @Override protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, - Request request) { + boolean monoRepo, Request request) { return new ProjectAlmSettingDto() .setProjectUuid(projectUuid) .setAlmSettingUuid(settingsUuid) .setAlmRepo(request.mandatoryParam(REPOSITORY_PARAMETER)) .setAlmSlug(request.mandatoryParam(SLUG_PARAMETER)) - .setMonorepo(false); + .setMonorepo(monoRepo); } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBitbucketCloudBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBitbucketCloudBindingAction.java similarity index 90% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBitbucketCloudBindingAction.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBitbucketCloudBindingAction.java index 63a76aa3c..c94adf341 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBitbucketCloudBindingAction.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBitbucketCloudBindingAction.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Michael Clarke + * Copyright (C) 2021-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; @@ -37,16 +37,17 @@ public SetBitbucketCloudBindingAction(DbClient dbClient, ComponentFinder compone protected void configureAction(WebService.NewAction action) { super.configureAction(action); action.createParam(REPOSITORY_PARAMETER).setRequired(true); + } @Override protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, - Request request) { + boolean monoRepo, Request request) { return new ProjectAlmSettingDto() .setProjectUuid(projectUuid) .setAlmSettingUuid(settingsUuid) .setAlmRepo(request.mandatoryParam(REPOSITORY_PARAMETER)) - .setMonorepo(false); + .setMonorepo(monoRepo); } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetGithubBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetGithubBindingAction.java similarity index 92% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetGithubBindingAction.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetGithubBindingAction.java index b532d256b..a0d3875e1 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetGithubBindingAction.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetGithubBindingAction.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; @@ -42,13 +42,13 @@ protected void configureAction(WebService.NewAction action) { } @Override - protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, Request request) { + protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, boolean monoRepo, Request request) { return new ProjectAlmSettingDto() .setProjectUuid(projectUuid) .setAlmSettingUuid(settingsUuid) .setAlmRepo(request.mandatoryParam(REPOSITORY_PARAMETER)) .setSummaryCommentEnabled(request.paramAsBoolean(SUMMARY_COMMENT_PARAMETER)) - .setMonorepo(false); + .setMonorepo(monoRepo); } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetGitlabBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetGitlabBindingAction.java similarity index 90% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetGitlabBindingAction.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetGitlabBindingAction.java index 1162d587f..edfdc8d5f 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetGitlabBindingAction.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetGitlabBindingAction.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; @@ -40,12 +40,12 @@ protected void configureAction(WebService.NewAction action) { @Override protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, - Request request) { + boolean monoRepo, Request request) { return new ProjectAlmSettingDto() .setProjectUuid(projectUuid) .setAlmSettingUuid(settingsUuid) .setAlmRepo(request.param(REPOSITORY_PARAMETER)) - .setMonorepo(false); + .setMonorepo(monoRepo); } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ValidateBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/ValidateBindingAction.java similarity index 97% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ValidateBindingAction.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/ValidateBindingAction.java index 97e06cab7..1e5a6e970 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ValidateBindingAction.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/ValidateBindingAction.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Michael Clarke + * Copyright (C) 2021-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; import com.github.mc1arke.sonarqube.plugin.server.pullrequest.validator.Validator; @@ -41,7 +41,7 @@ public class ValidateBindingAction extends ProjectWsAction { private final List validators; public ValidateBindingAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, List validators) { - super("validate_binding", dbClient, componentFinder, userSession, true, UserRole.USER); + super("validate_binding", dbClient, componentFinder, userSession, UserRole.USER); this.validators = validators; } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/PullRequestWs.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/PullRequestWs.java new file mode 100644 index 000000000..ac83e24df --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/PullRequestWs.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest; + +import org.sonar.api.server.ws.WebService; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action.PullRequestWsAction; + +public class PullRequestWs implements WebService { + + private final PullRequestWsAction[] actions; + + public PullRequestWs(PullRequestWsAction... actions) { + this.actions = actions; + } + + @Override + public void define(Context context) { + NewController controller = context.createController("api/project_pull_requests"); + for (PullRequestWsAction action : actions) { + action.define(controller); + } + controller.done(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/DeleteAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/DeleteAction.java new file mode 100644 index 000000000..e0b9e4242 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/DeleteAction.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009-2022 SonarSource SA (mailto:info AT sonarsource DOT com), Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; +import org.sonar.db.project.ProjectDto; +import org.sonar.server.component.ComponentCleanerService; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.user.UserSession; + +public class DeleteAction extends ProjectWsAction { + + private static final String PULL_REQUEST_PARAMETER = "pullRequest"; + + private final UserSession userSession; + private final ComponentCleanerService componentCleanerService; + + public DeleteAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, ComponentCleanerService componentCleanerService) { + super("delete", dbClient, componentFinder); + this.userSession = userSession; + this.componentCleanerService = componentCleanerService; + } + + @Override + protected void configureAction(WebService.NewAction action) { + action.setPost(true) + .createParam(PULL_REQUEST_PARAMETER) + .setRequired(true); + } + + @Override + public void handleProjectRequest(ProjectDto project, Request request, Response response, DbSession dbSession) { + userSession.checkLoggedIn() + .checkProjectPermission(UserRole.ADMIN, project); + + String pullRequestId = request.mandatoryParam(PULL_REQUEST_PARAMETER); + + BranchDto pullRequest = getDbClient().branchDao().selectByPullRequestKey(dbSession, project.getUuid(), pullRequestId) + .filter(branch -> branch.getBranchType() == BranchType.PULL_REQUEST) + .orElseThrow(() -> new NotFoundException(String.format("Pull request '%s' is not found for project '%s'", pullRequestId, project.getKey()))); + + componentCleanerService.deleteBranch(dbSession, pullRequest); + response.noContent(); + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ListAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ListAction.java new file mode 100644 index 000000000..a434ee416 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ListAction.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2009-2022 SonarSource SA (mailto:info AT sonarsource DOT com), Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action; + +import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +import org.apache.commons.lang.StringUtils; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.DateUtils; +import org.sonar.api.web.UserRole; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.BranchDao; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.SnapshotDto; +import org.sonar.db.measure.LiveMeasureDto; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.protobuf.DbProjectBranches; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.issue.index.IssueIndex; +import org.sonar.server.issue.index.PrStatistics; +import org.sonar.server.user.UserSession; +import org.sonar.server.ws.WsUtils; +import org.sonarqube.ws.ProjectPullRequests; +import org.springframework.beans.factory.annotation.Autowired; + +import com.google.common.base.Strings; + +public class ListAction extends ProjectWsAction { + + private final UserSession userSession; + private final IssueIndex issueIndex; + private final ProtoBufWriter protoBufWriter; + + @Autowired + public ListAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, IssueIndex issueIndex) { + this(dbClient, componentFinder, userSession, issueIndex, WsUtils::writeProtobuf); + } + + ListAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, IssueIndex issueIndex, ProtoBufWriter protoBufWriter) { + super("list", dbClient, componentFinder); + this.userSession = userSession; + this.issueIndex = issueIndex; + this.protoBufWriter = protoBufWriter; + } + + @Override + protected void configureAction(WebService.NewAction action) { + //no-op + } + + @Override + public void handleProjectRequest(ProjectDto project, Request request, Response response, DbSession dbSession) { + checkPermission(project, userSession); + + BranchDao branchDao = getDbClient().branchDao(); + List pullRequests = branchDao.selectByProject(dbSession, project).stream() + .filter(b -> b.getBranchType() == BranchType.PULL_REQUEST) + .collect(Collectors.toList()); + List pullRequestUuids = pullRequests.stream().map(BranchDto::getUuid).collect(Collectors.toList()); + + Map mergeBranchesByUuid = branchDao + .selectByUuids(dbSession, pullRequests.stream() + .map(BranchDto::getMergeBranchUuid) + .filter(Objects::nonNull) + .collect(Collectors.toList())) + .stream().collect(MoreCollectors.uniqueIndex(BranchDto::getUuid)); + + Map branchStatisticsByBranchUuid = issueIndex.searchBranchStatistics(project.getUuid(), pullRequestUuids).stream() + .collect(MoreCollectors.uniqueIndex(PrStatistics::getBranchUuid, Function.identity())); + Map qualityGateMeasuresByComponentUuids = getDbClient().liveMeasureDao() + .selectByComponentUuidsAndMetricKeys(dbSession, pullRequestUuids, List.of(CoreMetrics.ALERT_STATUS_KEY)).stream() + .collect(MoreCollectors.uniqueIndex(LiveMeasureDto::getComponentUuid)); + Map analysisDateByBranchUuid = getDbClient().snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, pullRequestUuids).stream() + .collect(MoreCollectors.uniqueIndex(SnapshotDto::getComponentUuid, s -> DateUtils.formatDateTime(s.getCreatedAt()))); + + ProjectPullRequests.ListWsResponse.Builder protobufResponse = ProjectPullRequests.ListWsResponse.newBuilder(); + pullRequests + .forEach(b -> addPullRequest(protobufResponse, b, mergeBranchesByUuid, qualityGateMeasuresByComponentUuids.get(b.getUuid()), branchStatisticsByBranchUuid.get(b.getUuid()), + analysisDateByBranchUuid.get(b.getUuid()))); + protoBufWriter.write(protobufResponse.build(), request, response); + } + + private static void checkPermission(ProjectDto project, UserSession userSession) { + if (userSession.hasProjectPermission(UserRole.USER, project) || + userSession.hasProjectPermission(UserRole.SCAN, project) || + userSession.hasPermission(GlobalPermission.SCAN)) { + return; + } + throw insufficientPrivilegesException(); + } + + private static void addPullRequest(ProjectPullRequests.ListWsResponse.Builder response, BranchDto branch, Map mergeBranchesByUuid, + @Nullable LiveMeasureDto qualityGateMeasure, PrStatistics prStatistics, @Nullable String analysisDate) { + Optional mergeBranch = Optional.ofNullable(mergeBranchesByUuid.get(branch.getMergeBranchUuid())); + + ProjectPullRequests.PullRequest.Builder builder = ProjectPullRequests.PullRequest.newBuilder(); + builder.setKey(branch.getKey()); + + DbProjectBranches.PullRequestData pullRequestData = Objects.requireNonNull(branch.getPullRequestData(), "Pull request data should be available for branch type PULL_REQUEST"); + builder.setBranch(pullRequestData.getBranch()); + Optional.ofNullable(Strings.emptyToNull(pullRequestData.getUrl())).ifPresent(builder::setUrl); + Optional.ofNullable(Strings.emptyToNull(pullRequestData.getTitle())).ifPresent(builder::setTitle); + + if (mergeBranch.isPresent()) { + String mergeBranchKey = mergeBranch.get().getKey(); + builder.setBase(mergeBranchKey); + } else { + builder.setIsOrphan(true); + } + + if (StringUtils.isNotEmpty(pullRequestData.getTarget())) { + builder.setTarget(pullRequestData.getTarget()); + } else { + mergeBranch.ifPresent(branchDto -> builder.setTarget(branchDto.getKey())); + } + + Optional.ofNullable(analysisDate).ifPresent(builder::setAnalysisDate); + setQualityGate(builder, qualityGateMeasure, prStatistics); + response.addPullRequests(builder); + } + + private static void setQualityGate(ProjectPullRequests.PullRequest.Builder builder, @Nullable LiveMeasureDto qualityGateMeasure, @Nullable PrStatistics prStatistics) { + ProjectPullRequests.Status.Builder statusBuilder = ProjectPullRequests.Status.newBuilder(); + if (qualityGateMeasure != null) { + Optional.ofNullable(qualityGateMeasure.getDataAsString()).ifPresent(statusBuilder::setQualityGateStatus); + } + statusBuilder.setBugs(prStatistics == null ? 0L : prStatistics.getBugs()); + statusBuilder.setVulnerabilities(prStatistics == null ? 0L : prStatistics.getVulnerabilities()); + statusBuilder.setCodeSmells(prStatistics == null ? 0L : prStatistics.getCodeSmells()); + builder.setStatus(statusBuilder); + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ProjectWsAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ProjectWsAction.java new file mode 100644 index 000000000..d6a5daeea --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ProjectWsAction.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.project.ProjectDto; +import org.sonar.server.component.ComponentFinder; + +public abstract class ProjectWsAction implements PullRequestWsAction { + + private static final String PROJECT_PARAMETER = "project"; + + private final String actionName; + private final DbClient dbClient; + private final ComponentFinder componentFinder; + + protected ProjectWsAction(String actionName, DbClient dbClient, ComponentFinder componentFinder) { + super(); + this.actionName = actionName; + this.dbClient = dbClient; + this.componentFinder = componentFinder; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction(actionName).setHandler(this); + action.createParam(PROJECT_PARAMETER).setRequired(true); + + configureAction(action); + } + + protected abstract void configureAction(WebService.NewAction action); + + + @Override + public void handle(Request request, Response response) { + String projectKey = request.mandatoryParam(PROJECT_PARAMETER); + + try (DbSession dbSession = dbClient.openSession(false)) { + ProjectDto project = componentFinder.getProjectByKey(dbSession, projectKey); + handleProjectRequest(project, request, response, dbSession); + } + } + + protected abstract void handleProjectRequest(ProjectDto project, Request request, Response response, DbSession dbSession); + + protected DbClient getDbClient() { + return dbClient; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ProtoBufWriter.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ProtoBufWriter.java new file mode 100644 index 000000000..80fddc3f5 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ProtoBufWriter.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020-2022 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; + +import com.google.protobuf.Message; + +@FunctionalInterface +interface ProtoBufWriter { + + void write(Message message, Request request, Response response); + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/PullRequestWsAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/PullRequestWsAction.java new file mode 100644 index 000000000..64ca7956f --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/PullRequestWsAction.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action; + +import org.sonar.server.ws.WsAction; + +public interface PullRequestWsAction extends WsAction { + +} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchAgentTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchAgentTest.java index f5607b35e..28eaa7ee6 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchAgentTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchAgentTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Michael Clarke + * Copyright (C) 2021-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,18 +18,11 @@ */ package com.github.mc1arke.sonarqube.plugin; -import org.apache.commons.io.IOUtils; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.sonar.core.platform.EditionProvider; -import org.sonar.core.platform.PlatformEditionProvider; -import org.sonar.db.DbClient; -import org.sonar.db.newcodeperiod.NewCodePeriodDao; -import org.sonar.server.almsettings.MultipleAlmFeatureProvider; -import org.sonar.server.component.ComponentFinder; -import org.sonar.server.newcodeperiod.ws.SetAction; -import org.sonar.server.newcodeperiod.ws.UnsetAction; -import org.sonar.server.user.UserSession; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import java.io.IOException; import java.io.InputStream; @@ -40,49 +33,56 @@ import java.lang.reflect.Field; import java.util.Optional; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.SonarRuntime; +import org.sonar.core.platform.EditionProvider; +import org.sonar.core.platform.PlatformEditionProvider; +import org.sonar.db.DbClient; +import org.sonar.db.newcodeperiod.NewCodePeriodDao; +import org.sonar.server.almsettings.MultipleAlmFeature; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.feature.SonarQubeFeature; +import org.sonar.server.newcodeperiod.ws.SetAction; +import org.sonar.server.newcodeperiod.ws.UnsetAction; +import org.sonar.server.user.UserSession; -public class CommunityBranchAgentTest { +class CommunityBranchAgentTest { @Test - public void checkErrorThrownIfAgentArgsNotValid() { + void shouldThrowErrorIfAgentArgsNotValid() { assertThatThrownBy(() -> CommunityBranchAgent.premain("badarg", mock(Instrumentation.class))) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Invalid/missing agent argument"); } @Test - public void checkRedefineForWebLaunchRedefinesMultipleAlmFeatureClass() throws ReflectiveOperationException, IOException, UnmodifiableClassException, IllegalClassFormatException { + void shouldRedefineMultipleAlmFeatureClassForWebLaunch() throws ReflectiveOperationException, IOException, UnmodifiableClassException, IllegalClassFormatException { CustomClassloader classLoader = new CustomClassloader(); Instrumentation instrumentation = mock(Instrumentation.class); CommunityBranchAgent.premain("web", instrumentation); ArgumentCaptor classFileTransformerArgumentCaptor = ArgumentCaptor.forClass(ClassFileTransformer.class); - verify(instrumentation).retransformClasses(MultipleAlmFeatureProvider.class); + verify(instrumentation).retransformClasses(MultipleAlmFeature.class); verify(instrumentation, times(3)).addTransformer(classFileTransformerArgumentCaptor.capture()); - try (InputStream inputStream = MultipleAlmFeatureProvider.class.getResourceAsStream(MultipleAlmFeatureProvider.class.getSimpleName())) { + try (InputStream inputStream = MultipleAlmFeature.class.getResourceAsStream(MultipleAlmFeature.class.getSimpleName() + ".class")) { byte[] input = IOUtils.toByteArray(inputStream); - byte[] result = classFileTransformerArgumentCaptor.getAllValues().get(0).transform(classLoader, MultipleAlmFeatureProvider.class.getName().replaceAll("\\.", "/"), getClass(), getClass().getProtectionDomain(), input); - Class redefined = classLoader.loadClass(MultipleAlmFeatureProvider.class.getName(), result); + byte[] result = classFileTransformerArgumentCaptor.getAllValues().get(0).transform(classLoader, MultipleAlmFeature.class.getName().replaceAll("\\.", "/"), getClass(), getClass().getProtectionDomain(), input); + Class redefined = (Class) classLoader.loadClass(MultipleAlmFeature.class.getName(), result); - PlatformEditionProvider platformEditionProvider = mock(PlatformEditionProvider.class); - - Object multipleAlmFeatureProvider = redefined.getConstructor(PlatformEditionProvider.class).newInstance(platformEditionProvider); - Field editionProviderField = redefined.getDeclaredField("editionProvider"); - editionProviderField.setAccessible(true); + SonarRuntime sonarRuntime = mock(SonarRuntime.class); - assertThat(((EditionProvider) editionProviderField.get(multipleAlmFeatureProvider)).get()).isEqualTo(Optional.of(EditionProvider.Edition.ENTERPRISE)); + SonarQubeFeature multipleAlmFeatureProvider = redefined.getConstructor(SonarRuntime.class).newInstance(sonarRuntime); + assertThat(multipleAlmFeatureProvider.isEnabled()).isTrue(); } } + @Test - public void checkRedefineForWebLaunchRedefinesSetActionClass() throws ReflectiveOperationException, IOException, UnmodifiableClassException, IllegalClassFormatException { + void shouldRedefineSetActionClassForWebLaunch() throws ReflectiveOperationException, IOException, UnmodifiableClassException, IllegalClassFormatException { CustomClassloader classLoader = new CustomClassloader(); Instrumentation instrumentation = mock(Instrumentation.class); @@ -92,7 +92,7 @@ public void checkRedefineForWebLaunchRedefinesSetActionClass() throws Reflective verify(instrumentation).retransformClasses(SetAction.class); verify(instrumentation, times(3)).addTransformer(classFileTransformerArgumentCaptor.capture()); - try (InputStream inputStream = SetAction.class.getResourceAsStream(SetAction.class.getSimpleName())) { + try (InputStream inputStream = SetAction.class.getResourceAsStream(SetAction.class.getSimpleName() + ".class")) { byte[] input = IOUtils.toByteArray(inputStream); byte[] result = classFileTransformerArgumentCaptor.getAllValues().get(1).transform(classLoader, SetAction.class.getName().replaceAll("\\.", "/"), getClass(), getClass().getProtectionDomain(), input); @@ -114,7 +114,7 @@ public void checkRedefineForWebLaunchRedefinesSetActionClass() throws Reflective } @Test - public void checkRedefineForWebLaunchRedefinesUnsetActionClass() throws IOException, UnmodifiableClassException, IllegalClassFormatException, ReflectiveOperationException { + void shouldRedefinesUnsetActionClassForWebLaunch() throws IOException, UnmodifiableClassException, IllegalClassFormatException, ReflectiveOperationException { CustomClassloader classLoader = new CustomClassloader(); Instrumentation instrumentation = mock(Instrumentation.class); @@ -124,7 +124,7 @@ public void checkRedefineForWebLaunchRedefinesUnsetActionClass() throws IOExcept verify(instrumentation).retransformClasses(UnsetAction.class); verify(instrumentation, times(3)).addTransformer(classFileTransformerArgumentCaptor.capture()); - try (InputStream inputStream = SetAction.class.getResourceAsStream(SetAction.class.getSimpleName())) { + try (InputStream inputStream = SetAction.class.getResourceAsStream(SetAction.class.getSimpleName() + ".class")) { byte[] input = IOUtils.toByteArray(inputStream); byte[] result = classFileTransformerArgumentCaptor.getAllValues().get(2).transform(classLoader, UnsetAction.class.getName().replaceAll("\\.", "/"), getClass(), getClass().getProtectionDomain(), input); @@ -145,13 +145,13 @@ public void checkRedefineForWebLaunchRedefinesUnsetActionClass() throws IOExcept } @Test - public void checkRedefineForWebLaunchSkipsNonTargetClass() throws UnmodifiableClassException, ClassNotFoundException, IllegalClassFormatException { + void shouldSkipNonTargetClasForWebLaunch() throws UnmodifiableClassException, ClassNotFoundException, IllegalClassFormatException { Instrumentation instrumentation = mock(Instrumentation.class); CommunityBranchAgent.premain("web", instrumentation); ArgumentCaptor classFileTransformerArgumentCaptor = ArgumentCaptor.forClass(ClassFileTransformer.class); - verify(instrumentation).retransformClasses(MultipleAlmFeatureProvider.class); + verify(instrumentation).retransformClasses(MultipleAlmFeature.class); verify(instrumentation, times(3)).addTransformer(classFileTransformerArgumentCaptor.capture()); byte[] input = new byte[]{1, 2, 3, 4, 5, 6}; @@ -161,14 +161,14 @@ public void checkRedefineForWebLaunchSkipsNonTargetClass() throws UnmodifiableCl } @Test - public void checkRedefineForCeLaunchSkipsNonTargetClass() throws UnmodifiableClassException, ClassNotFoundException, IllegalClassFormatException { + void shouldSkipNonTargetClassForCeLunch() throws UnmodifiableClassException, ClassNotFoundException, IllegalClassFormatException { Instrumentation instrumentation = mock(Instrumentation.class); CommunityBranchAgent.premain("ce", instrumentation); ArgumentCaptor classFileTransformerArgumentCaptor = ArgumentCaptor.forClass(ClassFileTransformer.class); verify(instrumentation).retransformClasses(PlatformEditionProvider.class); - verify(instrumentation).addTransformer(classFileTransformerArgumentCaptor.capture()); + verify(instrumentation, times(2)).addTransformer(classFileTransformerArgumentCaptor.capture()); byte[] input = new byte[]{1, 2, 3, 4, 5, 6}; byte[] result = classFileTransformerArgumentCaptor.getValue().transform(getClass().getClassLoader(), "com/github/mc1arke/Dummy", getClass(), getClass().getProtectionDomain(), input); @@ -176,25 +176,38 @@ public void checkRedefineForCeLaunchSkipsNonTargetClass() throws UnmodifiableCla assertThat(result).isEqualTo(input); } - @Test - public void checkRedefineForCeLaunchRedefinesTargetClass() throws ReflectiveOperationException, IOException, UnmodifiableClassException, IllegalClassFormatException { + void shouldRedefineTargetClassesForCeLaunch() throws ReflectiveOperationException, IOException, UnmodifiableClassException, IllegalClassFormatException { Instrumentation instrumentation = mock(Instrumentation.class); CommunityBranchAgent.premain("ce", instrumentation); ArgumentCaptor classFileTransformerArgumentCaptor = ArgumentCaptor.forClass(ClassFileTransformer.class); + verify(instrumentation).retransformClasses(MultipleAlmFeature.class); verify(instrumentation).retransformClasses(PlatformEditionProvider.class); - verify(instrumentation).addTransformer(classFileTransformerArgumentCaptor.capture()); + verify(instrumentation, times(2)).addTransformer(classFileTransformerArgumentCaptor.capture()); try (InputStream inputStream = PlatformEditionProvider.class.getResourceAsStream(PlatformEditionProvider.class.getSimpleName() + ".class")) { byte[] input = IOUtils.toByteArray(inputStream); - byte[] result = classFileTransformerArgumentCaptor.getValue().transform(getClass().getClassLoader(), PlatformEditionProvider.class.getName().replaceAll("\\.", "/"), getClass(), getClass().getProtectionDomain(), input); + byte[] result = classFileTransformerArgumentCaptor.getAllValues().get(0).transform(getClass().getClassLoader(), PlatformEditionProvider.class.getName().replaceAll("\\.", "/"), getClass(), getClass().getProtectionDomain(), input); CustomClassloader classLoader = new CustomClassloader(); Class redefined = (Class) classLoader.loadClass(PlatformEditionProvider.class.getName(), result); - assertThat(redefined.getConstructor().newInstance().get()).isEqualTo(Optional.of(EditionProvider.Edition.DEVELOPER)); + assertThat(redefined.getConstructor().newInstance().get()).contains(EditionProvider.Edition.DEVELOPER); + } + + try (InputStream inputStream = MultipleAlmFeature.class.getResourceAsStream(MultipleAlmFeature.class.getSimpleName() + ".class")) { + byte[] input = IOUtils.toByteArray(inputStream); + byte[] result = classFileTransformerArgumentCaptor.getAllValues().get(1).transform(getClass().getClassLoader(), MultipleAlmFeature.class.getName().replaceAll("\\.", "/"), getClass(), getClass().getProtectionDomain(), input); + + CustomClassloader classLoader = new CustomClassloader(); + + SonarRuntime sonarRuntime = mock(SonarRuntime.class); + + Class redefined = (Class) classLoader.loadClass(MultipleAlmFeature.class.getName(), result); + SonarQubeFeature multipleAlmFeatureProvider = redefined.getConstructor(SonarRuntime.class).newInstance(sonarRuntime); + assertThat(multipleAlmFeatureProvider.isEnabled()).isTrue(); } } @@ -204,6 +217,6 @@ public Class loadClass(String name, byte[] value) { return defineClass(name, value, 0, value.length); } - }; + } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java index cbce5c46e..4fbb27cc0 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java @@ -18,6 +18,24 @@ */ package com.github.mc1arke.sonarqube.plugin; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.sonar.api.Plugin; +import org.sonar.api.SonarQubeSide; +import org.sonar.core.extension.CoreExtension; + import com.github.mc1arke.sonarqube.plugin.ce.CommunityReportAnalysisComponentProvider; import com.github.mc1arke.sonarqube.plugin.scanner.CommunityBranchConfigurationLoader; import com.github.mc1arke.sonarqube.plugin.scanner.CommunityBranchParamsValidator; @@ -25,40 +43,14 @@ import com.github.mc1arke.sonarqube.plugin.scanner.CommunityProjectPullRequestsLoader; import com.github.mc1arke.sonarqube.plugin.server.CommunityBranchFeatureExtension; import com.github.mc1arke.sonarqube.plugin.server.CommunityBranchSupportDelegate; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import org.sonar.api.Plugin; -import org.sonar.api.SonarQubeSide; -import org.sonar.core.extension.CoreExtension; - -import java.util.Arrays; -import java.util.Collections; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; /** * @author Michael Clarke */ -public class CommunityBranchPluginTest { - - private final ExpectedException expectedException = ExpectedException.none(); - - @Rule - public ExpectedException expectedException() { - return expectedException; - } +class CommunityBranchPluginTest { @Test - public void testScannerSideDefine() { + void shouldDefineClassesForScannerSide() { final CommunityBranchPlugin testCase = new CommunityBranchPlugin(); final Plugin.Context context = mock(Plugin.Context.class, Mockito.RETURNS_DEEP_STUBS); @@ -71,13 +63,12 @@ public void testScannerSideDefine() { .addExtensions(argumentCaptor.capture(), argumentCaptor.capture(), argumentCaptor.capture()); - assertEquals(Arrays.asList(CommunityProjectBranchesLoader.class, CommunityProjectPullRequestsLoader.class, - CommunityBranchConfigurationLoader.class, CommunityBranchParamsValidator.class), - argumentCaptor.getAllValues().subList(0, 4)); + assertThat(argumentCaptor.getAllValues().subList(0, 4)).isEqualTo(Arrays.asList(CommunityProjectBranchesLoader.class, CommunityProjectPullRequestsLoader.class, + CommunityBranchConfigurationLoader.class, CommunityBranchParamsValidator.class)); } @Test - public void testNonScannerSideDefine() { + void shouldDefineClassesForServerSide() { final CommunityBranchPlugin testCase = new CommunityBranchPlugin(); final Plugin.Context context = mock(Plugin.Context.class, Mockito.RETURNS_DEEP_STUBS); @@ -89,7 +80,7 @@ public void testNonScannerSideDefine() { } @Test - public void testComputeEngineSideLoad() { + void shouldDefineClassesForComputeEngineSide() { final CommunityBranchPlugin testCase = new CommunityBranchPlugin(); final CoreExtension.Context context = mock(CoreExtension.Context.class, Mockito.RETURNS_DEEP_STUBS); @@ -97,17 +88,16 @@ public void testComputeEngineSideLoad() { testCase.load(context); - final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Class.class); + final ArgumentCaptor> argumentCaptor = ArgumentCaptor.forClass(Class.class); verify(context, times(2)).addExtensions(argumentCaptor.capture(), argumentCaptor.capture()); - assertEquals(Collections.singletonList(CommunityReportAnalysisComponentProvider.class), - argumentCaptor.getAllValues().subList(0, 1)); + assertThat(argumentCaptor.getAllValues().subList(0, 1)).isEqualTo(List.of(CommunityReportAnalysisComponentProvider.class)); } @Test - public void testServerSideLoad() { + void shouldAddExtensionsForServerSideLoad() { final CommunityBranchPlugin testCase = new CommunityBranchPlugin(); final CoreExtension.Context context = mock(CoreExtension.Context.class, Mockito.RETURNS_DEEP_STUBS); @@ -118,14 +108,13 @@ public void testServerSideLoad() { final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Object.class); verify(context, times(2)).addExtensions(argumentCaptor.capture(), argumentCaptor.capture()); - assertEquals(25, argumentCaptor.getAllValues().size()); + assertThat(argumentCaptor.getAllValues()).hasSize(29); - assertEquals(Arrays.asList(CommunityBranchFeatureExtension.class, CommunityBranchSupportDelegate.class), - argumentCaptor.getAllValues().subList(0, 2)); + assertThat(argumentCaptor.getAllValues().subList(0, 2)).isEqualTo(List.of(CommunityBranchFeatureExtension.class, CommunityBranchSupportDelegate.class)); } @Test - public void testLoad() { + void shouldNotAddAnyExtensionsForScannerSideLoad() { final CommunityBranchPlugin testCase = new CommunityBranchPlugin(); final CoreExtension.Context context = mock(CoreExtension.Context.class, Mockito.RETURNS_DEEP_STUBS); @@ -137,7 +126,7 @@ public void testLoad() { } @Test - public void testGetName() { - assertEquals("Community Branch Plugin", new CommunityBranchPlugin().getName()); + void shouldReturnPluginNameForGetName() { + assertThat(new CommunityBranchPlugin().getName()).isEqualTo("Community Branch Plugin"); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchTest.java index e3a9b72c8..e30c8103c 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,76 +18,50 @@ */ package com.github.mc1arke.sonarqube.plugin.ce; -import org.hamcrest.core.IsEqual; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.db.component.BranchType; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import org.sonar.db.component.BranchType; /** * @author Michael Clarke */ -public class CommunityBranchTest { - - private final ExpectedException expectedException = ExpectedException.none(); - - @Rule - public ExpectedException expectedException() { - return expectedException; - } +class CommunityBranchTest { @Test - public void testGenerateKeyMainBranchNullFileOfPath() { + void testGenerateKeyMainBranchNullFileOfPath() { CommunityBranch testCase = new CommunityBranch("name", BranchType.PULL_REQUEST, true, null, null, null); - assertEquals("projectKey", testCase.generateKey("projectKey", null)); + assertThat(testCase.generateKey("projectKey", null)).isEqualTo("projectKey"); } @Test - public void testGenerateKeyMainBranchNonNullFileOfPathHolder() { + void testGenerateKeyMainBranchNonNullFileOfPathHolder() { CommunityBranch testCase = new CommunityBranch("name", BranchType.PULL_REQUEST, true, null, null, null); - assertEquals("projectKey", testCase.generateKey("projectKey", "")); + assertThat(testCase.generateKey("projectKey", "")).isEqualTo("projectKey"); } @Test - public void testGenerateKeyMainBranchNonNullFileOfPathContent() { + void testGenerateKeyMainBranchNonNullFileOfPathContent() { CommunityBranch testCase = new CommunityBranch("name", BranchType.PULL_REQUEST, true, null, null, null); - assertEquals("projectKey:path", testCase.generateKey("projectKey", "path")); + assertThat(testCase.generateKey("projectKey", "path")).isEqualTo("projectKey:path"); } @Test - public void testGenerateKeyNonMainBranchNonNullFileOfPathContentPullRequest() { - CommunityBranch testCase = - new CommunityBranch("name", BranchType.PULL_REQUEST, false, null, "pullRequestKey", null); - - assertEquals("projectKey:path:PULL_REQUEST:pullRequestKey", testCase.generateKey("projectKey", "path")); + void testGetPulRequestKey() { + assertThat(new CommunityBranch("name", BranchType.PULL_REQUEST, false, null, "prKey", null) + .getPullRequestKey()).isEqualTo("prKey"); } @Test - public void testGenerateKeyNonMainBranchNonNullFileOfPathContentBranch() { - CommunityBranch testCase = new CommunityBranch("name", BranchType.BRANCH, false, null, null, null); - - assertEquals("projectKey:path:BRANCH:name", testCase.generateKey("projectKey", "path")); - } - - - @Test - public void testGetPulRequestKey() { - assertEquals("prKey", new CommunityBranch("name", BranchType.PULL_REQUEST, false, null, "prKey", null) - .getPullRequestKey()); - } - - @Test - public void testGetPulRequestKeyNonPullRequest() { - expectedException - .expectMessage(IsEqual.equalTo("Only a branch of type PULL_REQUEST can have a pull request ID")); - expectedException.expect(IllegalStateException.class); - - new CommunityBranch("name", BranchType.BRANCH, false, null, "prKey", null).getPullRequestKey(); + void testGetPulRequestKeyNonPullRequest() { + CommunityBranch underTest = new CommunityBranch("name", BranchType.BRANCH, false, null, "prKey", null); + assertThatThrownBy(underTest::getPullRequestKey) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Only a branch of type PULL_REQUEST can have a pull request ID"); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabMergeRequestDecoratorIntegrationTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabMergeRequestDecoratorIntegrationTest.java index a18fa8798..9ceb16be8 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabMergeRequestDecoratorIntegrationTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabMergeRequestDecoratorIntegrationTest.java @@ -95,11 +95,11 @@ private void decorateQualityGateStatus(QualityGate.Status status) { AlmSettingDto almSettingDto = mock(AlmSettingDto.class); when(almSettingDto.getDecryptedPersonalAccessToken(any())).thenReturn("token"); when(almSettingDto.getUrl()).thenReturn(wireMockRule.baseUrl()+"/api/v4"); - when(projectAlmSettingDto.getAlmRepo()).thenReturn(repositorySlug); AnalysisDetails analysisDetails = mock(AnalysisDetails.class); when(almSettingDto.getUrl()).thenReturn(wireMockRule.baseUrl()+"/api/v4"); when(projectAlmSettingDto.getAlmRepo()).thenReturn(repositorySlug); + when(projectAlmSettingDto.getMonorepo()).thenReturn(true); when(analysisDetails.getQualityGateStatus()).thenReturn(status); when(analysisDetails.getAnalysisProjectKey()).thenReturn(projectKey); when(analysisDetails.getPullRequestId()).thenReturn(Long.toString(mergeRequestIid)); diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchFeatureExtensionTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchFeatureExtensionTest.java index 5cf7dbb18..eb19ac0c2 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchFeatureExtensionTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchFeatureExtensionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * Copyright (C) 2019-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,17 +18,24 @@ */ package com.github.mc1arke.sonarqube.plugin.server; -import org.junit.Test; +import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; /** * @author Michael Clarke */ -public class CommunityBranchFeatureExtensionTest { +class CommunityBranchFeatureExtensionTest { + + private final CommunityBranchFeatureExtension underTest = new CommunityBranchFeatureExtension(); + + @Test + void shouldReturnNameThatFrontEndLooksFor() { + assertThat(underTest.getName()).isEqualTo("branch-support"); + } @Test - public void testEnabled() { - assertTrue(new CommunityBranchFeatureExtension().isEnabled()); + void shouldReturnEnabledForFeature() { + assertThat(underTest.isEnabled()).isTrue(); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegateTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegateTest.java index 2ec0704d3..17c580ccf 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegateTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegateTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,114 +18,91 @@ */ package com.github.mc1arke.sonarqube.plugin.server; -import org.hamcrest.core.IsEqual; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.Clock; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; +import org.sonar.api.config.Configuration; import org.sonar.core.util.SequenceUuidFactory; -import org.sonar.core.util.UuidFactory; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.db.component.BranchDao; import org.sonar.db.component.BranchDto; import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDao; import org.sonar.db.component.ComponentDto; import org.sonar.server.ce.queue.BranchSupport; - -import java.time.Clock; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.sonar.server.setting.ProjectConfigurationLoader; /** * @author Michael Clarke */ -public class CommunityBranchSupportDelegateTest { - - private final ExpectedException expectedException = ExpectedException.none(); +class CommunityBranchSupportDelegateTest { - @Rule - public ExpectedException expectedException() { - return expectedException; - } + private final Clock clock = mock(Clock.class); + private final SequenceUuidFactory sequenceUuidFactory = mock(SequenceUuidFactory.class); + private final DbClient dbClient = mock(DbClient.class); + private final ProjectConfigurationLoader projectConfigurationLoader = mock(ProjectConfigurationLoader.class); + private final CommunityBranchSupportDelegate underTest = new CommunityBranchSupportDelegate(sequenceUuidFactory, dbClient, clock, projectConfigurationLoader); @Test - public void testCreateComponentKeyBranchType() { + void shouldReturnValidComponentKeyForBranchParameters() { Map params = new HashMap<>(); params.put("branch", "release-1.1"); params.put("branchType", "BRANCH"); - BranchSupport.ComponentKey componentKey = - new CommunityBranchSupportDelegate(new SequenceUuidFactory(), mock(DbClient.class), mock(Clock.class)) - .createComponentKey("yyy", params); - - assertEquals("yyy:BRANCH:release-1.1", componentKey.getDbKey()); - assertEquals("yyy", componentKey.getKey()); - assertFalse(componentKey.getPullRequestKey().isPresent()); - assertFalse(componentKey.isMainBranch()); - assertTrue(componentKey.getBranchName().isPresent()); - assertEquals("release-1.1", componentKey.getBranchName().get()); - assertTrue(componentKey.getMainBranchComponentKey().isMainBranch()); + BranchSupport.ComponentKey componentKey = underTest.createComponentKey("yyy", params); + + assertThat(componentKey).usingRecursiveComparison().isEqualTo(new CommunityComponentKey("yyy", "release-1.1", null)); } @Test - public void testCreateComponentKeyPullRequest() { + void shouldReturnValidComponentKeyForPullRequestParameters() { Map params = new HashMap<>(); params.put("pullRequest", "pullrequestkey"); - CommunityComponentKey componentKey = - new CommunityBranchSupportDelegate(new SequenceUuidFactory(), mock(DbClient.class), mock(Clock.class)) - .createComponentKey("yyy", params); - assertEquals("yyy:PULL_REQUEST:pullrequestkey", componentKey.getDbKey()); - assertEquals("yyy", componentKey.getKey()); - assertTrue(componentKey.getPullRequestKey().isPresent()); - assertEquals("pullrequestkey", componentKey.getPullRequestKey().get()); - assertFalse(componentKey.isMainBranch()); - assertFalse(componentKey.getBranchName().isPresent()); - assertTrue(componentKey.getMainBranchComponentKey().isMainBranch()); - CommunityComponentKey mainBranchComponentKey = componentKey.getMainBranchComponentKey(); - assertSame(mainBranchComponentKey, mainBranchComponentKey.getMainBranchComponentKey()); + CommunityComponentKey componentKey = underTest.createComponentKey("aaa", params); + + assertThat(componentKey).usingRecursiveComparison().isEqualTo(new CommunityComponentKey("aaa", null, "pullrequestkey")); } @Test - public void testCreateComponentKeyMissingBranchTypeAndPullParameters() { + void shouldThrowExceptionOnCreateComponentKeyMissingBranchTypeAndPullParameters() { Map params = new HashMap<>(); - expectedException.expect(IllegalArgumentException.class); - expectedException - .expectMessage(IsEqual.equalTo("One of 'branchType' or 'pullRequest' parameters must be specified")); - - new CommunityBranchSupportDelegate(new SequenceUuidFactory(), mock(DbClient.class), mock(Clock.class)) - .createComponentKey("xxx", params); + assertThatThrownBy(() -> underTest.createComponentKey("xxx", params)).isInstanceOf(IllegalArgumentException.class) + .hasMessage("One of 'branchType' or 'pullRequest' parameters must be specified"); } @Test - public void testCreateComponentKeyInvalidBranchTypeParameter() { + void shouldThrowExceptoinOnCreateComponentKeyInvalidBranchTypeParameter() { Map params = new HashMap<>(); params.put("branchType", "abc"); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage(IsEqual.equalTo("Unsupported branch type 'abc'")); - - new CommunityBranchSupportDelegate(new SequenceUuidFactory(), mock(DbClient.class), mock(Clock.class)) - .createComponentKey("xxx", params); + + assertThatThrownBy(() -> underTest.createComponentKey("xxx", params)).isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unsupported branch type 'abc'"); } @Test - public void testCreateBranchComponentComponentKeyComponentDtoKeyMismatch() { + void shouldThrowExceptionIfBranchAndComponentKeysMismatch() { DbSession dbSession = mock(DbSession.class); ComponentDto componentDto = mock(ComponentDto.class); @@ -139,174 +116,94 @@ public void testCreateBranchComponentComponentKeyComponentDtoKeyMismatch() { when(branchDto.getUuid()).thenReturn("componentUuid"); when(branchDto.getBranchType()).thenReturn(BranchType.BRANCH); - Clock clock = mock(Clock.class); when(clock.millis()).thenReturn(12345678901234L); BranchSupport.ComponentKey componentKey = mock(BranchSupport.ComponentKey.class); when(componentKey.getKey()).thenReturn("componentKey"); - when(componentKey.getDbKey()).thenReturn("dbKey"); when(componentKey.getBranchName()).thenReturn(Optional.of("dummy")); when(componentKey.getPullRequestKey()).thenReturn(Optional.empty()); - ComponentDao componentDao = spy(mock(ComponentDao.class)); + ComponentDao componentDao = mock(ComponentDao.class); - DbClient dbClient = mock(DbClient.class); when(dbClient.componentDao()).thenReturn(componentDao); - UuidFactory uuidFactory = new SequenceUuidFactory(); - - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage(IsEqual.equalTo("Component Key and Main Component Key do not match")); + assertThatThrownBy(() -> underTest.createBranchComponent(dbSession, componentKey, componentDto, branchDto)).isInstanceOf(IllegalStateException.class) + .hasMessage("Component Key and Main Component Key do not match"); - new CommunityBranchSupportDelegate(uuidFactory, dbClient, clock) - .createBranchComponent(dbSession, componentKey, componentDto, branchDto); + } + static Stream shouldCreateComponentAndBranchDtoIfValidationPassesData() { + return Stream.of( + Arguments.of("branchName", null, BranchType.BRANCH, new String[0], false), + Arguments.of(null, "pullRequestKey", BranchType.PULL_REQUEST, new String[0], false), + Arguments.of("complex-name", null, BranchType.BRANCH, new String[]{"abc", "def", "comp.*"}, true) + ); } - @Test - public void testCreateBranchComponent() { + @MethodSource("shouldCreateComponentAndBranchDtoIfValidationPassesData") + @ParameterizedTest + void shouldCreateComponentAndBranchDtoIfValidationPasses(String branchName, String pullRequestKey, BranchType branchType, + String[] retainBranchesConfiguration, boolean excludedFromPurge) { DbSession dbSession = mock(DbSession.class); ComponentDto componentDto = mock(ComponentDto.class); when(componentDto.getKey()).thenReturn("componentKey"); when(componentDto.uuid()).thenReturn("componentUuid"); - ComponentDto copyComponentDto = spy(ComponentDto.class); + ComponentDto copyComponentDto = mock(ComponentDto.class); when(componentDto.copy()).thenReturn(copyComponentDto); + when(copyComponentDto.setBranchUuid(any())).thenReturn(copyComponentDto); + when(copyComponentDto.setKey(any())).thenReturn(copyComponentDto); + when(copyComponentDto.setRootUuid(any())).thenReturn(copyComponentDto); + when(copyComponentDto.setUuidPath(any())).thenReturn(copyComponentDto); + when(copyComponentDto.setModuleUuidPath(any())).thenReturn(copyComponentDto); + when(copyComponentDto.setUuid(any())).thenReturn(copyComponentDto); + when(copyComponentDto.setMainBranchProjectUuid(any())).thenReturn(copyComponentDto); + when(copyComponentDto.setCreatedAt(any())).thenReturn(copyComponentDto); BranchDto branchDto = mock(BranchDto.class); when(branchDto.getUuid()).thenReturn("componentUuid"); when(branchDto.getKey()).thenReturn("nonDummy"); - Clock clock = mock(Clock.class); when(clock.millis()).thenReturn(12345678901234L); BranchSupport.ComponentKey componentKey = mock(BranchSupport.ComponentKey.class); when(componentKey.getKey()).thenReturn("componentKey"); - when(componentKey.getDbKey()).thenReturn("dbKey"); - when(componentKey.getBranchName()).thenReturn(Optional.of("dummy")); - when(componentKey.getPullRequestKey()).thenReturn(Optional.empty()); + when(componentKey.getBranchName()).thenReturn(Optional.ofNullable(branchName)); + when(componentKey.getPullRequestKey()).thenReturn(Optional.ofNullable(pullRequestKey)); + BranchDao branchDao = mock(BranchDao.class); ComponentDao componentDao = mock(ComponentDao.class); - - DbClient dbClient = mock(DbClient.class); when(dbClient.componentDao()).thenReturn(componentDao); + when(dbClient.branchDao()).thenReturn(branchDao); - UuidFactory uuidFactory = mock(UuidFactory.class); - when(uuidFactory.create()).then(new Answer() { - private int i = 0; + when(sequenceUuidFactory.create()).thenReturn("uuid0"); - @Override - public String answer(InvocationOnMock invocationOnMock) { - return "uuid" + (i++); - } - }); + Configuration configuration = mock(Configuration.class); + when(configuration.getStringArray(any())).thenReturn(retainBranchesConfiguration); + when(projectConfigurationLoader.loadProjectConfiguration(any(), any())).thenReturn(configuration); - ComponentDto result = new CommunityBranchSupportDelegate(uuidFactory, dbClient, clock) - .createBranchComponent(dbSession, componentKey, componentDto, branchDto); + ComponentDto result = underTest.createBranchComponent(dbSession, componentKey, componentDto, branchDto); verify(componentDao).insert(dbSession, copyComponentDto); verify(copyComponentDto).setUuid("uuid0"); - verify(copyComponentDto).setProjectUuid("uuid0"); verify(copyComponentDto).setRootUuid("uuid0"); verify(copyComponentDto).setUuidPath("."); verify(copyComponentDto).setModuleUuidPath(".uuid0."); verify(copyComponentDto).setMainBranchProjectUuid("componentUuid"); - verify(copyComponentDto).setDbKey(componentKey.getDbKey()); verify(copyComponentDto).setCreatedAt(new Date(12345678901234L)); - assertSame(copyComponentDto, result); - - - } - - @Test - public void testCreateBranchComponentUseExistingDto() { - DbSession dbSession = mock(DbSession.class); - - ComponentDto componentDto = mock(ComponentDto.class); - when(componentDto.getKey()).thenReturn("componentKey"); - when(componentDto.uuid()).thenReturn("componentUuid"); - - ComponentDto copyComponentDto = spy(ComponentDto.class); - when(componentDto.copy()).thenReturn(copyComponentDto); - - BranchDto branchDto = mock(BranchDto.class); - when(branchDto.getUuid()).thenReturn("componentUuid"); - when(branchDto.getKey()).thenReturn("dummy"); - when(branchDto.getBranchType()).thenReturn(BranchType.BRANCH); - - Clock clock = mock(Clock.class); - when(clock.millis()).thenReturn(1234567890123L); + assertThat(result).isSameAs(copyComponentDto); - BranchSupport.ComponentKey componentKey = mock(BranchSupport.ComponentKey.class); - when(componentKey.getKey()).thenReturn("componentKey"); - when(componentKey.getDbKey()).thenReturn("dbKey"); - when(componentKey.getBranchName()).thenReturn(Optional.of("dummy")); - when(componentKey.getPullRequestKey()).thenReturn(Optional.empty()); - - ComponentDao componentDao = spy(mock(ComponentDao.class)); - - DbClient dbClient = mock(DbClient.class); - when(dbClient.componentDao()).thenReturn(componentDao); - - UuidFactory uuidFactory = new SequenceUuidFactory(); - - ComponentDto result = new CommunityBranchSupportDelegate(uuidFactory, dbClient, clock) - .createBranchComponent(dbSession, componentKey, componentDto, branchDto); - - assertSame(componentDto, result); + ArgumentCaptor branchDtoArgumentCaptor = ArgumentCaptor.forClass(BranchDto.class); + verify(branchDao).insert(eq(dbSession), branchDtoArgumentCaptor.capture()); + assertThat(branchDtoArgumentCaptor.getValue()).usingRecursiveComparison().isEqualTo(new BranchDto() + .setBranchType(branchType) + .setExcludeFromPurge(excludedFromPurge) + .setProjectUuid("componentUuid") + .setKey(branchType == BranchType.BRANCH ? branchName : pullRequestKey) + .setUuid("uuid0")); } - @Test - public void testCreateBranchComponentUseExistingDto2() { - DbSession dbSession = mock(DbSession.class); - - ComponentDto componentDto = mock(ComponentDto.class); - when(componentDto.getKey()).thenReturn("componentKey"); - when(componentDto.uuid()).thenReturn("componentUuid"); - - ComponentDto copyComponentDto = mock(ComponentDto.class); - when(componentDto.copy()).thenReturn(copyComponentDto); - - BranchDto branchDto = mock(BranchDto.class); - when(branchDto.getUuid()).thenReturn("componentUuid"); - when(branchDto.getKey()).thenReturn("dummy"); - when(branchDto.getBranchType()).thenReturn(BranchType.BRANCH); - - Clock clock = mock(Clock.class); - when(clock.millis()).thenReturn(1234567890123L); - - BranchSupport.ComponentKey componentKey = mock(BranchSupport.ComponentKey.class); - when(componentKey.getKey()).thenReturn("componentKey"); - when(componentKey.getDbKey()).thenReturn("dbKey"); - when(componentKey.getBranchName()).thenReturn(Optional.empty()); - when(componentKey.getPullRequestKey()).thenReturn(Optional.empty()); - - ComponentDao componentDao = mock(ComponentDao.class); - - DbClient dbClient = mock(DbClient.class); - when(dbClient.componentDao()).thenReturn(componentDao); - - UuidFactory uuidFactory = new SequenceUuidFactory(); - - ComponentDto result = new CommunityBranchSupportDelegate(uuidFactory, dbClient, clock) - .createBranchComponent(dbSession, componentKey, componentDto, branchDto); - - verify(componentDao).insert(dbSession, copyComponentDto); - verify(copyComponentDto).setUuid("1"); - verify(copyComponentDto).setProjectUuid("1"); - verify(copyComponentDto).setRootUuid("1"); - verify(copyComponentDto).setUuidPath("."); - verify(copyComponentDto).setModuleUuidPath(".1."); - verify(copyComponentDto).setMainBranchProjectUuid("componentUuid"); - verify(copyComponentDto).setDbKey(componentKey.getDbKey()); - verify(copyComponentDto).setCreatedAt(new Date(1234567890123L)); - - assertSame(copyComponentDto, result); - - } - - } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/MonoRepoFeatureTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/MonoRepoFeatureTest.java new file mode 100644 index 000000000..3cf1582ff --- /dev/null +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/MonoRepoFeatureTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class MonoRepoFeatureTest { + + private final MonoRepoFeature underTest = new MonoRepoFeature(); + + @Test + void shouldMatchNameRequiredByFrontEnd() { + assertThat(underTest.getName()).isEqualTo("monorepo"); + } + + @Test + void shouldSetFeatureAsEnabled() { + assertThat(underTest.isEnabled()).isTrue(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/DeleteBindingActionTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/DeleteBindingActionTest.java similarity index 57% rename from src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/DeleteBindingActionTest.java rename to src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/DeleteBindingActionTest.java index 678140ef8..77e4ae342 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/DeleteBindingActionTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/DeleteBindingActionTest.java @@ -1,6 +1,27 @@ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +/* + * Copyright (C) 2020-2022 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; -import org.junit.Test; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; @@ -13,43 +34,38 @@ import org.sonar.server.component.ComponentFinder; import org.sonar.server.user.UserSession; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class DeleteBindingActionTest { +class DeleteBindingActionTest { @Test - public void testDefine() { + void shouldDefineEndpointWithParameters() { DbClient dbClient = mock(DbClient.class); UserSession userSession = mock(UserSession.class); ComponentFinder componentFinder = mock(ComponentFinder.class); DeleteBindingAction testCase = new DeleteBindingAction(dbClient, userSession, componentFinder); WebService.NewParam keyParam = mock(WebService.NewParam.class); - when(keyParam.setMaximumLength(eq(200))).thenReturn(keyParam); + when(keyParam.setMaximumLength(200)).thenReturn(keyParam); WebService.NewParam newKeyParam = mock(WebService.NewParam.class); - when(newKeyParam.setMaximumLength(eq(200))).thenReturn(newKeyParam); + when(newKeyParam.setMaximumLength(200)).thenReturn(newKeyParam); WebService.NewController newController = mock(WebService.NewController.class); WebService.NewAction newAction = mock(WebService.NewAction.class); - when(newController.createAction(eq("delete_binding"))).thenReturn(newAction); - when(newAction.setPost(eq(true))).thenReturn(newAction); - when(newAction.setHandler(eq(testCase))).thenReturn(newAction); - when(newAction.createParam(eq("project"))).thenReturn(keyParam); + when(newController.createAction("delete_binding")).thenReturn(newAction); + when(newAction.setPost(true)).thenReturn(newAction); + when(newAction.setHandler(testCase)).thenReturn(newAction); + when(newAction.createParam("project")).thenReturn(keyParam); testCase.define(newController); - verify(newAction).setHandler(eq(testCase)); + verify(newAction).setHandler(testCase); verify(keyParam).setRequired(true); } @Test - public void testHandle() { + void shouldHandleEndpointWithValidRequest() { DbClient dbClient = mock(DbClient.class); DbSession dbSession = mock(DbSession.class); - when(dbClient.openSession(eq(false))).thenReturn(dbSession); + when(dbClient.openSession(false)).thenReturn(dbSession); AlmSettingDao almSettingDao = mock(AlmSettingDao.class); when(dbClient.almSettingDao()).thenReturn(almSettingDao); @@ -60,18 +76,18 @@ public void testHandle() { ProjectDto componentDto = mock(ProjectDto.class); ComponentFinder componentFinder = mock(ComponentFinder.class); - when(componentFinder.getProjectByKey(eq(dbSession), eq("projectKey"))).thenReturn(componentDto); + when(componentFinder.getProjectByKey(dbSession, "projectKey")).thenReturn(componentDto); DeleteBindingAction testCase = new DeleteBindingAction(dbClient, userSession, componentFinder); Request request = mock(Request.class, Mockito.RETURNS_DEEP_STUBS); - when(request.param("project")).thenReturn("projectKey"); + when(request.mandatoryParam("project")).thenReturn("projectKey"); Response response = mock(Response.class, Mockito.RETURNS_DEEP_STUBS); testCase.handle(request, response); verify(dbSession).commit(); - verify(projectAlmSettingDao).deleteByProject(eq(dbSession), eq(componentDto)); + verify(projectAlmSettingDao).deleteByProject(dbSession, componentDto); verify(response).noContent(); verify(userSession).checkProjectPermission("admin", componentDto); diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetAzureBindingActionTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetAzureBindingActionTest.java similarity index 78% rename from src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetAzureBindingActionTest.java rename to src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetAzureBindingActionTest.java index a12b6034b..77cc1a584 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetAzureBindingActionTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetAzureBindingActionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,9 +16,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; @@ -33,15 +33,16 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class SetAzureBindingActionTest { +class SetAzureBindingActionTest { @Test - public void testConfigureAction() { + void shouldDefineActionWithRequiredParameters() { DbClient dbClient = mock(DbClient.class); UserSession userSession = mock(UserSession.class); ComponentFinder componentFinder = mock(ComponentFinder.class); WebService.NewAction newAction = mock(WebService.NewAction.class); + when(newAction.setPost(anyBoolean())).thenReturn(newAction); WebService.NewParam repositoryNameParameter = mock(WebService.NewParam.class); when(repositoryNameParameter.setMaximumLength(any(Integer.class))).thenReturn(repositoryNameParameter); @@ -58,16 +59,23 @@ public void testConfigureAction() { when(almSettingParameter.setRequired(anyBoolean())).thenReturn(almSettingParameter); when(newAction.createParam("almSetting")).thenReturn(almSettingParameter); + WebService.NewParam monoRepoParameter = mock(WebService.NewParam.class); + when(monoRepoParameter.setRequired(anyBoolean())).thenReturn(monoRepoParameter); + when(newAction.createParam("monorepo")).thenReturn(monoRepoParameter); + SetAzureBindingAction testCase = new SetAzureBindingAction(dbClient, componentFinder, userSession); testCase.configureAction(newAction); + verify(newAction).setPost(true); verify(repositoryNameParameter).setRequired(true); verify(projectNameParameter).setRequired(true); verify(almSettingParameter).setRequired(true); + verify(monoRepoParameter).setRequired(true); + verify(monoRepoParameter).setBooleanPossibleValues(); } @Test - public void testCreateProjectAlmSettingDto() { + void shouldHandleRequestWithValidParameters() { DbClient dbClient = mock(DbClient.class); UserSession userSession = mock(UserSession.class); ComponentFinder componentFinder = mock(ComponentFinder.class); @@ -77,9 +85,9 @@ public void testCreateProjectAlmSettingDto() { when(request.mandatoryParam("projectName")).thenReturn("project"); SetAzureBindingAction testCase = new SetAzureBindingAction(dbClient, componentFinder, userSession); - ProjectAlmSettingDto result = testCase.createProjectAlmSettingDto("projectUuid", "settingsUuid", request); + ProjectAlmSettingDto result = testCase.createProjectAlmSettingDto("projectUuid", "settingsUuid", true, request); - assertThat(result).isEqualToComparingFieldByField(new ProjectAlmSettingDto().setProjectUuid("projectUuid").setAlmSettingUuid("settingsUuid").setAlmRepo("repository").setAlmSlug("project").setMonorepo(false)); + assertThat(result).usingRecursiveComparison().isEqualTo(new ProjectAlmSettingDto().setProjectUuid("projectUuid").setAlmSettingUuid("settingsUuid").setAlmRepo("repository").setAlmSlug("project").setMonorepo(true)); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBindingActionTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBindingActionTest.java similarity index 67% rename from src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBindingActionTest.java rename to src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBindingActionTest.java index 34e41cd79..7fd27366b 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBindingActionTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBindingActionTest.java @@ -1,6 +1,35 @@ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +/* + * Copyright (C) 2020-2022 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; -import org.junit.Test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.junit.jupiter.api.Test; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; @@ -14,21 +43,10 @@ import org.sonar.server.component.ComponentFinder; import org.sonar.server.user.UserSession; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -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.when; - -public class SetBindingActionTest { +class SetBindingActionTest { @Test - public void testDefine() { + void shouldDefineActionWithRequiredParameters() { DbClient dbClient = mock(DbClient.class); ComponentFinder componentFinder = mock(ComponentFinder.class); UserSession userSession = mock(UserSession.class); @@ -36,7 +54,7 @@ public void testDefine() { SetBindingAction testCase = new SetBindingAction(dbClient, componentFinder, userSession, "dummy") { @Override - protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, Request request) { + protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, boolean monoRepo, Request request) { return projectAlmSettingDto; } }; @@ -46,37 +64,40 @@ protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, St WebService.NewController newController = mock(WebService.NewController.class); WebService.NewAction newAction = mock(WebService.NewAction.class); when(newController.createAction(any())).thenReturn(newAction); - when(newAction.setPost(eq(true))).thenReturn(newAction); - when(newAction.setHandler(eq(testCase))).thenReturn(newAction); + when(newAction.setPost(true)).thenReturn(newAction); + when(newAction.setHandler(testCase)).thenReturn(newAction); when(newAction.createParam(any())).then(i -> { WebService.NewParam newParam = mock(WebService.NewParam.class); paramMap.put(i.getArgument(0), newParam); + when(newParam.setRequired(anyBoolean())).thenReturn(newParam); return newParam; }); testCase.define(newController); - verify(newAction).createParam(eq("project")); - verify(newAction).createParam(eq("almSetting")); + verify(newAction).createParam("project"); + verify(newAction).createParam("almSetting"); verify(paramMap.get("project")).setRequired(true); verify(paramMap.get("almSetting")).setRequired(true); + verify(paramMap.get("monorepo")).setRequired(true); + verify(paramMap.get("monorepo")).setBooleanPossibleValues(); } @Test - public void testHandle() { + void shouldHandleRequestWithRequiredParameters() { DbClient dbClient = mock(DbClient.class); DbSession dbSession = mock(DbSession.class); - when(dbClient.openSession(eq(false))).thenReturn(dbSession); + when(dbClient.openSession(false)).thenReturn(dbSession); AlmSettingDao almSettingDao = mock(AlmSettingDao.class); AlmSettingDto almSettingDto = mock(AlmSettingDto.class); when(almSettingDto.getUuid()).thenReturn("almSettingsUuid"); - when(almSettingDao.selectByKey(eq(dbSession), eq("almSetting"))).thenReturn(Optional.of(almSettingDto)); + when(almSettingDao.selectByKey(dbSession, "almSetting")).thenReturn(Optional.of(almSettingDto)); when(dbClient.almSettingDao()).thenReturn(almSettingDao); ProjectAlmSettingDao projectAlmSettingDao = mock(ProjectAlmSettingDao.class); when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); ComponentFinder componentFinder = mock(ComponentFinder.class); ProjectDto componentDto = mock(ProjectDto.class); when(componentDto.getUuid()).thenReturn("projectUuid"); - when(componentFinder.getProjectByKey(eq(dbSession), eq("project"))).thenReturn(componentDto); + when(componentFinder.getProjectByKey(dbSession, "project")).thenReturn(componentDto); UserSession userSession = mock(UserSession.class); ThreadLocal capturedAction = new ThreadLocal<>(); ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); @@ -88,7 +109,7 @@ protected void configureAction(WebService.NewAction action) { } @Override - protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, Request request) { + protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, boolean monoRepo, Request request) { assertThat(projectUuid).isEqualTo("projectUuid"); assertThat(settingsUuid).isEqualTo("almSettingsUuid"); return projectAlmSettingDto; @@ -99,7 +120,7 @@ protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, St Response response = mock(Response.class); when(request.mandatoryParam("almSetting")).thenReturn("almSetting"); - when(request.param("project")).thenReturn("project"); + when(request.mandatoryParam("project")).thenReturn("project"); testCase.handle(request, response); diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBitBucketBindingActionTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBitBucketBindingActionTest.java similarity index 78% rename from src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBitBucketBindingActionTest.java rename to src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBitBucketBindingActionTest.java index 6324a8ba3..53a5f71b6 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBitBucketBindingActionTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBitBucketBindingActionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,9 +16,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; @@ -33,15 +33,16 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class SetBitBucketBindingActionTest { +class SetBitBucketBindingActionTest { @Test - public void testConfigureAction() { + void shouldDefineActionWithRequiredParameters() { DbClient dbClient = mock(DbClient.class); UserSession userSession = mock(UserSession.class); ComponentFinder componentFinder = mock(ComponentFinder.class); WebService.NewAction newAction = mock(WebService.NewAction.class); + when(newAction.setPost(anyBoolean())).thenReturn(newAction); WebService.NewParam slugParameter = mock(WebService.NewParam.class); when(slugParameter.setMaximumLength(any(Integer.class))).thenReturn(slugParameter); @@ -58,18 +59,26 @@ public void testConfigureAction() { when(almSettingParameter.setRequired(anyBoolean())).thenReturn(almSettingParameter); when(newAction.createParam("almSetting")).thenReturn(almSettingParameter); + WebService.NewParam monoRepoParameter = mock(WebService.NewParam.class); + when(monoRepoParameter.setRequired(anyBoolean())).thenReturn(monoRepoParameter); + when(newAction.createParam("monorepo")).thenReturn(monoRepoParameter); + SetBitbucketBindingAction testCase = new SetBitbucketBindingAction(dbClient, componentFinder, userSession); testCase.configureAction(newAction); + verify(newAction).setPost(true); verify(slugParameter).setRequired(true); verify(repositoryParameter).setRequired(true); verify(almSettingParameter).setRequired(true); + + verify(monoRepoParameter).setRequired(true); + verify(monoRepoParameter).setBooleanPossibleValues(); } @Test - public void testCreateProjectAlmSettingDto() { + void shouldHandleRequestWithRequiredParameters() { DbClient dbClient = mock(DbClient.class); UserSession userSession = mock(UserSession.class); ComponentFinder componentFinder = mock(ComponentFinder.class); @@ -79,9 +88,9 @@ public void testCreateProjectAlmSettingDto() { when(request.mandatoryParam("repository")).thenReturn("repository"); SetBitbucketBindingAction testCase = new SetBitbucketBindingAction(dbClient, componentFinder, userSession); - ProjectAlmSettingDto result = testCase.createProjectAlmSettingDto("projectUuid", "settingsUuid", request); + ProjectAlmSettingDto result = testCase.createProjectAlmSettingDto("projectUuid", "settingsUuid", true, request); - assertThat(result).isEqualToComparingFieldByField(new ProjectAlmSettingDto().setProjectUuid("projectUuid").setAlmSettingUuid("settingsUuid").setAlmRepo("repository").setAlmSlug("slug").setMonorepo(false)); + assertThat(result).usingRecursiveComparison().isEqualTo(new ProjectAlmSettingDto().setProjectUuid("projectUuid").setAlmSettingUuid("settingsUuid").setAlmRepo("repository").setAlmSlug("slug").setMonorepo(true)); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBitBucketCloudBindingActionTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBitBucketCloudBindingActionTest.java similarity index 76% rename from src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBitBucketCloudBindingActionTest.java rename to src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBitBucketCloudBindingActionTest.java index 6fc959fe0..281ed27af 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetBitBucketCloudBindingActionTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetBitBucketCloudBindingActionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Michael Clarke + * Copyright (C) 2021-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,9 +16,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; @@ -33,15 +33,16 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class SetBitBucketCloudBindingActionTest { +class SetBitBucketCloudBindingActionTest { @Test - public void testConfigureAction() { + void shouldDefineActionWithValidParameters() { DbClient dbClient = mock(DbClient.class); UserSession userSession = mock(UserSession.class); ComponentFinder componentFinder = mock(ComponentFinder.class); WebService.NewAction newAction = mock(WebService.NewAction.class); + when(newAction.setPost(anyBoolean())).thenReturn(newAction); WebService.NewParam repositoryParameter = mock(WebService.NewParam.class); when(repositoryParameter.setMaximumLength(any(Integer.class))).thenReturn(repositoryParameter); @@ -53,16 +54,24 @@ public void testConfigureAction() { when(almSettingParameter.setRequired(anyBoolean())).thenReturn(almSettingParameter); when(newAction.createParam("almSetting")).thenReturn(almSettingParameter); + WebService.NewParam monoRepoParameter = mock(WebService.NewParam.class); + when(monoRepoParameter.setRequired(anyBoolean())).thenReturn(monoRepoParameter); + when(newAction.createParam("monorepo")).thenReturn(monoRepoParameter); + SetBitbucketCloudBindingAction testCase = new SetBitbucketCloudBindingAction(dbClient, componentFinder, userSession); testCase.configureAction(newAction); + verify(newAction).setPost(true); verify(repositoryParameter).setRequired(true); verify(almSettingParameter).setRequired(true); + + verify(monoRepoParameter).setRequired(true); + verify(monoRepoParameter).setBooleanPossibleValues(); } @Test - public void testCreateProjectAlmSettingDto() { + void shouldHandleRequestWithValidParameters() { DbClient dbClient = mock(DbClient.class); UserSession userSession = mock(UserSession.class); ComponentFinder componentFinder = mock(ComponentFinder.class); @@ -71,9 +80,9 @@ public void testCreateProjectAlmSettingDto() { when(request.mandatoryParam("repository")).thenReturn("repository"); SetBitbucketCloudBindingAction testCase = new SetBitbucketCloudBindingAction(dbClient, componentFinder, userSession); - ProjectAlmSettingDto result = testCase.createProjectAlmSettingDto("projectUuid", "settingsUuid", request); + ProjectAlmSettingDto result = testCase.createProjectAlmSettingDto("projectUuid", "settingsUuid", true, request); - assertThat(result).isEqualToComparingFieldByField(new ProjectAlmSettingDto().setProjectUuid("projectUuid").setAlmSettingUuid("settingsUuid").setAlmRepo("repository").setMonorepo(false)); + assertThat(result).usingRecursiveComparison().isEqualTo(new ProjectAlmSettingDto().setProjectUuid("projectUuid").setAlmSettingUuid("settingsUuid").setAlmRepo("repository").setMonorepo(true)); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetGithubBindingActionTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetGithubBindingActionTest.java similarity index 78% rename from src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetGithubBindingActionTest.java rename to src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetGithubBindingActionTest.java index d38868fc4..406b3055e 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetGithubBindingActionTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetGithubBindingActionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,9 +16,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; @@ -33,15 +33,16 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class SetGithubBindingActionTest { +class SetGithubBindingActionTest { @Test - public void testConfigureAction() { + void shouldDefineActionWithRequiredParameters() { DbClient dbClient = mock(DbClient.class); UserSession userSession = mock(UserSession.class); ComponentFinder componentFinder = mock(ComponentFinder.class); WebService.NewAction newAction = mock(WebService.NewAction.class); + when(newAction.setPost(anyBoolean())).thenReturn(newAction); WebService.NewParam repositoryParameter = mock(WebService.NewParam.class); when(repositoryParameter.setMaximumLength(any(Integer.class))).thenReturn(repositoryParameter); @@ -58,15 +59,22 @@ public void testConfigureAction() { when(almSettingParameter.setRequired(anyBoolean())).thenReturn(almSettingParameter); when(newAction.createParam("almSetting")).thenReturn(almSettingParameter); + WebService.NewParam monoRepoParameter = mock(WebService.NewParam.class); + when(monoRepoParameter.setRequired(anyBoolean())).thenReturn(monoRepoParameter); + when(newAction.createParam("monorepo")).thenReturn(monoRepoParameter); + SetGithubBindingAction testCase = new SetGithubBindingAction(dbClient, componentFinder, userSession); testCase.configureAction(newAction); + verify(newAction).setPost(true); verify(repositoryParameter).setRequired(true); verify(almSettingParameter).setRequired(true); + verify(monoRepoParameter).setRequired(true); + verify(monoRepoParameter).setBooleanPossibleValues(); } @Test - public void testCreateProjectAlmSettingDto() { + void shouldHandleRequestWithRequiredParmaeters() { DbClient dbClient = mock(DbClient.class); UserSession userSession = mock(UserSession.class); ComponentFinder componentFinder = mock(ComponentFinder.class); @@ -76,9 +84,9 @@ public void testCreateProjectAlmSettingDto() { when(request.paramAsBoolean("summaryCommentEnabled")).thenReturn(true); SetGithubBindingAction testCase = new SetGithubBindingAction(dbClient, componentFinder, userSession); - ProjectAlmSettingDto result = testCase.createProjectAlmSettingDto("projectUuid", "settingsUuid", request); + ProjectAlmSettingDto result = testCase.createProjectAlmSettingDto("projectUuid", "settingsUuid", true, request); - assertThat(result).isEqualToComparingFieldByField(new ProjectAlmSettingDto().setProjectUuid("projectUuid").setAlmSettingUuid("settingsUuid").setAlmRepo("repository").setSummaryCommentEnabled(true).setMonorepo(false)); + assertThat(result).usingRecursiveComparison().isEqualTo(new ProjectAlmSettingDto().setProjectUuid("projectUuid").setAlmSettingUuid("settingsUuid").setAlmRepo("repository").setSummaryCommentEnabled(true).setMonorepo(true)); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetGitlabBindingActionTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetGitlabBindingActionTest.java similarity index 75% rename from src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetGitlabBindingActionTest.java rename to src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetGitlabBindingActionTest.java index 570a6184f..f6598d6ef 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/SetGitlabBindingActionTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/SetGitlabBindingActionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,9 +16,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; @@ -33,15 +33,16 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -public class SetGitlabBindingActionTest { +class SetGitlabBindingActionTest { @Test - public void testConfigureAction() { + void shouldDefineActionWithRequiredParameters() { DbClient dbClient = mock(DbClient.class); UserSession userSession = mock(UserSession.class); ComponentFinder componentFinder = mock(ComponentFinder.class); WebService.NewAction newAction = mock(WebService.NewAction.class); + when(newAction.setPost(anyBoolean())).thenReturn(newAction); WebService.NewParam repositoryParameter = mock(WebService.NewParam.class); when(newAction.createParam("repository")).thenReturn(repositoryParameter); @@ -50,16 +51,23 @@ public void testConfigureAction() { when(almSettingParameter.setRequired(anyBoolean())).thenReturn(almSettingParameter); when(newAction.createParam("almSetting")).thenReturn(almSettingParameter); + WebService.NewParam monoRepoParameter = mock(WebService.NewParam.class); + when(monoRepoParameter.setRequired(anyBoolean())).thenReturn(monoRepoParameter); + when(newAction.createParam("monorepo")).thenReturn(monoRepoParameter); + SetGitlabBindingAction testCase = new SetGitlabBindingAction(dbClient, componentFinder, userSession); testCase.configureAction(newAction); verify(newAction).createParam("repository"); + verify(newAction).setPost(true); verify(almSettingParameter).setRequired(true); + verify(monoRepoParameter).setRequired(true); + verify(monoRepoParameter).setBooleanPossibleValues(); } @Test - public void testCreateProjectAlmSettingDto() { + void shouldHandleRequestWithValidParameters() { DbClient dbClient = mock(DbClient.class); UserSession userSession = mock(UserSession.class); ComponentFinder componentFinder = mock(ComponentFinder.class); @@ -68,9 +76,9 @@ public void testCreateProjectAlmSettingDto() { when(request.param("repository")).thenReturn("repositoryId"); SetGitlabBindingAction testCase = new SetGitlabBindingAction(dbClient, componentFinder, userSession); - ProjectAlmSettingDto result = testCase.createProjectAlmSettingDto("projectUuid", "settingsUuid", request); + ProjectAlmSettingDto result = testCase.createProjectAlmSettingDto("projectUuid", "settingsUuid", true, request); - assertThat(result).isEqualToComparingFieldByField(new ProjectAlmSettingDto().setProjectUuid("projectUuid").setAlmSettingUuid("settingsUuid").setAlmRepo("repositoryId").setMonorepo(false)); + assertThat(result).usingRecursiveComparison().isEqualTo(new ProjectAlmSettingDto().setProjectUuid("projectUuid").setAlmSettingUuid("settingsUuid").setAlmRepo("repositoryId").setMonorepo(true)); verify(request).param("repository"); verifyNoMoreInteractions(request); } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ValidateBindingActionTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/ValidateBindingActionTest.java similarity index 98% rename from src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ValidateBindingActionTest.java rename to src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/ValidateBindingActionTest.java index a537b529d..d4ea769ce 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ValidateBindingActionTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/binding/action/ValidateBindingActionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Michael Clarke + * Copyright (C) 2021-2022 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.binding.action; import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; import com.github.mc1arke.sonarqube.plugin.server.pullrequest.validator.Validator; @@ -198,7 +198,7 @@ void testHandleProjectRequestHappyPath() { when(almSettingDao.selectByUuid(dbSession, almUuid)).thenReturn(Optional.of(almSettingDto)); when(dbClient.almSettingDao()).thenReturn(almSettingDao); - when(request.param("project")).thenReturn("project"); + when(request.mandatoryParam("project")).thenReturn("project"); underTest.handle(request, response); diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/PullRequestWsTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/PullRequestWsTest.java new file mode 100644 index 000000000..6c88d7854 --- /dev/null +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/PullRequestWsTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest; + + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; +import org.sonar.api.server.ws.WebService; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action.PullRequestWsAction; + +class PullRequestWsTest { + + @Test + void shouldCallDefineOnEachAction() { + PullRequestWsAction[] pullRequestWsActions = new PullRequestWsAction[]{mock(PullRequestWsAction.class), mock(PullRequestWsAction.class), mock(PullRequestWsAction.class)}; + + WebService.Context context = mock(WebService.Context.class); + WebService.NewController controller = mock(WebService.NewController.class); + when(context.createController(any())).thenReturn(controller); + + new PullRequestWs(pullRequestWsActions).define(context); + + for (PullRequestWsAction pullRequestWsAction : pullRequestWsActions) { + verify(pullRequestWsAction).define(controller); + } + verify(context).createController("api/project_pull_requests"); + } + +} \ No newline at end of file diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/DeleteActionTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/DeleteActionTest.java new file mode 100644 index 000000000..0c76b04a3 --- /dev/null +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/DeleteActionTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2022 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.component.BranchDao; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; +import org.sonar.db.project.ProjectDto; +import org.sonar.server.component.ComponentCleanerService; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.user.UserSession; + +class DeleteActionTest { + + private final DbClient dbClient = mock(DbClient.class); + private final UserSession userSession = mock(UserSession.class); + private final ComponentFinder componentFinder = mock(ComponentFinder.class); + private final ComponentCleanerService componentCleanerService = mock(ComponentCleanerService.class); + private final DeleteAction underTest = new DeleteAction(dbClient, componentFinder, userSession, componentCleanerService); + + @Test + void shouldDefineEndpointWithAllParameters() { + WebService.NewController newController = mock(WebService.NewController.class); + WebService.NewAction newAction = mock(WebService.NewAction.class); + when(newAction.setHandler(any())).thenReturn(newAction); + when(newController.createAction(any())).thenReturn(newAction); + WebService.NewParam projectParam = mock(WebService.NewParam.class); + WebService.NewParam pullRequestParam = mock(WebService.NewParam.class); + when(newAction.createParam(any())).thenReturn(projectParam, pullRequestParam); + + when(newAction.setPost(anyBoolean())).thenReturn(newAction); + + underTest.define(newController); + + verify(newController).createAction("delete"); + verify(newAction).setHandler(underTest); + verify(newAction).createParam("project"); + verify(newAction).createParam("pullRequest"); + verify(newAction).setPost(true); + verifyNoMoreInteractions(newAction); + verify(projectParam).setRequired(true); + verify(pullRequestParam).setRequired(true); + verifyNoMoreInteractions(projectParam); + verifyNoMoreInteractions(pullRequestParam); + + verifyNoMoreInteractions(newController); + } + + @Test + void shouldExecuteRequestWithValidParameters() { + Request request = mock(Request.class); + when(request.mandatoryParam("project")).thenReturn("project"); + when(request.mandatoryParam("pullRequest")).thenReturn("pullRequestId"); + + when(componentFinder.getProjectByKey(any(), any())).thenReturn(new ProjectDto().setKey("projectKey").setUuid("uuid0")); + + when(userSession.checkLoggedIn()).thenReturn(userSession); + + BranchDto pullRequest = new BranchDto().setBranchType(BranchType.PULL_REQUEST); + BranchDao branchDao = mock(BranchDao.class); + when(dbClient.branchDao()).thenReturn(branchDao); + when(branchDao.selectByPullRequestKey(any(), any(), any())).thenReturn(Optional.of(pullRequest)); + + Response response = mock(Response.class); + + underTest.handle(request, response); + + verify(componentCleanerService).deleteBranch(any(), eq(pullRequest)); + verify(response).noContent(); + } + + @Test + void shouldNotPerformDeleteIfUserNotLoggedIn() { + Request request = mock(Request.class); + when(request.mandatoryParam("project")).thenReturn("project"); + when(request.mandatoryParam("pullRequest")).thenReturn("pullRequestId"); + + when(componentFinder.getProjectByKey(any(), any())).thenReturn(new ProjectDto().setKey("projectKey").setUuid("uuid0")); + + when(userSession.checkLoggedIn()).thenThrow(new UnauthorizedException("Dummy")); + + Response response = mock(Response.class); + + assertThatThrownBy(() -> underTest.handle(request, response)).isInstanceOf(UnauthorizedException.class).hasMessage("Dummy"); + + verify(componentCleanerService, never()).deleteBranch(any(), any()); + verify(response, never()).noContent(); + } + + @Test + void shouldNotPerformDeleteIfUserNotProjectAdmin() { + Request request = mock(Request.class); + when(request.mandatoryParam("project")).thenReturn("project"); + when(request.mandatoryParam("pullRequest")).thenReturn("pullRequestId"); + + when(componentFinder.getProjectByKey(any(), any())).thenReturn(new ProjectDto().setKey("projectKey").setUuid("uuid0")); + + when(userSession.checkLoggedIn()).thenReturn(userSession); + when(userSession.checkProjectPermission(any(), any())).thenThrow(new UnauthorizedException("Dummy")); + + Response response = mock(Response.class); + + assertThatThrownBy(() -> underTest.handle(request, response)).isInstanceOf(UnauthorizedException.class).hasMessage("Dummy"); + + verify(componentCleanerService, never()).deleteBranch(any(), any()); + verify(response, never()).noContent(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ListActionTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ListActionTest.java new file mode 100644 index 000000000..07c29a5ce --- /dev/null +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/pullrequest/action/ListActionTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2022 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +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.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.component.BranchDao; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.SnapshotDao; +import org.sonar.db.component.SnapshotDto; +import org.sonar.db.measure.LiveMeasureDao; +import org.sonar.db.measure.LiveMeasureDto; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.protobuf.DbProjectBranches; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.issue.index.IssueIndex; +import org.sonar.server.issue.index.PrStatistics; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.ProjectPullRequests; + +class ListActionTest { + + private final DbClient dbClient = mock(DbClient.class); + private final UserSession userSession = mock(UserSession.class); + private final ComponentFinder componentFinder = mock(ComponentFinder.class); + private final IssueIndex issueIndex = mock(IssueIndex.class); + private final ProtoBufWriter protoBufWriter = mock(ProtoBufWriter.class); + private final ListAction underTest = new ListAction(dbClient, componentFinder, userSession, issueIndex, protoBufWriter); + + @Test + void shouldDefineEndpointWithProjectParameter() { + WebService.NewController newController = mock(WebService.NewController.class); + WebService.NewAction newAction = mock(WebService.NewAction.class); + when(newAction.setHandler(any())).thenReturn(newAction); + when(newController.createAction(any())).thenReturn(newAction); + WebService.NewParam projectParam = mock(WebService.NewParam.class); + when(newAction.createParam(any())).thenReturn(projectParam); + + underTest.define(newController); + + verify(newController).createAction("list"); + verify(newAction).setHandler(underTest); + verify(newAction).createParam("project"); + verifyNoMoreInteractions(newAction); + verify(projectParam).setRequired(true); + verifyNoMoreInteractions(projectParam); + + verifyNoMoreInteractions(newController); + } + + @Test + void shouldExecuteRequestWithValidParameter() { + Request request = mock(Request.class); + when(request.mandatoryParam("project")).thenReturn("project"); + + when(componentFinder.getProjectByKey(any(), any())).thenReturn(new ProjectDto().setKey("projectKey").setUuid("uuid0")); + + when(userSession.hasPermission(any())).thenReturn(true); + + BranchDao branchDao = mock(BranchDao.class); + when(dbClient.branchDao()).thenReturn(branchDao); + when(branchDao.selectByProject(any(), any())).thenReturn(List.of(new BranchDto() + .setBranchType(BranchType.PULL_REQUEST) + .setKey("prKey") + .setUuid("uuid1") + .setMergeBranchUuid("uuid2") + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder() + .setBranch("prBranch") + .setTitle("title") + .setTarget("target") + .setUrl("url") + .build()), + new BranchDto() + .setBranchType(BranchType.PULL_REQUEST) + .setKey("prKey2") + .setUuid("uuid3") + .setMergeBranchUuid("orphan") + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder() + .setBranch("prBranch2") + .setTitle("title2") + .setUrl("url2") + .build()), + new BranchDto() + .setBranchType(BranchType.PULL_REQUEST) + .setKey("prKey3") + .setUuid("uuid4") + .setMergeBranchUuid("uuid2") + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder() + .setBranch("prBranch2") + .setTitle("title3") + .setUrl("url3") + .build()))); + + when(branchDao.selectByUuids(any(), any())).thenReturn(List.of(new BranchDto() + .setUuid("uuid2") + .setKey("branch2Key"))); + + LiveMeasureDao liveMeasureDao = mock(LiveMeasureDao.class); + when(dbClient.liveMeasureDao()).thenReturn(liveMeasureDao); + when(liveMeasureDao.selectByComponentUuidsAndMetricKeys(any(), any(), any())).thenReturn(List.of(new LiveMeasureDto() + .setComponentUuid("uuid1") + .setData("live measure"))); + + SnapshotDao snapshotDao = mock(SnapshotDao.class); + when(dbClient.snapshotDao()).thenReturn(snapshotDao); + when(snapshotDao.selectLastAnalysesByRootComponentUuids(any(), any())).thenReturn(List.of(new SnapshotDto().setComponentUuid("componentUuid").setCreatedAt(1234L))); + + when(issueIndex.searchBranchStatistics(any(), any())).thenReturn(List.of(new PrStatistics("uuid1", Map.of(ScannerReport.IssueType.BUG.name(), 11L, ScannerReport.IssueType.CODE_SMELL.name(), 22L, ScannerReport.IssueType.VULNERABILITY.name(), 33L)), + new PrStatistics("uuid4", Map.of(ScannerReport.IssueType.BUG.name(), 1L, ScannerReport.IssueType.CODE_SMELL.name(), 2L, ScannerReport.IssueType.VULNERABILITY.name(), 3L)))); + + Response response = mock(Response.class); + + ProjectPullRequests.ListWsResponse expected = ProjectPullRequests.ListWsResponse.newBuilder() + .addPullRequests(ProjectPullRequests.PullRequest.newBuilder() + .setKey("prKey") + .setTitle("title") + .setBranch("prBranch") + .setBase("branch2Key") + .setStatus(ProjectPullRequests.Status.newBuilder() + .setQualityGateStatus("live measure") + .setBugs(11) + .setVulnerabilities(33) + .setCodeSmells(22) + .build()) + .setUrl("url") + .setTarget("target") + .build()) + .addPullRequests(ProjectPullRequests.PullRequest.newBuilder() + .setKey("prKey2") + .setTitle("title2") + .setBranch("prBranch2") + .setStatus(ProjectPullRequests.Status.newBuilder() + .setBugs(0) + .setCodeSmells(0) + .setVulnerabilities(0) + .build()) + .setIsOrphan(true) + .setUrl("url2")) + .addPullRequests(ProjectPullRequests.PullRequest.newBuilder() + .setKey("prKey3") + .setTitle("title3") + .setBranch("prBranch2") + .setBase("branch2Key") + .setStatus(ProjectPullRequests.Status.newBuilder() + .setBugs(1) + .setVulnerabilities(3) + .setCodeSmells(2) + .build()) + .setUrl("url3") + .setTarget("branch2Key") + .build()) + .build(); + + + underTest.handle(request, response); + + ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(ProjectPullRequests.ListWsResponse.class); + verify(protoBufWriter).write(messageArgumentCaptor.capture(), eq(request), eq(response)); + + assertThat(messageArgumentCaptor.getValue()).usingRecursiveComparison().isEqualTo(expected); + } + + @Test + void shouldNotExecuteRequestIfUserDoesNotHaveAnyPermissions() { + Request request = mock(Request.class); + when(request.mandatoryParam("project")).thenReturn("project"); + + Response response = mock(Response.class); + + assertThatThrownBy(() -> underTest.handle(request, response)).isInstanceOf(ForbiddenException.class); + + verifyNoMoreInteractions(protoBufWriter); + } + +} \ No newline at end of file