diff --git a/.gitignore b/.gitignore index 7da9f5f1d..bfb47e66c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,11 @@ /.idea/ *.iml +# Eclipse +/.classpath +/.project +/.settings/ +/bin/ + #Project libs -/sonarqube-lib/ \ No newline at end of file +/sonarqube-lib/ diff --git a/build.gradle b/build.gradle index 237b6ab0f..5cde31c4b 100644 --- a/build.gradle +++ b/build.gradle @@ -68,6 +68,7 @@ dependencies { compile('io.aexp.nodes.graphql:nodes:0.5.0') { exclude group: 'com.fasterxml.jackson.core' } + compileOnly 'com.google.code.findbugs:jsr305:3.0.2' } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetails.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetails.java index 934bc94e0..45447445e 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetails.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetails.java @@ -45,7 +45,6 @@ import org.sonar.ce.task.projectanalysis.measure.Measure; import org.sonar.ce.task.projectanalysis.measure.MeasureRepository; import org.sonar.ce.task.projectanalysis.metric.MetricRepository; -import org.sonar.core.issue.DefaultIssue; import org.sonar.server.measure.Rating; import java.io.UnsupportedEncodingException; @@ -175,7 +174,7 @@ public String createAnalysisSummary(FormatterFactory formatterFactory) { issueCounts.get(RuleType.SECURITY_HOTSPOT), "Vulnerability", "Vulnerabilities"))), new ListItem(new Image("Code Smell", baseImageUrl + - "/common/vulnerability.svg?sanitize=true"), + "/common/code_smell.svg?sanitize=true"), new Text(" "), new Text( pluralOf(issueCounts.get(RuleType.CODE_SMELL), "Code Smell", "Code Smells")))), @@ -201,7 +200,7 @@ public String createAnalysisSummary(FormatterFactory formatterFactory) { } public String createAnalysisIssueSummary(PostAnalysisIssueVisitor.ComponentIssue componentIssue, FormatterFactory formatterFactory) { - final DefaultIssue issue = componentIssue.getIssue(); + final PostAnalysisIssueVisitor.LightIssue issue = componentIssue.getIssue(); String baseImageUrl = getBaseImageUrl(); @@ -293,6 +292,10 @@ public String getAnalysisProjectKey() { return project.getKey(); } + public String getAnalysisProjectName() { + return project.getName(); + } + public List findFailedConditions() { return qualityGate.getConditions().stream().filter(c -> c.getStatus() == QualityGate.EvaluationStatus.ERROR) .collect(Collectors.toList()); diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PostAnalysisIssueVisitor.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PostAnalysisIssueVisitor.java index 1eb42c942..c2eea5960 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PostAnalysisIssueVisitor.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PostAnalysisIssueVisitor.java @@ -18,6 +18,7 @@ */ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest; +import org.sonar.api.rules.RuleType; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.issue.IssueVisitor; import org.sonar.core.issue.DefaultIssue; @@ -25,6 +26,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; + +import javax.annotation.CheckForNull; public class PostAnalysisIssueVisitor extends IssueVisitor { @@ -42,20 +46,115 @@ public List getIssues() { public static class ComponentIssue { private final Component component; - private final DefaultIssue issue; + private final LightIssue issue; ComponentIssue(Component component, DefaultIssue issue) { super(); this.component = component; - this.issue = issue; + this.issue = (issue != null) ? new LightIssue(issue) : null; + // the null test is to please PostAnalysisIssueVisitorTest.checkAllIssuesCollected() } public Component getComponent() { return component; } - public DefaultIssue getIssue() { + public LightIssue getIssue() { return issue; } } + + /** + * A simple bean for holding the useful bits of a #{@link DefaultIssue}. + *
+ * It presents a subset of the #{@link DefaultIssue} interface, hence the inconsistent getters names, + * and CheckForNull annotations. + */ + public static class LightIssue { + + private final Long effortInMinutes; + private final String key; + private final Integer line; + private final String message; + private final String resolution; + private final String severity; + private final String status; + private final RuleType type; + + private LightIssue(DefaultIssue issue) { + this.effortInMinutes = issue.effortInMinutes(); + this.key = issue.key(); + this.line = issue.getLine(); + this.message = issue.getMessage(); + this.resolution = issue.resolution(); + this.severity = issue.severity(); + this.status = issue.status(); + this.type = issue.type(); + } + + @CheckForNull + public Long effortInMinutes() { + return effortInMinutes; + } + + public String key() { + return key; + } + + @CheckForNull + public Integer getLine() { + return line; + } + + @CheckForNull + public String getMessage() { + return message; + } + + @CheckForNull + public String resolution() { + return resolution; + } + + public String severity() { + return severity; + } + + public String getStatus() { + return status; + } + + public String status() { + return status; + } + + public RuleType type() { + return type; + } + + @Override + public int hashCode() { + return Objects.hash(effortInMinutes, key, line, message, resolution, severity, status, type); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + LightIssue other = (LightIssue) obj; + return Objects.equals(effortInMinutes, other.effortInMinutes) + && Objects.equals(key, other.key) + && Objects.equals(line, other.line) + && Objects.equals(message, other.message) + && Objects.equals(resolution, other.resolution) + && Objects.equals(severity, other.severity) + && Objects.equals(status, other.status) + && type == other.type; + } + + } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/CodeInsightsAnnotation.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/CodeInsightsAnnotation.java index 472bc5910..7634c0f7c 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/CodeInsightsAnnotation.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/CodeInsightsAnnotation.java @@ -26,7 +26,7 @@ public class CodeInsightsAnnotation { @JsonProperty("line") private final int line; - @JsonProperty("message") + @JsonProperty("summary") private final String message; @JsonProperty("path") private final String path; diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/cloud/CloudAnnotation.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/cloud/CloudAnnotation.java index d7627d9b3..d82573b53 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/cloud/CloudAnnotation.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/cloud/CloudAnnotation.java @@ -25,7 +25,7 @@ public class CloudAnnotation extends CodeInsightsAnnotation { @JsonProperty("external_id") private final String externalId; - @JsonProperty("summary") + @JsonProperty("link") private final String link; @JsonProperty("annotation_type") private final String annotationType; diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProvider.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProvider.java index 3fa142c62..42e0c2688 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProvider.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProvider.java @@ -127,7 +127,7 @@ public DecorationResult createCheckRun(AnalysisDetails analysisDetails, AlmSetti Map inputObjectArguments = new HashMap<>(); inputObjectArguments.put("repositoryId", repositoryAuthenticationToken.getRepositoryId()); - inputObjectArguments.put("name", "Sonarqube Results"); + inputObjectArguments.put("name", String.format("%s Sonarqube Results", analysisDetails.getAnalysisProjectName())); inputObjectArguments.put("status", RequestableCheckStatusState.COMPLETED); inputObjectArguments.put("conclusion", QualityGate.Status.OK == analysisDetails.getQualityGateStatus() ? CheckConclusionState.SUCCESS : CheckConclusionState.FAILURE); diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecorator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecorator.java index 7864f1353..cfea4dc57 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecorator.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecorator.java @@ -104,15 +104,16 @@ public DecorationResult decorateQualityGateStatus(AnalysisDetails analysis, AlmS try { final String apiURL = Optional.ofNullable(StringUtils.stripToNull(almSettingDto.getUrl())) - .orElse(analysis.getScannerProperty(PULLREQUEST_GITLAB_INSTANCE_URL) - .orElseThrow(() -> new IllegalStateException(String.format( - "Could not decorate Gitlab merge request. '%s' has not been set in scanner properties", - PULLREQUEST_GITLAB_INSTANCE_URL)))); + .orElseGet(() -> analysis.getScannerProperty(PULLREQUEST_GITLAB_INSTANCE_URL) + .orElseThrow(() -> new IllegalStateException(String.format( + "Could not decorate Gitlab merge request. '%s' has not been set in scanner properties", + PULLREQUEST_GITLAB_INSTANCE_URL)))); final String apiToken = almSettingDto.getPersonalAccessToken(); - final String projectId = analysis.getScannerProperty(PULLREQUEST_GITLAB_PROJECT_ID).orElseThrow( - () -> new IllegalStateException(String.format( - "Could not decorate Gitlab merge request. '%s' has not been set in scanner properties", - PULLREQUEST_GITLAB_PROJECT_ID))); + final String projectId = Optional.ofNullable(StringUtils.stripToNull(projectAlmSettingDto.getAlmRepo())) + .orElseGet(() -> analysis.getScannerProperty(PULLREQUEST_GITLAB_PROJECT_ID) + .orElseThrow(() -> new IllegalStateException(String.format( + "Could not decorate Gitlab merge request. '%s' has not been set in scanner properties", + PULLREQUEST_GITLAB_PROJECT_ID)))); final String pullRequestId = analysis.getBranchName(); final String projectURL = apiURL + String.format("/projects/%s", URLEncoder diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/markup/MarkdownFormatterFactory.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/markup/MarkdownFormatterFactory.java index e6f0b7942..517f09c09 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/markup/MarkdownFormatterFactory.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/markup/MarkdownFormatterFactory.java @@ -19,6 +19,7 @@ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup; import java.util.stream.IntStream; +import static com.google.common.html.HtmlEscapers.htmlEscaper; public final class MarkdownFormatterFactory implements FormatterFactory { @@ -110,7 +111,7 @@ public Formatter textFormatter() { return new BaseFormatter() { @Override public String format(Text node, FormatterFactory formatterFactory) { - return node.getContent(); + return htmlEscaper().escape(node.getContent()).trim(); } }; } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/SetGitlabBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/SetGitlabBindingAction.java index f22bdd32f..7027bcb67 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/SetGitlabBindingAction.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/SetGitlabBindingAction.java @@ -19,6 +19,7 @@ package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.gitlab; import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; import org.sonar.db.alm.setting.ProjectAlmSettingDto; import org.sonar.server.component.ComponentFinder; @@ -27,15 +28,25 @@ import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.SetBindingAction; public class SetGitlabBindingAction extends SetBindingAction { + private static final String REPOSITORY_PARAMETER = "repository"; public SetGitlabBindingAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) { super(dbClient, componentFinder, userSession, "set_gitlab_binding"); } + @Override + protected void configureAction(WebService.NewAction action) { + super.configureAction(action); + action.createParam(REPOSITORY_PARAMETER); + } + @Override protected ProjectAlmSettingDto createProjectAlmSettingDto(String projectUuid, String settingsUuid, Request request) { - return new ProjectAlmSettingDto().setProjectUuid(projectUuid).setAlmSettingUuid(settingsUuid); + return new ProjectAlmSettingDto() + .setProjectUuid(projectUuid) + .setAlmSettingUuid(settingsUuid) + .setAlmRepo(request.param(REPOSITORY_PARAMETER)); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetailsTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetailsTest.java index f4e0e7842..525113764 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetailsTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetailsTest.java @@ -45,7 +45,6 @@ import org.sonar.ce.task.projectanalysis.measure.MeasureRepository; import org.sonar.ce.task.projectanalysis.metric.Metric; import org.sonar.ce.task.projectanalysis.metric.MetricRepository; -import org.sonar.core.issue.DefaultIssue; import java.util.ArrayList; import java.util.Arrays; @@ -193,26 +192,26 @@ public void testCreateAnalysisSummary() { doReturn(treeRootHolder).when(measuresHolder).getTreeRootHolder(); PostAnalysisIssueVisitor postAnalysisIssueVisitor = mock(PostAnalysisIssueVisitor.class); - DefaultIssue issue1 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue1 = mock(PostAnalysisIssueVisitor.LightIssue.class); doReturn(Issue.STATUS_CLOSED).when(issue1).status(); - DefaultIssue issue2 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue2 = mock(PostAnalysisIssueVisitor.LightIssue.class); doReturn(Issue.STATUS_OPEN).when(issue2).status(); doReturn(RuleType.BUG).when(issue2).type(); - DefaultIssue issue3 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue3 = mock(PostAnalysisIssueVisitor.LightIssue.class); doReturn(Issue.STATUS_OPEN).when(issue3).status(); doReturn(RuleType.SECURITY_HOTSPOT).when(issue3).type(); - DefaultIssue issue4 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue4 = mock(PostAnalysisIssueVisitor.LightIssue.class); doReturn(Issue.STATUS_OPEN).when(issue4).status(); doReturn(RuleType.CODE_SMELL).when(issue4).type(); - DefaultIssue issue5 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue5 = mock(PostAnalysisIssueVisitor.LightIssue.class); doReturn(Issue.STATUS_OPEN).when(issue5).status(); doReturn(RuleType.VULNERABILITY).when(issue5).type(); - DefaultIssue issue6 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue6 = mock(PostAnalysisIssueVisitor.LightIssue.class); doReturn(Issue.STATUS_OPEN).when(issue6).status(); doReturn(RuleType.BUG).when(issue6).type(); @@ -339,7 +338,7 @@ public void testCreateAnalysisSummary() { "2 Vulnerabilities")), new ListItem(new Image( "Code Smell", - "http://localhost:9000/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true"), + "http://localhost:9000/static/communityBranchPlugin/common/code_smell.svg?sanitize=true"), new Text( " "), new Text( @@ -439,7 +438,7 @@ public void testCreateAnalysisSummary2() { "0 Vulnerabilities")), new ListItem(new Image( "Code Smell", - "http://localhost:9000/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true"), + "http://localhost:9000/static/communityBranchPlugin/common/code_smell.svg?sanitize=true"), new Text( " "), new Text( @@ -469,7 +468,7 @@ public void testCreateAnalysisSummary3() { AnalysisDetails.MeasuresHolder measuresHolder = mock(AnalysisDetails.MeasuresHolder.class); doReturn(treeRootHolder).when(measuresHolder).getTreeRootHolder(); - DefaultIssue issue = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue = mock(PostAnalysisIssueVisitor.LightIssue.class); doReturn(Issue.STATUS_OPEN).when(issue).status(); doReturn(RuleType.BUG).when(issue).type(); PostAnalysisIssueVisitor postAnalysisIssueVisitor = mock(PostAnalysisIssueVisitor.class); @@ -546,7 +545,7 @@ public void testCreateAnalysisSummary3() { "0 Vulnerabilities")), new ListItem(new Image( "Code Smell", - "http://host.name/path/common/vulnerability.svg?sanitize=true"), + "http://host.name/path/common/code_smell.svg?sanitize=true"), new Text( " "), new Text( @@ -644,7 +643,7 @@ public void testCreateAnalysisSummary4() { "0 Vulnerabilities")), new ListItem(new Image( "Code Smell", - "http://localhost:9000/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true"), + "http://localhost:9000/static/communityBranchPlugin/common/code_smell.svg?sanitize=true"), new Text( " "), new Text( diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PostAnalysisIssueVisitorTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PostAnalysisIssueVisitorTest.java index fdd871a8e..b5d9bd02f 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PostAnalysisIssueVisitorTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PostAnalysisIssueVisitorTest.java @@ -19,6 +19,7 @@ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest; import org.junit.Test; +import org.sonar.api.rules.RuleType; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.core.issue.DefaultIssue; @@ -26,10 +27,24 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; public class PostAnalysisIssueVisitorTest { + private static final long EXAMPLE_ISSUE_EFFORT_IN_MINUTES = 15L; + private static final String EXAMPLE_ISSUE_KEY = "key"; + private static final int EXAMPLE_ISSUE_LINE = 1000; + private static final String EXAMPLE_ISSUE_MESSAGE = "message"; + private static final String EXAMPLE_ISSUE_RESOLUTION = "resolution"; + private static final String EXAMPLE_ISSUE_SEVERITY = "severity"; + private static final String EXAMPLE_ISSUE_STATUS = "status"; + private static final RuleType EXAMPLE_ISSUE_TYPE = RuleType.BUG; + @Test public void checkAllIssuesCollected() { PostAnalysisIssueVisitor testCase = new PostAnalysisIssueVisitor(); @@ -50,4 +65,97 @@ public void checkAllIssuesCollected() { assertThat(testCase.getIssues().get(i).getComponent()).isEqualTo(expected.get(i).getComponent()); } } + + private DefaultIssue exampleDefaultIssue() { + DefaultIssue defaultIssue = mock(DefaultIssue.class); + doReturn(EXAMPLE_ISSUE_EFFORT_IN_MINUTES).when(defaultIssue).effortInMinutes(); + doReturn(EXAMPLE_ISSUE_KEY).when(defaultIssue).key(); + doReturn(EXAMPLE_ISSUE_LINE).when(defaultIssue).getLine(); + doReturn(EXAMPLE_ISSUE_MESSAGE).when(defaultIssue).getMessage(); + doReturn(EXAMPLE_ISSUE_RESOLUTION).when(defaultIssue).resolution(); + doReturn(EXAMPLE_ISSUE_SEVERITY).when(defaultIssue).severity(); + doReturn(EXAMPLE_ISSUE_STATUS).when(defaultIssue).status(); + doReturn(EXAMPLE_ISSUE_TYPE).when(defaultIssue).type(); + return defaultIssue; + } + + @Test + public void testLightIssueMapping() { + // mock a DefaultIssue + DefaultIssue defaultIssue = exampleDefaultIssue(); + + // map the DefaultIssue into a LightIssue (using PostAnalysisIssueVisitor to workaround private constructor) + PostAnalysisIssueVisitor visitor = new PostAnalysisIssueVisitor(); + visitor.onIssue(null, defaultIssue); + PostAnalysisIssueVisitor.LightIssue lightIssue = visitor.getIssues().get(0).getIssue(); + + // check values equality, twice (see below) + for (int i = 0; i < 2; i++) { + assertThat(lightIssue.effortInMinutes()).isEqualTo(EXAMPLE_ISSUE_EFFORT_IN_MINUTES); + assertThat(lightIssue.key()).isEqualTo(EXAMPLE_ISSUE_KEY); + assertThat(lightIssue.getLine()).isEqualTo(EXAMPLE_ISSUE_LINE); + assertThat(lightIssue.getMessage()).isEqualTo(EXAMPLE_ISSUE_MESSAGE); + assertThat(lightIssue.resolution()).isEqualTo(EXAMPLE_ISSUE_RESOLUTION); + assertThat(lightIssue.severity()).isEqualTo(EXAMPLE_ISSUE_SEVERITY); + assertThat(lightIssue.status()).isEqualTo(EXAMPLE_ISSUE_STATUS); + assertThat(lightIssue.getStatus()).isEqualTo(EXAMPLE_ISSUE_STATUS); // alias getter + assertThat(lightIssue.type()).isEqualTo(EXAMPLE_ISSUE_TYPE); + } + + // check DefaultIssue getters have been called _exactly once_ + verify(defaultIssue).effortInMinutes(); + verify(defaultIssue).key(); + verify(defaultIssue).getLine(); + verify(defaultIssue).getMessage(); + verify(defaultIssue).resolution(); + verify(defaultIssue).severity(); + verify(defaultIssue).status(); + verify(defaultIssue).type(); + verifyNoMoreInteractions(defaultIssue); + } + + @Test + public void testEqualLightIssues() { + DefaultIssue defaultIssue = exampleDefaultIssue(); + + // map the DefaultIssue into two equal LightIssues + PostAnalysisIssueVisitor visitor = new PostAnalysisIssueVisitor(); + visitor.onIssue(null, defaultIssue); + visitor.onIssue(null, defaultIssue); + PostAnalysisIssueVisitor.LightIssue lightIssue1 = visitor.getIssues().get(0).getIssue(); + PostAnalysisIssueVisitor.LightIssue lightIssue2 = visitor.getIssues().get(1).getIssue(); + + // assert equality + assertEquals(lightIssue1, lightIssue2); + assertEquals(lightIssue1.hashCode(), lightIssue2.hashCode()); + + // also assert equality on identity + assertEquals(lightIssue1, lightIssue1); + assertEquals(lightIssue1.hashCode(), lightIssue1.hashCode()); + } + + @Test + public void testDifferentLightIssues() { + DefaultIssue defaultIssue = exampleDefaultIssue(); + + // map the DefaultIssue into a first LightIssue + PostAnalysisIssueVisitor visitor = new PostAnalysisIssueVisitor(); + visitor.onIssue(null, defaultIssue); + PostAnalysisIssueVisitor.LightIssue lightIssue1 = visitor.getIssues().get(0).getIssue(); + + // map a slightly different DefaultIssue into an other LightIssue + doReturn("another message").when(defaultIssue).getMessage(); + visitor.onIssue(null, defaultIssue); + PostAnalysisIssueVisitor.LightIssue lightIssue2 = visitor.getIssues().get(1).getIssue(); + + // assert difference + assertNotEquals(lightIssue1, lightIssue2); + assertNotEquals(lightIssue1.hashCode(), lightIssue2.hashCode()); + + // also assert difference with unrelated objects, and null + assertNotEquals(lightIssue1, new Object()); + assertNotEquals(lightIssue1, null); + + } + } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecoratorTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecoratorTest.java index d8e303300..2dbbda4a7 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecoratorTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecoratorTest.java @@ -15,7 +15,6 @@ import org.sonar.api.rules.RuleType; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.ReportAttributes; -import org.sonar.core.issue.DefaultIssue; import org.sonar.db.alm.setting.AlmSettingDto; import org.sonar.db.alm.setting.ProjectAlmSettingDto; @@ -104,7 +103,7 @@ private void mockValidAnalysis() { when(component.getType()).thenReturn(Component.Type.FILE); when(component.getReportAttributes()).thenReturn(reportAttributes); - DefaultIssue defaultIssue = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue defaultIssue = mock(PostAnalysisIssueVisitor.LightIssue.class); when(defaultIssue.status()).thenReturn(Issue.STATUS_OPEN); when(defaultIssue.severity()).thenReturn(Severity.CRITICAL); when(defaultIssue.getLine()).thenReturn(ISSUE_LINE); diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProviderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProviderTest.java index 0c027be32..5e600ae82 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProviderTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProviderTest.java @@ -39,7 +39,6 @@ import org.sonar.api.rule.Severity; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.ReportAttributes; -import org.sonar.core.issue.DefaultIssue; import org.sonar.db.alm.setting.AlmSettingDto; import org.sonar.db.alm.setting.ProjectAlmSettingDto; @@ -146,7 +145,7 @@ public void createCheckRunExceptionOnInvalidIssueSeverity() throws IOException, when(component.getType()).thenReturn(Component.Type.FILE); when(component.getReportAttributes()).thenReturn(reportAttributes); - DefaultIssue defaultIssue = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue defaultIssue = mock(PostAnalysisIssueVisitor.LightIssue.class); when(defaultIssue.severity()).thenReturn("dummy"); when(defaultIssue.status()).thenReturn(Issue.STATUS_OPEN); when(defaultIssue.resolution()).thenReturn(null); @@ -239,7 +238,7 @@ private void createCheckRunHappyPath(QualityGate.Status status, String basePath, when(server.getPublicRootUrl()).thenReturn("http://sonar.server/root"); - DefaultIssue issue1 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue1 = mock(PostAnalysisIssueVisitor.LightIssue.class); when(issue1.getLine()).thenReturn(2); when(issue1.getMessage()).thenReturn(messageInput[0]); when(issue1.severity()).thenReturn(Severity.INFO); @@ -256,7 +255,7 @@ private void createCheckRunHappyPath(QualityGate.Status status, String basePath, when(componentIssue1.getComponent()).thenReturn(component1); when(componentIssue1.getIssue()).thenReturn(issue1); - DefaultIssue issue2 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue2 = mock(PostAnalysisIssueVisitor.LightIssue.class); when(issue2.getLine()).thenReturn(null); when(issue2.getMessage()).thenReturn(messageInput[1]); when(issue2.status()).thenReturn(Issue.STATUS_OPEN); @@ -273,7 +272,7 @@ private void createCheckRunHappyPath(QualityGate.Status status, String basePath, when(component2.getReportAttributes()).thenReturn(reportAttributes); when(component2.getType()).thenReturn(Component.Type.FILE); - DefaultIssue issue3 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue3 = mock(PostAnalysisIssueVisitor.LightIssue.class); when(issue3.getLine()).thenReturn(9); when(issue3.severity()).thenReturn(Severity.CRITICAL); when(issue3.getMessage()).thenReturn(messageInput[2]); @@ -290,7 +289,7 @@ private void createCheckRunHappyPath(QualityGate.Status status, String basePath, when(component3.getReportAttributes()).thenReturn(reportAttributes); when(component3.getType()).thenReturn(Component.Type.PROJECT); - DefaultIssue issue4 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue4 = mock(PostAnalysisIssueVisitor.LightIssue.class); when(issue4.getLine()).thenReturn(2); when(issue4.severity()).thenReturn(Severity.CRITICAL); when(issue4.getMessage()).thenReturn(messageInput[3]); @@ -301,7 +300,7 @@ private void createCheckRunHappyPath(QualityGate.Status status, String basePath, when(componentIssue4.getComponent()).thenReturn(component3); when(componentIssue4.getIssue()).thenReturn(issue4); - DefaultIssue issue5 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue5 = mock(PostAnalysisIssueVisitor.LightIssue.class); when(issue5.getLine()).thenReturn(1999); when(issue5.severity()).thenReturn(Severity.MAJOR); when(issue5.getMessage()).thenReturn(messageInput[4]); @@ -312,7 +311,7 @@ private void createCheckRunHappyPath(QualityGate.Status status, String basePath, when(componentIssue5.getComponent()).thenReturn(component2); when(componentIssue5.getIssue()).thenReturn(issue5); - DefaultIssue issue6 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue6 = mock(PostAnalysisIssueVisitor.LightIssue.class); when(issue6.getLine()).thenReturn(42); when(issue6.severity()).thenReturn(Severity.MINOR); when(issue6.getMessage()).thenReturn(messageInput[5]); @@ -323,7 +322,7 @@ private void createCheckRunHappyPath(QualityGate.Status status, String basePath, when(componentIssue6.getComponent()).thenReturn(component2); when(componentIssue6.getIssue()).thenReturn(issue6); - DefaultIssue issue7 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue7 = mock(PostAnalysisIssueVisitor.LightIssue.class); when(issue7.getLine()).thenReturn(42); when(issue7.severity()).thenReturn(Severity.MINOR); when(issue7.getMessage()).thenReturn(messageInput[6]); @@ -334,7 +333,7 @@ private void createCheckRunHappyPath(QualityGate.Status status, String basePath, when(componentIssue7.getComponent()).thenReturn(component2); when(componentIssue7.getIssue()).thenReturn(issue7); - DefaultIssue issue8 = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue issue8 = mock(PostAnalysisIssueVisitor.LightIssue.class); when(issue8.getLine()).thenReturn(42); when(issue8.severity()).thenReturn(Severity.MINOR); when(issue8.status()).thenReturn(Issue.STATUS_RESOLVED); @@ -354,6 +353,7 @@ private void createCheckRunHappyPath(QualityGate.Status status, String basePath, when(analysisDetails.createAnalysisSummary(any())).thenReturn("dummy summary"); when(analysisDetails.getCommitSha()).thenReturn("commit SHA"); when(analysisDetails.getAnalysisProjectKey()).thenReturn("projectKey"); + when(analysisDetails.getAnalysisProjectName()).thenReturn("projectName"); when(analysisDetails.getBranchName()).thenReturn("branchName"); when(analysisDetails.getAnalysisDate()).thenReturn(new Date(1234567890)); when(analysisDetails.getAnalysisId()).thenReturn("analysis ID"); @@ -486,7 +486,7 @@ private void createCheckRunHappyPath(QualityGate.Status status, String basePath, assertThat(annotationArgumentCaptor.getValue()).isEqualTo(expectedAnnotationObjects); verify(inputObjectBuilders.get(position + 1)).put(eq("repositoryId"), eq("repository ID")); - verify(inputObjectBuilders.get(position + 1)).put(eq("name"), eq("Sonarqube Results")); + verify(inputObjectBuilders.get(position + 1)).put(eq("name"), eq("projectName Sonarqube Results")); verify(inputObjectBuilders.get(position + 1)).put(eq("headSha"), eq("commit SHA")); verify(inputObjectBuilders.get(position + 1)).put(eq("status"), eq(RequestableCheckStatusState.COMPLETED)); verify(inputObjectBuilders.get(position + 1)).put(eq("conclusion"), eq(status == QualityGate.Status.OK ? @@ -510,7 +510,7 @@ public void checkExcessIssuesCorrectlyReported() throws IOException, GeneralSecu when(component.getReportAttributes()).thenReturn(reportAttributes); List issues = IntStream.range(0, 120) .mapToObj(i -> { - DefaultIssue defaultIssue = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue defaultIssue = mock(PostAnalysisIssueVisitor.LightIssue.class); when(defaultIssue.severity()).thenReturn(Severity.INFO); when(defaultIssue.getMessage()).thenReturn("message"); when(defaultIssue.status()).thenReturn(Issue.STATUS_OPEN); diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecoratorTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecoratorTest.java index bc67b7f56..f26385254 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecoratorTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecoratorTest.java @@ -30,7 +30,6 @@ import org.sonar.ce.task.projectanalysis.scm.Changeset; import org.sonar.ce.task.projectanalysis.scm.ScmInfo; import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepository; -import org.sonar.core.issue.DefaultIssue; import org.sonar.db.alm.setting.AlmSettingDto; import org.sonar.db.alm.setting.ProjectAlmSettingDto; @@ -89,7 +88,7 @@ public void decorateQualityGateStatus() { when(analysisDetails.getNewCoverage()).thenReturn(Optional.of(BigDecimal.TEN)); PostAnalysisIssueVisitor issueVisitor = mock(PostAnalysisIssueVisitor.class); PostAnalysisIssueVisitor.ComponentIssue componentIssue = mock(PostAnalysisIssueVisitor.ComponentIssue.class); - DefaultIssue defaultIssue = mock(DefaultIssue.class); + PostAnalysisIssueVisitor.LightIssue defaultIssue = mock(PostAnalysisIssueVisitor.LightIssue.class); when(defaultIssue.getStatus()).thenReturn(Issue.STATUS_OPEN); when(defaultIssue.getLine()).thenReturn(lineNumber); when(componentIssue.getIssue()).thenReturn(defaultIssue); diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/markup/MarkdownFormatterFactoryTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/markup/MarkdownFormatterFactoryTest.java index 0d2890b44..7b535f9bf 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/markup/MarkdownFormatterFactoryTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/markup/MarkdownFormatterFactoryTest.java @@ -84,4 +84,18 @@ public void testTextFormatter() { MarkdownFormatterFactory testCase = new MarkdownFormatterFactory(); assertEquals("Text", testCase.textFormatter().format(new Text("Text"), testCase)); } -} \ No newline at end of file + + @Test + public void testContentTextFormatterEscapedHtml(){ + MarkdownFormatterFactory testCase = new MarkdownFormatterFactory(); + assertEquals("<p> no html allowed", testCase.textFormatter().format(new Text("

no html allowed"), testCase)); + assertEquals("no html <p> allowed", testCase.textFormatter().format(new Text("no html

allowed"), testCase)); + assertEquals("</i>no html <p> allowed<i>", testCase.textFormatter().format(new Text("no html

allowed"), testCase)); + } + + @Test + public void testContentTextFormatterTrimWhitespaceAtBeginAndEnd(){ + MarkdownFormatterFactory testCase = new MarkdownFormatterFactory(); + assertEquals("", testCase.textFormatter().format(new Text(" "), testCase)); + } +} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/SetGitlabBindingActionTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/SetGitlabBindingActionTest.java index efc93cc53..7ecf910fe 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/SetGitlabBindingActionTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/SetGitlabBindingActionTest.java @@ -1,18 +1,47 @@ package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.gitlab; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyNoMoreInteractions; - import org.junit.Test; import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; import org.sonar.db.alm.setting.ProjectAlmSettingDto; import org.sonar.server.component.ComponentFinder; import org.sonar.server.user.UserSession; +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.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + public class SetGitlabBindingActionTest { + @Test + public void testConfigureAction() { + DbClient dbClient = mock(DbClient.class); + UserSession userSession = mock(UserSession.class); + ComponentFinder componentFinder = mock(ComponentFinder.class); + + WebService.NewAction newAction = mock(WebService.NewAction.class); + + WebService.NewParam repositoryParameter = mock(WebService.NewParam.class); + when(newAction.createParam(eq("repository"))).thenReturn(repositoryParameter); + + WebService.NewParam almSettingParameter = mock(WebService.NewParam.class); + when(almSettingParameter.setRequired(anyBoolean())).thenReturn(almSettingParameter); + when(newAction.createParam(eq("almSetting"))).thenReturn(almSettingParameter); + + SetGitlabBindingAction testCase = new SetGitlabBindingAction(dbClient, componentFinder, userSession); + testCase.configureAction(newAction); + + verify(newAction).createParam(eq("repository")); + + verify(almSettingParameter).setRequired(eq(true)); + } + @Test public void testCreateProjectAlmSettingDto() { DbClient dbClient = mock(DbClient.class); @@ -20,12 +49,13 @@ public void testCreateProjectAlmSettingDto() { ComponentFinder componentFinder = mock(ComponentFinder.class); Request request = mock(Request.class); + when(request.param("repository")).thenReturn("repositoryId"); SetGitlabBindingAction testCase = new SetGitlabBindingAction(dbClient, componentFinder, userSession); ProjectAlmSettingDto result = testCase.createProjectAlmSettingDto("projectUuid", "settingsUuid", request); - assertThat(result).isEqualToComparingFieldByField(new ProjectAlmSettingDto().setProjectUuid("projectUuid").setAlmSettingUuid("settingsUuid")); + assertThat(result).isEqualToComparingFieldByField(new ProjectAlmSettingDto().setProjectUuid("projectUuid").setAlmSettingUuid("settingsUuid").setAlmRepo("repositoryId")); + verify(request).param("repository"); verifyNoMoreInteractions(request); - } }