Skip to content

Commit

Permalink
#738: Prevent missing pull request data from causing API errors
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mc1arke committed Jan 1, 2024
1 parent 62a4422 commit 77522bb
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<DbProjectBranches.PullRequestData> 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();
Expand All @@ -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<String> pullRequestTarget = optionalPullRequestData.map(DbProjectBranches.PullRequestData::getTarget)
.filter(StringUtils::isNotEmpty);
if (pullRequestTarget.isPresent()) {
builder.setTarget(pullRequestTarget.get());
} else {
mergeBranch.ifPresent(branchDto -> builder.setTarget(branchDto.getKey()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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();


Expand Down

0 comments on commit 77522bb

Please sign in to comment.