Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#738: Prevent missing pull request data from causing API errors #847

Merged
merged 1 commit into from
Jan 1, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
#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.
mc1arke committed Jan 1, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit f32779b28a9d27747528def07e274c9ac04669af
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
@@ -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) {
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
@@ -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();
@@ -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()));
}
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
@@ -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
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
@@ -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();