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