From 77522bbea8f60d481d1fb34ed426864fe1ee90f9 Mon Sep 17 00:00:00 2001 From: Michael Clarke Date: Mon, 1 Jan 2024 13:29:59 +0000 Subject: [PATCH] #738: Prevent missing pull request data from causing API errors The scanner currently skips validation of a target branch if a Pull Request is used to create a new project, so the resulting project fails to load in front-end due to the Pull Request API treating the data on that pull request as invalid. This is being overcome by validating that a target branch exists for all Pull Request submissions and rejecting the scan submission if the target branch is not found in Sonarqube. Additionally, there's a delay between a Pull Request being recorded in the database by the server component as a result of the call from the scanner, and the Compute Engine recording the Pull Request details (source, target, title etc.) against the branch. During this time the Pull Request treats that Pull Request as invalid and throws an error, meaning the project cannot be loaded through the UI, or the Pull Requests listed through the API. As the Pull Request response fields filled from the Pull Request data are not mandatory, those fields are now only being completed if the Pull Request data is set on the branch DTO rather than throwing an exception if the data isn't set. --- .../scanner/BranchConfigurationFactory.java | 12 ++--- .../ws/pullrequest/action/ListAction.java | 18 +++++--- .../BranchConfigurationFactoryTest.java | 46 +++++++++++++++++-- .../ws/pullrequest/action/ListActionTest.java | 15 +++++- 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchConfigurationFactory.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchConfigurationFactory.java index 96c87afd8..d5ce98688 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchConfigurationFactory.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchConfigurationFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Michael Clarke + * Copyright (C) 2022-2024 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,13 +40,9 @@ public BranchConfiguration createBranchConfiguration(String branchName, ProjectB } public BranchConfiguration createPullRequestConfiguration(String pullRequestKey, String pullRequestBranch, String pullRequestBase, ProjectBranches branches) { - if (branches.isEmpty()) { - return new CommunityBranchConfiguration(pullRequestBranch, BranchType.PULL_REQUEST, null, pullRequestBase, pullRequestKey); - } - - String referenceBranch = branches.get(pullRequestBase) == null ? branches.defaultBranchName() : findReferenceBranch(pullRequestBase, branches); - return new CommunityBranchConfiguration(pullRequestBranch, BranchType.PULL_REQUEST, referenceBranch, pullRequestBase, pullRequestKey); - + String targetBranch = Optional.ofNullable(pullRequestBase).orElse(branches.defaultBranchName()); + String referenceBranch = findReferenceBranch(targetBranch, branches); + return new CommunityBranchConfiguration(pullRequestBranch, BranchType.PULL_REQUEST, referenceBranch, targetBranch, pullRequestKey); } private static String findReferenceBranch(String targetBranch, ProjectBranches branches) { 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 index 0218a1594..dccef12de 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2023 SonarSource SA (mailto:info AT sonarsource DOT com), Michael Clarke + * Copyright (C) 2009-2024 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 @@ -120,10 +120,12 @@ private static void addPullRequest(ProjectPullRequests.ListWsResponse.Builder re 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); + Optional optionalPullRequestData = Optional.ofNullable(branch.getPullRequestData()); + optionalPullRequestData.ifPresent(pullRequestData -> { + 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(); @@ -132,8 +134,10 @@ private static void addPullRequest(ProjectPullRequests.ListWsResponse.Builder re builder.setIsOrphan(true); } - if (StringUtils.isNotEmpty(pullRequestData.getTarget())) { - builder.setTarget(pullRequestData.getTarget()); + Optional pullRequestTarget = optionalPullRequestData.map(DbProjectBranches.PullRequestData::getTarget) + .filter(StringUtils::isNotEmpty); + if (pullRequestTarget.isPresent()) { + builder.setTarget(pullRequestTarget.get()); } else { mergeBranch.ifPresent(branchDto -> builder.setTarget(branchDto.getKey())); } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchConfigurationFactoryTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchConfigurationFactoryTest.java index 5d4ebc490..465dbf6ae 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchConfigurationFactoryTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchConfigurationFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Michael Clarke + * Copyright (C) 2022-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,6 +19,7 @@ package com.github.mc1arke.sonarqube.plugin.scanner; import org.junit.jupiter.api.Test; +import org.sonar.api.utils.MessageException; import org.sonar.scanner.scan.branch.BranchConfiguration; import org.sonar.scanner.scan.branch.BranchInfo; import org.sonar.scanner.scan.branch.BranchType; @@ -70,14 +71,51 @@ void shouldReturnBranchWithSelfReferenceIfSpecifiedBranchDoesExist() { } @Test - void shouldReturnPullRequestWithNoTargetIfNoProjectBranchesExist() { + void shouldThrowErrorIfAttemptingToCreatePullRequestWithNoTargetIfNoProjectBranchesExist() { ProjectBranches projectBranches = mock(ProjectBranches.class); when(projectBranches.isEmpty()).thenReturn(true); + when(projectBranches.defaultBranchName()).thenReturn("default-branch-name"); BranchConfigurationFactory underTest = new BranchConfigurationFactory(); - BranchConfiguration actual = underTest.createPullRequestConfiguration("key", "source", "target", projectBranches); + assertThatThrownBy(() -> underTest.createPullRequestConfiguration("key", "source", null, projectBranches)) + .usingRecursiveComparison() + .isEqualTo(MessageException.of("No branch exists in Sonarqube with the name default-branch-name")); + } + + @Test + void shouldThrowErrorIfAttemptingToCreatePullRequestWithTargetIfNoProjectBranchesExist() { + ProjectBranches projectBranches = mock(ProjectBranches.class); + when(projectBranches.isEmpty()).thenReturn(true); + + BranchConfigurationFactory underTest = new BranchConfigurationFactory(); + assertThatThrownBy(() -> underTest.createPullRequestConfiguration("key", "source", "target", projectBranches)) + .usingRecursiveComparison() + .isEqualTo(MessageException.of("No branch exists in Sonarqube with the name target")); + } + + @Test + void shouldThrowErrorIfAttemptingToCreatePullRequestWithTargetBranchThatDoesNotExist() { + ProjectBranches projectBranches = mock(ProjectBranches.class); + when(projectBranches.isEmpty()).thenReturn(false); + + BranchConfigurationFactory underTest = new BranchConfigurationFactory(); + assertThatThrownBy(() -> underTest.createPullRequestConfiguration("key", "source", "target-branch", projectBranches)) + .usingRecursiveComparison() + .isEqualTo(MessageException.of("No branch exists in Sonarqube with the name target-branch")); + } + + @Test + void shouldReturnPullRequestWithTargetOfDefaultBranchIfTargetNotSpecifiedAndDefaultExists() { + ProjectBranches projectBranches = mock(ProjectBranches.class); + when(projectBranches.isEmpty()).thenReturn(false); + when(projectBranches.defaultBranchName()).thenReturn("defaultBranch"); + BranchInfo branchInfo = new BranchInfo("defaultBranch", BranchType.BRANCH, true, null); + when(projectBranches.get("defaultBranch")).thenReturn(branchInfo); + + BranchConfigurationFactory underTest = new BranchConfigurationFactory(); + BranchConfiguration actual = underTest.createPullRequestConfiguration("key", "source", null, projectBranches); - assertThat(actual).usingRecursiveComparison().isEqualTo(new CommunityBranchConfiguration("source", BranchType.PULL_REQUEST, null, "target", "key")); + assertThat(actual).usingRecursiveComparison().isEqualTo(new CommunityBranchConfiguration("source", BranchType.PULL_REQUEST, "defaultBranch", "defaultBranch", "key")); } @Test 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 index 93a7d701b..e55e568f4 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Michael Clarke + * Copyright (C) 2022-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -118,7 +118,12 @@ void shouldExecuteRequestWithValidParameter() { .setBranch("prBranch2") .setTitle("title3") .setUrl("url3") - .build()))); + .build()), + new BranchDto() + .setBranchType(BranchType.PULL_REQUEST) + .setKey("prKey4") + .setUuid("uuid5") + .setMergeBranchUuid("uuid2"))); when(branchDao.selectByUuids(any(), any())).thenReturn(List.of(new BranchDto() .setUuid("uuid2") @@ -166,6 +171,12 @@ void shouldExecuteRequestWithValidParameter() { .setUrl("url3") .setTarget("branch2Key") .build()) + .addPullRequests(ProjectPullRequests.PullRequest.newBuilder() + .setKey("prKey4") + .setBase("branch2Key") + .setStatus(ProjectPullRequests.Status.newBuilder().build()) + .setTarget("branch2Key") + .build()) .build();