Repository: AmadeusITGroup/sonar-stash Branch: master Commit: 924610151ea5 Files: 79 Total size: 651.2 KB Directory structure: gitextract_2kj9ve84/ ├── .gitignore ├── .travis/ │ ├── runSonarQubeAnalysis.sh │ └── script.sh ├── .travis.yml ├── LICENSE.md ├── README.md ├── dependency-check-suppression.xml ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── org/ │ │ └── sonar/ │ │ └── plugins/ │ │ └── stash/ │ │ ├── IssuePathResolver.java │ │ ├── PeekableInputStream.java │ │ ├── PluginInfo.java │ │ ├── PullRequestRef.java │ │ ├── StashIssueReportingPostJob.java │ │ ├── StashPlugin.java │ │ ├── StashPluginConfiguration.java │ │ ├── StashPluginUtils.java │ │ ├── StashProjectBuilder.java │ │ ├── StashRequestFacade.java │ │ ├── client/ │ │ │ ├── ContentType.java │ │ │ ├── StashClient.java │ │ │ └── StashCredentials.java │ │ ├── exceptions/ │ │ │ ├── StashClientException.java │ │ │ ├── StashConfigurationException.java │ │ │ ├── StashException.java │ │ │ └── StashReportExtractionException.java │ │ └── issue/ │ │ ├── MarkdownPrinter.java │ │ ├── StashComment.java │ │ ├── StashCommentReport.java │ │ ├── StashDiff.java │ │ ├── StashDiffReport.java │ │ ├── StashPullRequest.java │ │ ├── StashTask.java │ │ ├── StashUser.java │ │ └── collector/ │ │ ├── SonarQubeCollector.java │ │ └── StashCollector.java │ └── resources/ │ └── org/ │ └── sonar/ │ └── plugins/ │ └── stash/ │ └── sonar-stash.properties └── test/ ├── java/ │ └── org/ │ └── sonar/ │ └── plugins/ │ └── stash/ │ ├── CompleteITCase.java │ ├── DefaultIssue.java │ ├── DummyStashProjectBuilder.java │ ├── JavaUtilLoggingCapture.java │ ├── PeekableInputStreamTest.java │ ├── PluginInfoTest.java │ ├── StashIssueReportingPostJobTest.java │ ├── StashPluginConfigurationTest.java │ ├── StashPluginUtilsTest.java │ ├── StashRequestFacadeTest.java │ ├── StashTest.java │ ├── TestUtils.java │ ├── TestWatcherExtension.java │ ├── client/ │ │ ├── ContentTypeTest.java │ │ └── StashClientTest.java │ ├── end2end/ │ │ ├── EndToEndTest.java │ │ └── Issue194.java │ ├── fixtures/ │ │ ├── DummyIssuePathResolver.java │ │ ├── DummyPostJobContext.java │ │ ├── DummyPostJobIssue.java │ │ ├── DummyServer.java │ │ ├── DummyStashServer.java │ │ ├── MavenSonarFixtures.java │ │ ├── SonarQube.java │ │ ├── SonarQubeRule.java │ │ ├── SonarScanner.java │ │ ├── WireMockExtension.java │ │ └── WireMockResponseCallback.java │ └── issue/ │ ├── MarkdownPrinterTest.java │ ├── StashCommentReportTest.java │ ├── StashCommentTest.java │ ├── StashDiffReportTest.java │ ├── StashDiffTest.java │ ├── StashPullRequestTest.java │ ├── StashTaskTest.java │ ├── StashUserTest.java │ └── collector/ │ ├── DiffReportSample.java │ ├── SonarQubeCollectorTest.java │ └── StashCollectorTest.java └── resources/ ├── fixtures/ │ └── issue194_stash_diff.json └── foo/ ├── module1/ │ └── src/ │ └── main/ │ └── java/ │ └── Foo.java ├── module2/ │ └── src/ │ └── main/ │ └── java/ │ └── Bar.java └── sonar-project.properties ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ target .idea .project .classpath .settings *.iml /bin/ /release.properties /pom.xml.releaseBackup /dependency-reduced-pom.xml ================================================ FILE: .travis/runSonarQubeAnalysis.sh ================================================ #!/bin/sh # Exit on failure set -e # We don't want to run X times the same analysis because of the matrix configuration if [ "${SQ_RUN}" != "yes" ]; then echo "Duplicated run detected, skipping the SonarQube analysis..." exit 0 fi echo "Starting analysis by SonarQube..." mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar -B -e -V ================================================ FILE: .travis/script.sh ================================================ #!/bin/bash set -ev if [ -z "${TEST_SUITE}" ]; then echo "No \$TEST_SUITE specified, aborting!" exit 1 elif [ "unit" = "${TEST_SUITE}" ]; then mvn -e test -Pcoverage-per-test elif [ "integration" = "${TEST_SUITE}" ]; then if [ -z "${SONARQUBE_VERSION}" ]; then echo "No \$SONARQUBE_VERSION specified, aborting!" exit 1 fi tail -F "target/fixtures/sonarqube/sonarqube-${SONARQUBE_VERSION}/logs/sonar.log" & # otherwise the rails bundled with sonarqube tries to load test.yml which does # not exist export RAILS_ENV=production env -u SONAR_TOKEN mvn -e verify -Dtest.sonarqube.dist.version="${SONARQUBE_VERSION}" elif [ "dependency-check" = "${TEST_SUITE}" ]; then mvn -e org.owasp:dependency-check-maven:check fi ================================================ FILE: .travis.yml ================================================ language: java matrix: fast_finish: true include: # The basic unit-tests environments - jdk: oraclejdk8 env: - TEST_SUITE=unit - SQ_RUN=yes # It is necessary only to run the analysis once ;) # The integration tests environments - jdk: openjdk8 env: - TEST_SUITE=integration - SONARQUBE_VERSION=7.6 - jdk: openjdk8 env: - TEST_SUITE=integration - SONARQUBE_VERSION=6.7 - jdk: openjdk8 env: - TEST_SUITE=dependency-check addons: sonarcloud: organization: "default" install: - mvn dependency:go-offline script: - ./.travis/script.sh - ./.travis/runSonarQubeAnalysis.sh sudo: false dist: trusty git: depth: false notifications: email: false ================================================ FILE: LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2015 Amadeus S.A.S., Antoine Copet, Mathieu-Antoine Simon Copyright (c) 2016 Amadeus Germany GmbH, Thomas Weißschuh Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # SonarQube Stash (BitBucket) plugin ## Important note Version 7.7 of SonarQube [dropped support for the extension point sonar-stash uses](https://jira.sonarsource.com/browse/SONAR-11670). This means this plugin *can not work* on SonarQube versions >= 7.7. Therefore the 1.6.0 release is the last feature-release of `sonar-stash`. [![Build Status](https://travis-ci.org/AmadeusITGroup/sonar-stash.svg?branch=master)](https://travis-ci.org/AmadeusITGroup/sonar-stash/branches) [![SonarQube Quality Gate](https://sonarcloud.io/api/project_badges/quality_gate?project=org.sonar:sonar-stash-plugin)](https://sonarcloud.io/dashboard?id=org.sonar%3Asonar-stash-plugin) [![Unit-Tests Overall Coverage](https://sonarcloud.io/api/project_badges/measure?project=org.sonar:sonar-stash-plugin&metric=coverage)](https://sonarcloud.io/dashboard?id=org.sonar%3Asonar-stash-plugin) [![SonarQube Reported Bugs](https://sonarcloud.io/api/project_badges/measure?project=org.sonar:sonar-stash-plugin&metric=bugs)](https://sonarcloud.io/dashboard?id=org.sonar%3Asonar-stash-plugin) [![SonarQube Reported Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=org.sonar:sonar-stash-plugin&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=org.sonar%3Asonar-stash-plugin) [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=org.sonar:sonar-stash-plugin&metric=sqale_index)](https://sonarcloud.io/dashboard?id=org.sonar%3Asonar-stash-plugin) **SonarQube is now a real reviewer!** SonarQube Stash (BitBucket) plugin is a pull-request decorator which allows to integrate SonarQube violations directly into your pull-request. ![Screenshot SonarQube plugin](resources/Stash-plugin-issues.PNG) After every run, in addition of the diff view, you may access to an overview of your SQ analysis: ![Screenshot SonarQube plugin](resources/Stash-plugin-overview.PNG) ## Getting started #### Prerequisites - Git client to checkout the code - Maven 3.0.5+ - JDK 1.8+ - SonarQube 6.7 (LTS) and 7.6 - Stash (BitBucket) REST API 1.0 (3.x, 4.x) Note: these are the versions where the plugin has been tested. Other versions may or may not work, YMMV. #### To build the plugin This command generates a jar file: ``` mvn clean package ``` #### To deploy the plugin Just copy the sonar-stash-plugin jar file to the plugin folder of the expected SonarQube server and restart the SonarQube server. For instance, on Linux platform: ``` cp target/sonar-stash-plugin-1.0.jar $SONARQUBE_HOME/extensions/plugins ``` #### Configuration on SonarQube server Go to Stash general settings screen on SonarQube server to fill: ![Screenshot SonarQube plugin](resources/Sonar-plugin-configuration.PNG) **Stash base URL** (sonar.stash.url): To define Stash instance. **Stash base user** (sonar.stash.login): To define user to push violations on Stash pull-request. User must have **REPO_READ permission** for the repository. **Please notice Stash password needs to be provided to sonar-runner through sonar.stash.password on the commandline**. **Stash user slug** (sonar.stash.user.slug): If the user username contains special characters the API requires the use of a different slug. **Stash issue threshold** (sonar.stash.issue.threshold): To limit the number of issue pushed to Stash. **Stash issue severity threshold** (sonar.stash.issue.severity.threshold): Defines minimum issue severity to create diff-view comments for. Overview comment will still contain all severities. By default, all issues are pushed to Stash. **Stash timeout** (sonar.stash.timeout): To timeout when Stash Rest api does not replied with expected. **Stash reviewer approval** (sonar.stash.reviewer.approval): SonarQube is able to approve the pull-request if there is no new issue introduced by the change. By default, this feature is deactivated: if activated, **Stash base user must have REPO_WRITE permission for the repositories.** **Approval severity** (sonar.stash.reviewer.approval.severity.threshold): Only approve the pull-request if no issues higher than this threshold are detected. **Include Analysis Overview Comment** (sonar.stash.include.overview): Toggles whether a comment with overview information should be created. ![Screenshot SonarQube plugin](resources/Sonar-plugin-approver.PNG) **Stash tasks severity threshold** (sonar.stash.task.issue.severity.threshold): SonarQube is able to create tasks for all issues with a severity higher to the threshold. By default, this feature is deactivated (threshold: NONE). ![Screenshot SonarQube plugin](resources/Stash-plugin-task.PNG) **Include existing issues** (sonar.stash.include.existing.issues): Toggles whether already existing issues should also be reported. **Include Vicinity Issues Range** (sonar.stash.include.vicinity.issues.range): Specifies in which area (in lines) around the current diff issues should be reported **Excluded Rules** (sonar.stash.exclude.rules): Comma separated list of rules for which no comments should be created. **File names in overview comment**(sonar.stash.overview.filenames): Amount of filenames listed in overview comments ## How to run the plugin? #### Plugin activation for an analysis To activate the plugin, just add the following options to the SonarQube launcher (for instance with sonar-runner): For SonarQube 5.2+: ``` sonar-runner -Dsonar.analysis.mode=issues \ -Dsonar.stash.notification=true -Dsonar.stash.project= -Dsonar.stash.repository= \ -Dsonar.stash.pullrequest.id= -Dsonar.stash.password=... ``` #### Repository source configuration To tell the plugin about the root directory of your repository use the `sonar.stash.repository.root` property. This is necessary to correlate the the file locations between SonarQube and Stash. ``` sonar-runner -Dsonar.stash.repository.root="$PWD" -Dsonar.stash.notification ``` ![Screenshot SonarQube plugin](resources/Stash-plugin-logs.PNG) #### Reset comments of previous SonarQube analysis If needed, you can reset comments published during the previous SonarQube analysis of your pull-request. Please add **sonar.stash.comments.reset** option to your SonarQube analysis. Please notice only comments linked to the **sonar.stash.login** user will be deleted. This reset will be the first action performed by the plugin. ``` sonar-runner -Dsonar.analysis.mode=incremental -Dsonar.stash.notification -Dsonar.stash.comments.reset -Dsonar.stash.project= -Dsonar.stash.repository= -Dsonar.stash.pullrequest.id= -Dsonar.stash.password=... ``` ## How to activate the coverage inside the pull-request *This functionality has been moved to its own plugin: https://github.com/AmadeusITGroup/sonar-coverage-evolution* ## Protect passwords The plugin can also read the password from an environment variable. This is configured by setting `sonar.stash.password.variable` to the name of the environment variable to read. The prevents the password from leaking into the process table. # How to contribute * Before developing a major feature please open a ticket and announce it. Maybe the maintainers have strong opinions or useful hints about it. * Add unit and for major features integration tests. * Use the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html) for new development. ================================================ FILE: dependency-check-suppression.xml ================================================ org\.asynchttpclient:.* cpe:/a:netty_project:netty com\.typesafe\.netty:netty-reactive-streams:.* CVE-2015-2156 CVE-2014-3488 ================================================ FILE: pom.xml ================================================ 4.0.0 org.sonar sonar-stash-plugin 1.7.0-SNAPSHOT sonar-plugin Integration between Atlassian Stash (BitBucket) and SonarQube Stash https://github.com/AmadeusITGroup/sonar-stash MIT https://opensource.org/licenses/MIT repo scm:git:git@github.com:AmadeusITGroup/sonar-stash.git scm:git:git@github.com:AmadeusITGroup/sonar-stash.git https://github.com/AmadeusITGroup/sonar-stash HEAD GitHub Issues https://github.com/AmadeusITGroup/sonar-stash/issues Travis https://travis-ci.org/AmadeusITGroup/sonar-stash 6.7 Stash org.sonar.plugins.stash.StashPlugin UTF-8 fixme.fixme sonarqube-dist 7.6 ${project.build.directory}/fixtures/sonarqube fixme.fixme sonarscanner-dist 3.3.0.1492 ${project.build.directory}/fixtures/sonarscanner https://binaries.sonarsource.com/Distribution ${project.build.directory}/${project.artifactId}-${project.version}.jar ${project.build.directory}/fixtures/sources src/main/java/org/sonar/plugins/stash/StashPlugin.java,src/main/java/org/sonar/plugins/stash/StashPluginConfiguration.java Amadeus http://www.amadeus.com org.junit junit-bom 5.4.0 pom import org.sonarsource.sonarqube sonar-plugin-api ${sonar.version} provided com.github.cliftonlabs json-simple 3.1.0 org.asynchttpclient async-http-client 2.8.1 io.netty netty-transport-native-epoll com.google.guava guava 27.0.1-jre org.junit.jupiter junit-jupiter-api test org.junit.jupiter junit-jupiter-engine test org.assertj assertj-core 3.11.1 test org.mockito mockito-core 2.25.0 test org.mockito mockito-junit-jupiter 2.25.0 test org.awaitility awaitility 3.1.6 test org.slf4j slf4j-jdk14 1.7.5 test com.github.tomakehurst wiremock 2.21.0 test junit junit org.picocontainer picocontainer 2.15 test com.google.code.gson gson 2.8.5 test src/main/resources/ true org.sonarsource.sonar-packaging-maven-plugin sonar-packaging-maven-plugin 1.18.0.372 true org.apache.maven.plugins maven-compiler-plugin 3.8.0 1.8 1.8 UTF-8 maven-resources-plugin 3.1.0 UTF-8 org.apache.maven.plugins maven-release-plugin 2.5.3 @{project.version} false com.googlecode.maven-download-plugin download-maven-plugin 1.3.0 install-sonarqube pre-integration-test wget ${test.url.binaries.repo}/sonarqube/sonarqube-${test.sonarqube.dist.version}.zip true ${test.sonarqube.dist.outputdir} install-sonarscanner pre-integration-test wget ${test.url.binaries.repo}/sonar-scanner-cli/sonar-scanner-cli-${test.sonarscanner.dist.version}.zip true ${test.sonarscanner.dist.outputdir} org.apache.maven.plugins maven-failsafe-plugin 3.0.0-M3 integration-test verify false ${test.sonarqube.dist.outputdir} ${test.sonarqube.dist.version} ${test.sonarscanner.dist.outputdir} ${test.sonarscanner.dist.version} ${test.plugin.archive} ${test.sources.dir} org.owasp dependency-check-maven 4.0.2 true true dependency-check-suppression.xml org.apache.maven.plugins maven-surefire-plugin 3.0.0-M3 coverage-per-test org.apache.maven.plugins maven-surefire-plugin listener org.sonar.java.jacoco.JUnitListener org.jacoco jacoco-maven-plugin 0.8.3 prepare-agent prepare-agent org.sonarsource.java sonar-jacoco-listeners 5.11.0.17289 test ================================================ FILE: src/main/java/org/sonar/plugins/stash/IssuePathResolver.java ================================================ package org.sonar.plugins.stash; import org.sonar.api.batch.postjob.issue.PostJobIssue; @FunctionalInterface public interface IssuePathResolver { String getIssuePath(PostJobIssue issue); } ================================================ FILE: src/main/java/org/sonar/plugins/stash/PeekableInputStream.java ================================================ package org.sonar.plugins.stash; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; import java.util.Optional; public class PeekableInputStream extends PushbackInputStream { public PeekableInputStream(InputStream in) { super(in); } public Optional peek() throws IOException { int next = read(); if (next == -1) { return Optional.empty(); } else { unread(next); return Optional.of(Character.valueOf((char)next)); } } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/PluginInfo.java ================================================ package org.sonar.plugins.stash; public class PluginInfo { private String name; private String version; public PluginInfo(String name, String version) { this.name = name; this.version = version; } public String getVersion() { return version; } public String getName() { return name; } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/PullRequestRef.java ================================================ package org.sonar.plugins.stash; public final class PullRequestRef { private String project; private String repository; private int pullRequestId; private PullRequestRef(String project, String repository, int pullRequestId) { this.project = project; this.repository = repository; this.pullRequestId = pullRequestId; } public String project() { return project; } public String repository() { return repository; } public int pullRequestId() { return pullRequestId; } public static Builder builder() { return new Builder(); } public static class Builder { private String project; private String repository; private int pullRequestId; public Builder setProject(String value) { project = value; return this; } public Builder setRepository(String value) { repository = value; return this; } public Builder setPullRequestId(int value) { pullRequestId = value; return this; } public PullRequestRef build() { return new PullRequestRef(project, repository, pullRequestId); } } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/StashIssueReportingPostJob.java ================================================ package org.sonar.plugins.stash; import com.google.common.annotations.VisibleForTesting; import java.util.Optional; import java.util.List; import org.sonar.api.batch.InstantiationStrategy; import org.sonar.api.batch.ScannerSide; import org.sonar.api.batch.postjob.PostJob; import org.sonar.api.batch.postjob.PostJobContext; import org.sonar.api.batch.postjob.PostJobDescriptor; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.api.batch.rule.Severity; import org.sonar.api.platform.Server; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.plugins.stash.client.StashClient; import org.sonar.plugins.stash.client.StashCredentials; import org.sonar.plugins.stash.exceptions.StashConfigurationException; import org.sonar.plugins.stash.exceptions.StashException; import org.sonar.plugins.stash.issue.StashDiffReport; import org.sonar.plugins.stash.issue.StashUser; @ScannerSide @InstantiationStrategy(InstantiationStrategy.PER_BATCH) public class StashIssueReportingPostJob implements PostJob { private static final Logger LOGGER = Loggers.get(StashIssueReportingPostJob.class); private final StashPluginConfiguration config; private final StashRequestFacade stashRequestFacade; private final Server sonarQubeServer; public StashIssueReportingPostJob(StashPluginConfiguration stashPluginConfiguration, StashRequestFacade stashRequestFacade, Server sonarQubeServer) { this.config = stashPluginConfiguration; this.stashRequestFacade = stashRequestFacade; this.sonarQubeServer = sonarQubeServer; } @Override public void execute(PostJobContext context) { if (!config.hasToNotifyStash()) { LOGGER.info("{} not enabled, skipping", this); return; } try { executeThrowing(context); } catch (StashException e) { LOGGER.error("Unable to push SonarQube report to Stash", e); } } @VisibleForTesting public void executeThrowing(PostJobContext context) { String stashURL = stashRequestFacade.getStashURL(); int stashTimeout = config.getStashTimeout(); StashCredentials stashCredentials = stashRequestFacade.getCredentials(); try (StashClient stashClient = new StashClient(stashURL, stashCredentials, stashTimeout, sonarQubeServer.getVersion())) { // Down the rabbit hole... updateStashWithSonarInfo(stashClient, stashCredentials, context.issues()); } } /* * Second part of the code necessary for the executeOn() -- squid:S134 */ private void updateStashWithSonarInfo(StashClient stashClient, StashCredentials stashCredentials, Iterable issues) { int issueThreshold = stashRequestFacade.getIssueThreshold(); PullRequestRef pr = stashRequestFacade.getPullRequest(); // SonarQube objects List issueReport = stashRequestFacade.extractIssueReport(issues); StashUser stashUser = stashRequestFacade .getSonarQubeReviewer(stashCredentials.getUserSlug(), stashClient); if (stashUser == null) { throw new StashConfigurationException( "No SonarQube reviewer identified to publish to Stash the SQ analysis"); } // Get all changes exposed from Stash differential view of the pull-request StashDiffReport diffReport = stashRequestFacade.getPullRequestDiffReport(pr, stashClient); if (diffReport == null) { throw new StashConfigurationException( "No Stash differential report available to process the SQ analysis"); } // if requested, reset all comments linked to the pull-request if (config.resetComments()) { stashRequestFacade.resetComments(pr, diffReport, stashUser, stashClient); } boolean canApprovePullrequest = config.canApprovePullRequest(); if (canApprovePullrequest) { stashRequestFacade.addPullRequestReviewer(pr, stashCredentials.getUserSlug(), stashClient); } postInfoAndPRsActions(pr, issueReport, issueThreshold, diffReport, stashClient); } /* * Second part of the code necessary for the updateStashWithSonarInfo() method * and third part of the executeOn() method (call of a call) -- squid:MethodCyclomaticComplexity */ private void postInfoAndPRsActions( PullRequestRef pr, List issueReport, int issueThreshold, StashDiffReport diffReport, StashClient stashClient ) { int issueTotal = issueReport.size(); // if threshold exceeded, do not push issue list to Stash if (issueTotal >= issueThreshold) { LOGGER.warn("Too many issues detected ({}/{}): Issues cannot be displayed in Diff view", issueTotal, issueThreshold); } else { stashRequestFacade.postSonarQubeReport(pr, issueReport, diffReport, stashClient); } if (config.includeAnalysisOverview()) { stashRequestFacade.postAnalysisOverview(pr, issueReport, stashClient); } if (config.canApprovePullRequest()) { if (shouldApprovePullRequest(config.getApprovalSeverityThreshold(), issueReport)) { stashRequestFacade.approvePullRequest(pr, stashClient); } else { stashRequestFacade.resetPullRequestApproval(pr, stashClient); } } } static boolean shouldApprovePullRequest(Optional approvalSeverityThreshold, List report) { if (approvalSeverityThreshold.isPresent()) { return report.stream().noneMatch(issue -> issue.severity().compareTo(approvalSeverityThreshold.get()) > 0 ); } return report.isEmpty(); } @Override public void describe(PostJobDescriptor descriptor) { descriptor.requireProperty(StashPlugin.STASH_NOTIFICATION); descriptor.name("Stash/Bitbucket notification"); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/StashPlugin.java ================================================ package org.sonar.plugins.stash; import com.google.common.collect.Lists; import java.util.Arrays; import java.util.stream.Collectors; import org.sonar.api.Plugin; import org.sonar.api.Properties; import org.sonar.api.Property; import org.sonar.api.PropertyType; import org.sonar.api.batch.rule.Severity; import org.sonar.api.config.PropertyDefinition; import org.sonar.api.resources.Qualifiers; import java.util.List; import org.sonar.plugins.stash.issue.StashDiffReport; @Properties({ @Property(key = StashPlugin.STASH_NOTIFICATION, name = "Stash Notification", defaultValue = "false", description = "Analysis result will be issued in Stash pull request", global = false), @Property(key = StashPlugin.STASH_PROJECT, name = "Stash Project", description = "Stash project of current pull-request", global = false), @Property(key = StashPlugin.STASH_REPOSITORY, name = "Stash Repository", description = "Stash project of current pull-request", global = false), @Property(key = StashPlugin.STASH_PULL_REQUEST_ID, name = "Stash Pull-request Id", description = "Stash pull-request Id", global = false)}) public class StashPlugin implements Plugin { private static final String DEFAULT_STASH_TIMEOUT_VALUE = "10000"; private static final String DEFAULT_STASH_THRESHOLD_VALUE = "100"; private static final boolean DEFAULT_STASH_ANALYSIS_OVERVIEW = true; private static final boolean DEFAULT_STASH_INCLUDE_EXISTING_ISSUES = false; private static final int DEFAULT_STASH_FILES_IN_OVERVIEW = 0; private static final int DEFAULT_STASH_INCLUDE_VICINITY_RANGE = StashDiffReport.VICINITY_RANGE_NONE; private static final String DEFAULT_STASH_EXCLUDE_RULES = ""; private static final String CONFIG_PAGE_SUB_CATEGORY_STASH = "Stash"; public static final String SEVERITY_NONE = "NONE"; private static final List SEVERITY_LIST = Arrays.stream(Severity.values()) .map(Severity::name).collect(Collectors.toList()); private static final List SEVERITY_LIST_WITH_NONE = Lists .asList(SEVERITY_NONE, SEVERITY_LIST.toArray(new String[]{})); public enum IssueType { CONTEXT, REMOVED, ADDED, } public static final String STASH_NOTIFICATION = "sonar.stash.notification"; public static final String STASH_PROJECT = "sonar.stash.project"; public static final String STASH_REPOSITORY = "sonar.stash.repository"; public static final String STASH_PULL_REQUEST_ID = "sonar.stash.pullrequest.id"; public static final String STASH_RESET_COMMENTS = "sonar.stash.comments.reset"; public static final String STASH_URL = "sonar.stash.url"; public static final String STASH_LOGIN = "sonar.stash.login"; public static final String STASH_USER_SLUG = "sonar.stash.user.slug"; public static final String STASH_PASSWORD = "sonar.stash.password"; public static final String STASH_PASSWORD_ENVIRONMENT_VARIABLE = "sonar.stash.password.variable"; public static final String STASH_REVIEWER_APPROVAL = "sonar.stash.reviewer.approval"; public static final String STASH_REVIEWER_APPROVAL_SEVERITY_THRESHOLD = "sonar.stash.reviewer.approval.severity.threshold"; public static final String STASH_ISSUE_THRESHOLD = "sonar.stash.issue.threshold"; public static final String STASH_ISSUE_SEVERITY_THRESHOLD = "sonar.stash.issue.severity.threshold"; public static final String STASH_TIMEOUT = "sonar.stash.timeout"; public static final String STASH_TASK_SEVERITY_THRESHOLD = "sonar.stash.task.issue.severity.threshold"; public static final String STASH_INCLUDE_ANALYSIS_OVERVIEW = "sonar.stash.include.overview"; public static final String STASH_REPOSITORY_ROOT = "sonar.stash.repository.root"; public static final String STASH_INCLUDE_EXISTING_ISSUES = "sonar.stash.include.existing.issues"; public static final String STASH_FILES_LIMIT_IN_OVERVIEW = "sonar.stash.overview.filenames"; public static final String STASH_INCLUDE_VICINITY_RANGE = "sonar.stash.include.vicinity.issues.range"; public static final String STASH_EXCLUDE_RULES = "sonar.stash.exclude.rules"; @Override public void define(Context context) { context.addExtensions( StashIssueReportingPostJob.class, StashPluginConfiguration.class, StashRequestFacade.class, StashProjectBuilder.class, PropertyDefinition.builder(STASH_URL) .name("Stash base URL") .description("HTTP URL of Stash instance, such as http://yourhost.yourdomain/stash") .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT).build(), PropertyDefinition.builder(STASH_LOGIN) .name("Stash base User") .description("User to push data on Stash instance") .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT).build(), PropertyDefinition.builder(STASH_USER_SLUG) .name("Stash base user slug") .description("If the username has special characters this setting has also to be specified") .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onlyOnQualifiers(Qualifiers.PROJECT).build(), PropertyDefinition.builder(STASH_PASSWORD) .name("Stash base Password") .description("Password for Stash base User (Do NOT use in production, passwords are public" + " for everyone with UNAUTHENTICATED HTTP access to SonarQube") .type(PropertyType.PASSWORD) .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT).build(), PropertyDefinition.builder(STASH_TIMEOUT) .name("Stash issue Timeout") .description("Timeout when pushing a new issue to Stash (in ms)") .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT) .defaultValue(DEFAULT_STASH_TIMEOUT_VALUE).build(), PropertyDefinition.builder(STASH_REVIEWER_APPROVAL) .name("Stash reviewer approval") .description("Does SonarQube approve the pull-request if there is no new issues?") .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT) .type(PropertyType.BOOLEAN) .defaultValue("false").build(), PropertyDefinition.builder(STASH_ISSUE_THRESHOLD) .name("Stash issue Threshold") .description("Threshold to limit the number of issues pushed to Stash server") .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT) .defaultValue(DEFAULT_STASH_THRESHOLD_VALUE).build(), PropertyDefinition.builder(STASH_ISSUE_SEVERITY_THRESHOLD) .name("Stash issue severity Threshold") .description("Defines minimum issue severity to create diff-view comments for." + " Overview comment will still contain all severities." + " By default, all issues are pushed to Stash.") .type(PropertyType.SINGLE_SELECT_LIST) .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT) .defaultValue(SEVERITY_LIST.get(0)) .options(SEVERITY_LIST).build(), PropertyDefinition.builder(STASH_REVIEWER_APPROVAL_SEVERITY_THRESHOLD) .name("Threshold tie the approval to the severity of the found issues") .description("Maximum severity of an issue for approval to complete") .type(PropertyType.SINGLE_SELECT_LIST) .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT) .defaultValue(SEVERITY_NONE) .options(SEVERITY_LIST_WITH_NONE).build(), PropertyDefinition.builder(STASH_TASK_SEVERITY_THRESHOLD) .name("Stash tasks severity threshold") .description("Only create tasks for issues with the same or higher severity") .type(PropertyType.SINGLE_SELECT_LIST) .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT) .defaultValue(SEVERITY_NONE) .options(SEVERITY_LIST_WITH_NONE).build(), PropertyDefinition.builder(STASH_INCLUDE_ANALYSIS_OVERVIEW) .name("Include Analysis Overview Comment") .description("Create a comment to the Pull Request providing a overview of the results") .type(PropertyType.BOOLEAN) .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT) .defaultValue(Boolean.toString(DEFAULT_STASH_ANALYSIS_OVERVIEW)).build(), PropertyDefinition.builder(STASH_FILES_LIMIT_IN_OVERVIEW) .name("Include Files in Overview") .description("Will extend the Analysis Overview comment to include the files where the " + "issues where found. Set to any positive number to limit how many files per issue " + "will be shown. Set to 0 to disable this feature.") .type(PropertyType.INTEGER) .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT) .defaultValue(String.valueOf(DEFAULT_STASH_FILES_IN_OVERVIEW)).build(), PropertyDefinition.builder(STASH_INCLUDE_EXISTING_ISSUES) .name("Include Existing Issues") .description("Set to true to include already existing issues on modified lines.") .type(PropertyType.BOOLEAN) .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT) .defaultValue(Boolean.toString(DEFAULT_STASH_INCLUDE_EXISTING_ISSUES)).build(), PropertyDefinition.builder(STASH_INCLUDE_VICINITY_RANGE) .name("Include Vicinity Issues Range") .description("Specifies the range around the actual changes for which issues are reported. (In lines)") .type(PropertyType.INTEGER) .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT) .defaultValue(String.valueOf(DEFAULT_STASH_INCLUDE_VICINITY_RANGE)).build(), PropertyDefinition.builder(STASH_EXCLUDE_RULES) .name("Excluded Rules") .description("Comma separated list of rules for which no comments should be created.") .type(PropertyType.STRING) .subCategory(CONFIG_PAGE_SUB_CATEGORY_STASH) .onQualifiers(Qualifiers.PROJECT) .defaultValue(DEFAULT_STASH_EXCLUDE_RULES).build() ); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/StashPluginConfiguration.java ================================================ package org.sonar.plugins.stash; import com.google.common.collect.Sets; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import org.sonar.api.CoreProperties; import org.sonar.api.batch.InstantiationStrategy; import org.sonar.api.batch.ScannerSide; import org.sonar.api.batch.rule.Severity; import org.sonar.api.config.Settings; import java.io.File; import java.util.Optional; import org.sonar.api.platform.Server; import org.sonar.api.rule.RuleKey; @ScannerSide @InstantiationStrategy(InstantiationStrategy.PER_BATCH) public class StashPluginConfiguration { private Settings settings; private Server server; public StashPluginConfiguration(Settings settings, Server server) { this.settings = settings; this.server = server; } public boolean hasToNotifyStash() { return settings.getBoolean(StashPlugin.STASH_NOTIFICATION); } public String getStashProject() { return settings.getString(StashPlugin.STASH_PROJECT); } public String getStashRepository() { return settings.getString(StashPlugin.STASH_REPOSITORY); } public Integer getPullRequestId() { return settings.getInt(StashPlugin.STASH_PULL_REQUEST_ID); } public String getStashURL() { return settings.getString(StashPlugin.STASH_URL); } public String getStashLogin() { return settings.getString(StashPlugin.STASH_LOGIN); } public String getStashUserSlug() { return settings.getString(StashPlugin.STASH_USER_SLUG); } public String getStashPassword() { return settings.getString(StashPlugin.STASH_PASSWORD); } public String getStashPasswordEnvironmentVariable() { return settings.getString(StashPlugin.STASH_PASSWORD_ENVIRONMENT_VARIABLE); } public String getSonarQubeURL() { return server.getURL(); } public String getSonarQubeLogin() { return settings.getString(CoreProperties.LOGIN); } public String getSonarQubePassword() { return settings.getString(CoreProperties.PASSWORD); } public int getIssueThreshold() { return settings.getInt(StashPlugin.STASH_ISSUE_THRESHOLD); } public Severity getIssueSeverityThreshold() { return Severity.valueOf( Objects.requireNonNull(settings.getString(StashPlugin.STASH_ISSUE_SEVERITY_THRESHOLD))); } public int getStashTimeout() { return settings.getInt(StashPlugin.STASH_TIMEOUT); } public boolean canApprovePullRequest() { return settings.getBoolean(StashPlugin.STASH_REVIEWER_APPROVAL); } public boolean resetComments() { return settings.getBoolean(StashPlugin.STASH_RESET_COMMENTS); } public Optional getTaskIssueSeverityThreshold() { return getOptionalSeveritySetting(StashPlugin.STASH_TASK_SEVERITY_THRESHOLD); } public Optional getApprovalSeverityThreshold() { return getOptionalSeveritySetting(StashPlugin.STASH_REVIEWER_APPROVAL_SEVERITY_THRESHOLD); } public boolean includeAnalysisOverview() { return settings.getBoolean(StashPlugin.STASH_INCLUDE_ANALYSIS_OVERVIEW); } public Optional getRepositoryRoot() { return Optional.ofNullable(settings.getString(StashPlugin.STASH_REPOSITORY_ROOT)).map(File::new); } public boolean includeExistingIssues() { return settings.getBoolean(StashPlugin.STASH_INCLUDE_EXISTING_ISSUES); } public int getFilesLimitInOverview() { return settings.getInt(StashPlugin.STASH_FILES_LIMIT_IN_OVERVIEW); } public int issueVicinityRange() { return settings.getInt(StashPlugin.STASH_INCLUDE_VICINITY_RANGE); } public Set excludedRules() { return Sets.newHashSet( settings.getStringArray(StashPlugin.STASH_EXCLUDE_RULES) ).stream() .map(String::trim) .map(RuleKey::parse) .collect(Collectors.toSet()); } private Optional getOptionalSeveritySetting(String key) { String setting = settings.getString(key); if (StashPlugin.SEVERITY_NONE.equals(setting)) { return Optional.empty(); } if (setting == null) { return Optional.empty(); } return Optional.of(Severity.valueOf(setting)); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/StashPluginUtils.java ================================================ package org.sonar.plugins.stash; import com.google.common.base.CharMatcher; import java.io.IOException; import java.util.Collection; import java.util.Properties; import org.sonar.api.batch.fs.InputComponent; import org.sonar.api.batch.fs.InputModule; import org.sonar.api.batch.postjob.issue.PostJobIssue; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import org.sonar.api.batch.rule.Severity; public final class StashPluginUtils { private StashPluginUtils() {} public static String formatPercentage(double d) { // Defining that our percentage is precise down to 0.1% DecimalFormat df = new DecimalFormat("0.0"); // Protecting this method against non-US locales that would not use '.' as decimal separation DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(); decimalFormatSymbols.setDecimalSeparator('.'); df.setDecimalFormatSymbols(decimalFormatSymbols); // Making sure that we round the 0.1% properly out of the double value df.setRoundingMode(RoundingMode.HALF_UP); return df.format(d); } public static boolean roundedPercentageGreaterThan(double left, double right) { return (left > right) && !formatPercentage(left).equals(formatPercentage(right)); } public static long countIssuesBySeverity(Collection issues, final Severity severity) { return issues.stream().filter(i -> severity.equals(i.severity())).count(); } public static boolean isProjectWide(PostJobIssue issue) { InputComponent ic = issue.inputComponent(); if (!(ic instanceof InputModule)) { return false; } InputModule im = (InputModule) ic; if (im.key() == null) { return false; } return CharMatcher.is(':').countIn(im.key()) == 0; } public static PluginInfo getPluginInfo() { Properties props = new Properties(); try { props.load(StashPluginUtils.class.getClassLoader().getResourceAsStream("org/sonar/plugins/stash/sonar-stash.properties")); } catch (IOException e) { throw new IllegalStateException(e); } return new PluginInfo( props.getProperty("project.name"), props.getProperty("project.version") ); } public static String removeEnd(String s, String suffix) { if (s.endsWith(suffix)) { return removeEnd(s.substring(0, s.length() - suffix.length()), suffix); } return s; } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/StashProjectBuilder.java ================================================ package org.sonar.plugins.stash; import org.sonar.api.batch.bootstrap.ProjectBuilder; import java.io.File; // FIXME when we depend on 7.0 and use ScmProvider.relativePathFromScmRoot public class StashProjectBuilder extends ProjectBuilder { private File projectBaseDir; @Override public void build(Context context) { projectBaseDir = context.projectReactor().getRoot().getBaseDir(); } public File getProjectBaseDir() { return projectBaseDir; } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/StashRequestFacade.java ================================================ package org.sonar.plugins.stash; import java.io.File; import java.nio.file.Path; import java.util.Collection; import java.util.Optional; import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import java.util.Map; import org.sonar.api.batch.InstantiationStrategy; import org.sonar.api.batch.ScannerSide; import org.sonar.api.batch.fs.InputComponent; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.api.batch.rule.Severity; import org.sonar.api.scan.filesystem.PathResolver; import org.sonar.api.utils.System2; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.plugins.stash.StashPlugin.IssueType; import org.sonar.plugins.stash.client.StashClient; import org.sonar.plugins.stash.client.StashCredentials; import org.sonar.plugins.stash.exceptions.StashConfigurationException; import org.sonar.plugins.stash.issue.MarkdownPrinter; import org.sonar.plugins.stash.issue.StashComment; import org.sonar.plugins.stash.issue.StashCommentReport; import org.sonar.plugins.stash.issue.StashDiffReport; import org.sonar.plugins.stash.issue.StashPullRequest; import org.sonar.plugins.stash.issue.StashTask; import org.sonar.plugins.stash.issue.StashUser; import org.sonar.plugins.stash.issue.collector.SonarQubeCollector; @ScannerSide @InstantiationStrategy(InstantiationStrategy.PER_BATCH) public class StashRequestFacade implements IssuePathResolver { private static final Logger LOGGER = Loggers.get(StashRequestFacade.class); private static final String EXCEPTION_STASH_CONF = "Unable to get {0} from plugin configuration (value is null)"; private final StashPluginConfiguration config; private final File projectBaseDir; private final System2 system; public StashRequestFacade( StashPluginConfiguration stashPluginConfiguration, StashProjectBuilder projectBuilder, System2 system ) { this.config = stashPluginConfiguration; this.projectBaseDir = projectBuilder.getProjectBaseDir(); this.system = system; } public List extractIssueReport(Iterable issues) { return SonarQubeCollector.extractIssueReport( issues, this, config.includeExistingIssues(), config.excludedRules() ); } private MarkdownPrinter getMarkdownPrinter() { return new MarkdownPrinter(getIssueThreshold(), config.getSonarQubeURL(), config.getFilesLimitInOverview(), this); } /** * Post SQ analysis overview on Stash */ public void postAnalysisOverview(PullRequestRef pr, Collection issueReport, StashClient stashClient) { String report = getMarkdownPrinter().printReportMarkdown(issueReport); stashClient.postCommentOnPullRequest(pr, report); LOGGER.info("SonarQube analysis overview has been reported to Stash."); } /** * Approve pull-request */ public void approvePullRequest(PullRequestRef pr, StashClient stashClient) { stashClient.approvePullRequest(pr); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Pull-request {} ({}/{}) APPROVED by user \"{}\"", pr.pullRequestId(), pr.project(), pr.repository(), stashClient.getLogin()); } } /** * Reset pull-request approval */ public void resetPullRequestApproval(PullRequestRef pr, StashClient stashClient) { stashClient.resetPullRequestApproval(pr); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Pull-request {} ({}/{}) NOT APPROVED by user \"{}\"", pr.pullRequestId(), pr.project(), pr.repository(), stashClient.getLogin()); } } /** * Add a reviewer to the current pull-request. */ public void addPullRequestReviewer(PullRequestRef pr, String userSlug, StashClient stashClient) { StashPullRequest pullRequest = stashClient.getPullRequest(pr); // user not yet in reviewer list StashUser reviewer = pullRequest.getReviewer(userSlug); if (reviewer == null) { List reviewers = pullRequest.getReviewers(); reviewers.add(stashClient.getUser(userSlug)); stashClient.addPullRequestReviewer(pr, pullRequest.getVersion(), reviewers); if (LOGGER.isDebugEnabled()) { LOGGER.debug("User \"{}\" is now a reviewer of the pull-request #{} in {}/{}", userSlug, pr.pullRequestId(), pr.project(), pr.repository()); } } } /** * Push SonarQube report into the pull-request as comments. */ public void postSonarQubeReport(PullRequestRef pr, Iterable issueReport, StashDiffReport diffReport, StashClient stashClient) { postCommentPerIssue(pr, issueReport, diffReport, stashClient); LOGGER.info("New SonarQube issues (if any) have been reported to Stash."); } /** * Post one comment by found issue on Stash. */ void postCommentPerIssue(PullRequestRef pr, Iterable issues, StashDiffReport diffReport, StashClient stashClient) { // to optimize request to Stash, builds comment match ordered by filepath Map commentsByFile = new HashMap<>(); for (PostJobIssue issue : issues) { String path = getIssuePath(issue); commentsByFile.computeIfAbsent(path, p -> { StashCommentReport comments = stashClient.getPullRequestComments(pr, p); // According to the type of the comment // if type == CONTEXT, comment.line is set to source line instead of destination line comments.applyDiffReport(diffReport); return comments; }); } Severity issueSeverityThreshold = config.getIssueSeverityThreshold(); for (PostJobIssue issue : issues) { if (issue.severity().compareTo(issueSeverityThreshold) >= 0) { postIssueComment(pr, issue, commentsByFile, diffReport, stashClient, config.getTaskIssueSeverityThreshold()); } } } private void postIssueComment(PullRequestRef pr, PostJobIssue issue, Map commentsByFile, StashDiffReport diffReport, StashClient stashClient, Optional taskSeverityThreshold ) { String issueKey = issue.key(); String path = getIssuePath(issue); StashCommentReport comments = commentsByFile.get(path); String commentContent = getMarkdownPrinter().printIssueMarkdown(issue); Integer issueLine = issue.line(); if (issueLine == null) { issueLine = 0; } // Surprisingly this syntax does not trigger the squid:NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE rule // but it does if you transform that into a ternary operator at the assignment level :/ // if comment not already pushed to Stash if (comments != null && comments.contains(commentContent, path, issueLine)) { LOGGER.debug("Comment \"{}\" already pushed on file {} ({})", issueKey, path, issueLine); return; } // check if issue belongs to the Stash diff view IssueType type = diffReport.getType(path, issueLine, config.issueVicinityRange()); if (type == null) { LOGGER.info( "Comment \"{}\" cannot be pushed to Stash like it does not belong to diff view - {} (line: {})", issueKey, path, issueLine); return; } long line = diffReport.getLine(path, issueLine); StashComment comment = stashClient .postCommentLineOnPullRequest(pr, commentContent, path, line, type); LOGGER .info("Comment \"{}\" has been created ({}) on file {} ({})", issueKey, type, path, line); // Create task linked to the comment if configured if ( taskSeverityThreshold.isPresent() && issue.severity().compareTo(taskSeverityThreshold.get()) >= 0 ) { stashClient.postTaskOnComment(issue.message(), comment.getId()); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Comment \"{}\" has been linked to a Stash task", comment.getId()); } } } public StashCredentials getCredentials() { String passwordEnvVariable = config.getStashPasswordEnvironmentVariable(); String password = config.getStashPassword(); String userSlug = config.getStashUserSlug(); if (passwordEnvVariable != null) { password = system.envVariable(passwordEnvVariable); if (password == null) { throw new StashConfigurationException( "Unable to retrieve password from configured environment variable " + StashPlugin.STASH_PASSWORD_ENVIRONMENT_VARIABLE); } } if (userSlug == null) { userSlug = config.getStashLogin(); } return new StashCredentials(config.getStashLogin(), password, userSlug); } /** * Mandatory Issue Threshold option. * * @throws StashConfigurationException if unable to get parameter as Integer */ public int getIssueThreshold() { int result = 0; try { result = config.getIssueThreshold(); } catch (NumberFormatException e) { throw new StashConfigurationException("Unable to get " + StashPlugin.STASH_ISSUE_THRESHOLD + " from plugin configuration", e); } return result; } /** * Mandatory Stash URL option. * * @throws StashConfigurationException if unable to get parameter */ public String getStashURL() { String result = config.getStashURL(); if (result == null) { throw new StashConfigurationException( MessageFormat.format(EXCEPTION_STASH_CONF, StashPlugin.STASH_URL)); } if (result.endsWith("/")) { LOGGER.warn("Stripping trailing slash from {}, as it leads to invalid URLs", StashPlugin.STASH_URL); result = StashPluginUtils.removeEnd(result, "/"); } return result; } public PullRequestRef getPullRequest() { return PullRequestRef.builder() .setProject(getStashProject()) .setRepository(getStashRepository()) .setPullRequestId(getStashPullRequestId()) .build(); } /** * Mandatory Stash Project option. * * @throws StashConfigurationException if unable to get parameter */ public String getStashProject() { String result = config.getStashProject(); if (result == null) { throw new StashConfigurationException( MessageFormat.format(EXCEPTION_STASH_CONF, StashPlugin.STASH_PROJECT)); } return result; } /** * Mandatory Stash Repository option. * * @throws StashConfigurationException if unable to get parameter */ public String getStashRepository() { String result = config.getStashRepository(); if (result == null) { throw new StashConfigurationException( MessageFormat.format(EXCEPTION_STASH_CONF, StashPlugin.STASH_REPOSITORY)); } return result; } /** * Mandatory Stash pull-request ID option. * * @throws StashConfigurationException if unable to get parameter */ public int getStashPullRequestId() { Integer result = config.getPullRequestId(); if (result == null) { throw new StashConfigurationException(MessageFormat.format(EXCEPTION_STASH_CONF, StashPlugin.STASH_PULL_REQUEST_ID)); } return result; } /** * Get user who published the SQ analysis in Stash. */ public StashUser getSonarQubeReviewer(String userSlug, StashClient stashClient) { StashUser result = null; result = stashClient.getUser(userSlug); LOGGER.debug("SonarQube reviewer {} identified in Stash", userSlug); return result; } /** * Get all changes exposed through the Stash pull-request. */ public StashDiffReport getPullRequestDiffReport(PullRequestRef pr, StashClient stashClient) { StashDiffReport result = stashClient.getPullRequestDiffs(pr); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Stash differential report retrieved from pull request {} #{}", pr.repository(), pr.pullRequestId()); } return result; } /** * Reset all comments linked to a pull-request. */ public void resetComments(PullRequestRef pr, StashDiffReport diffReport, StashUser sonarUser, StashClient stashClient) { // Let's call this "diffRep_loop" for (StashComment comment : diffReport.getComments()) { // delete comment only if published by the current SQ user if (sonarUser.getId() != comment.getAuthor().getId()) { continue; // Next element in "diffRep_loop" // comment contains tasks which cannot be deleted => do nothing } else if (comment.containsPermanentTasks()) { LOGGER.debug("Comment \"{}\" (path:\"{}\", line:\"{}\")" + "CANNOT be deleted because one of its tasks is not deletable.", comment.getId(), comment.getPath(), comment.getLine()); continue; // Next element in "diffRep_loop" } // delete tasks linked to the current comment for (StashTask task : comment.getTasks()) { stashClient.deleteTaskOnComment(task); } stashClient.deletePullRequestComment(pr, comment); } LOGGER.info("SonarQube issues reported to Stash by user \"{}\" have been reset", sonarUser.getName()); } @Override public String getIssuePath(PostJobIssue issue) { InputComponent ip = issue.inputComponent(); if (ip == null || !ip.isFile()) { return null; } InputFile inputFile = (InputFile) ip; Path baseDir = config .getRepositoryRoot() .orElse(projectBaseDir).toPath(); return new PathResolver().relativePath(baseDir, inputFile.path()); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/client/ContentType.java ================================================ package org.sonar.plugins.stash.client; import static java.util.Objects.requireNonNull; /* * basic implementation of RFC 7231, section 3.1.1.1 * subset of javax.mail.internet.ContentType */ public class ContentType { private String primaryType; private String subType; public static final int CONTENTTYPE_ELEM_NUM = 2; public ContentType(String primaryType, String subType, Object list) { if (list != null) { throw new IllegalArgumentException(); } this.primaryType = requireNonNull(primaryType); this.subType = requireNonNull(subType); } public boolean match(String s) { String[] parts = s.split(";", CONTENTTYPE_ELEM_NUM); // we ignore the parameters, match() does not care and we can't have our own String[] types = parts[0].trim().split("/", CONTENTTYPE_ELEM_NUM); if (types.length < CONTENTTYPE_ELEM_NUM) { return false; } return primaryType.equalsIgnoreCase(types[0]) && subType.equalsIgnoreCase(types[1]); } @Override public String toString() { return primaryType + "/" + subType; } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/client/StashClient.java ================================================ package org.sonar.plugins.stash.client; import com.github.cliftonlabs.json_simple.JsonArray; import com.github.cliftonlabs.json_simple.JsonException; import com.github.cliftonlabs.json_simple.JsonObject; import com.github.cliftonlabs.json_simple.Jsoner; import java.nio.charset.StandardCharsets; import java.util.Optional; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.BoundRequestBuilder; import org.asynchttpclient.DefaultAsyncHttpClient; import org.asynchttpclient.DefaultAsyncHttpClientConfig; import org.asynchttpclient.Realm; import org.asynchttpclient.Request; import org.asynchttpclient.Response; import org.asynchttpclient.config.AsyncHttpClientConfigDefaults; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.plugins.stash.PeekableInputStream; import org.sonar.plugins.stash.PluginInfo; import org.sonar.plugins.stash.PullRequestRef; import org.sonar.plugins.stash.StashPlugin.IssueType; import org.sonar.plugins.stash.StashPluginUtils; import org.sonar.plugins.stash.exceptions.StashClientException; import org.sonar.plugins.stash.issue.StashComment; import org.sonar.plugins.stash.issue.StashCommentReport; import org.sonar.plugins.stash.issue.StashDiffReport; import org.sonar.plugins.stash.issue.StashPullRequest; import org.sonar.plugins.stash.issue.StashTask; import org.sonar.plugins.stash.issue.StashUser; import org.sonar.plugins.stash.issue.collector.StashCollector; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.HttpURLConnection; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class StashClient implements AutoCloseable { private static final Logger LOGGER = Loggers.get(StashClient.class); private final String baseUrl; private final StashCredentials credentials; private final int stashTimeout; private AsyncHttpClient httpClient; private static final String REST_API = "/rest/api/1.0/"; private static final String USER_API = "{0}" + REST_API + "users/{1}"; private static final String REPO_API = "{0}" + REST_API + "projects/{1}/repos/{2}/"; private static final String TASKS_API = REST_API + "tasks"; private static final String API_ALL_PR = REPO_API + "pull-requests/"; private static final String API_ONE_PR = API_ALL_PR + "{3,number,#}"; private static final String API_ONE_PR_ALL_COMMENTS = API_ONE_PR + "/comments"; private static final String API_ONE_PR_DIFF = API_ONE_PR + "/diff?withComments=true"; private static final String API_ONE_PR_APPROVAL = API_ONE_PR + "/approve"; private static final String API_ONE_PR_COMMENT_PATH = API_ONE_PR + "/comments?path={4}&start={5,number,#}"; private static final String API_ONE_PR_ONE_COMMENT = API_ONE_PR_ALL_COMMENTS + "/{4}?version={5}"; private static final String PULL_REQUEST_APPROVAL_POST_ERROR_MESSAGE = "Unable to change status of pull-request {0}" + " #{1,number,#}."; private static final String PULL_REQUEST_GET_ERROR_MESSAGE = "Unable to retrieve pull-request {0} #{1,number,#}."; private static final String PULL_REQUEST_PUT_ERROR_MESSAGE = "Unable to update pull-request {0} #{1,number,#}."; private static final String USER_GET_ERROR_MESSAGE = "Unable to retrieve user {0}."; private static final String COMMENT_POST_ERROR_MESSAGE = "Unable to post a comment to {0} #{1,number,#}."; private static final String COMMENT_GET_ERROR_MESSAGE = "Unable to get comment linked to {0} #{1,number,#}."; private static final String COMMENT_DELETION_ERROR_MESSAGE = "Unable to delete comment {0,number,#}" + " from pull-request {1} #{2,number,#}."; private static final String TASK_POST_ERROR_MESSAGE = "Unable to post a task on comment {0,number,#}."; private static final String TASK_DELETION_ERROR_MESSAGE = "Unable to delete task {0,number,#}."; private static final ContentType JSON = new ContentType("application", "json", null); public StashClient(String url, StashCredentials credentials, int stashTimeout, String sonarQubeVersion) { this.baseUrl = url; this.credentials = credentials; this.stashTimeout = stashTimeout; this.httpClient = createHttpClient(sonarQubeVersion); } public String getBaseUrl() { return baseUrl; } public String getLogin() { return credentials.getLogin(); } public void postCommentOnPullRequest(PullRequestRef pr, String report) { String request = MessageFormat.format(API_ONE_PR_ALL_COMMENTS, baseUrl, pr.project(), pr.repository(), pr.pullRequestId()); JsonObject json = new JsonObject(); json.put("text", report); postCreate(request, json, MessageFormat.format(COMMENT_POST_ERROR_MESSAGE, pr.repository(), pr.pullRequestId())); } public StashCommentReport getPullRequestComments(PullRequestRef pr, String path) { StashCommentReport result = new StashCommentReport(); long start = 0; boolean isLastPage = false; while (!isLastPage) { String request = MessageFormat.format(API_ONE_PR_COMMENT_PATH, baseUrl, pr.project(), pr.repository(), pr.pullRequestId(), path, start); JsonObject jsonComments = get(request, MessageFormat.format(COMMENT_GET_ERROR_MESSAGE, pr.repository(), pr.pullRequestId())); result.add(StashCollector.extractComments(jsonComments)); // Stash pagination: check if you get all comments linked to the pull-request isLastPage = StashCollector.isLastPage(jsonComments); start = StashCollector.getNextPageStart(jsonComments); } return result; } public void deletePullRequestComment(PullRequestRef pr, StashComment comment) { String request = MessageFormat.format(API_ONE_PR_ONE_COMMENT, baseUrl, pr.project(), pr.repository(), pr.pullRequestId(), Long.toString(comment.getId()), Long.toString(comment.getVersion())); delete(request, MessageFormat.format(COMMENT_DELETION_ERROR_MESSAGE, comment.getId(), pr.repository(), pr.pullRequestId())); } public StashDiffReport getPullRequestDiffs(PullRequestRef pr) { String request = MessageFormat.format(API_ONE_PR_DIFF, baseUrl, pr.project(), pr.repository(), pr.pullRequestId()); JsonObject jsonDiffs = get(request, MessageFormat.format(COMMENT_GET_ERROR_MESSAGE, pr.repository(), pr.pullRequestId())); return StashCollector.extractDiffs(jsonDiffs); } public StashComment postCommentLineOnPullRequest(PullRequestRef pr, String message, String path, long line, IssueType type) { String request = MessageFormat.format(API_ONE_PR_ALL_COMMENTS, baseUrl, pr.project(), pr.repository(), pr.pullRequestId()); JsonObject anchor = new JsonObject(); if (line != 0L) { anchor.put("line", line); anchor.put("lineType", type.name()); } String fileType = "TO"; if (type == IssueType.CONTEXT) { fileType = "FROM"; } anchor.put("fileType", fileType); anchor.put("path", path); JsonObject json = new JsonObject(); json.put("text", message); json.put("anchor", anchor); JsonObject response = postCreate(request, json, MessageFormat.format(COMMENT_POST_ERROR_MESSAGE, pr.repository(), pr.pullRequestId())); return StashCollector.extractComment(response, path, line); } public StashUser getUser(String userSlug) { String request = MessageFormat.format(USER_API, baseUrl, userSlug); JsonObject response = get(request, MessageFormat.format(USER_GET_ERROR_MESSAGE, userSlug)); return StashCollector.extractUser(response); } public StashPullRequest getPullRequest(PullRequestRef pr) { String request = MessageFormat.format(API_ONE_PR, baseUrl, pr.project(), pr.repository(), pr.pullRequestId()); JsonObject response = get(request, MessageFormat.format(PULL_REQUEST_GET_ERROR_MESSAGE, pr.repository(), pr.pullRequestId())); return StashCollector.extractPullRequest(pr, response); } public void addPullRequestReviewer(PullRequestRef pr, long pullRequestVersion, List reviewers) { String request = MessageFormat.format(API_ONE_PR, baseUrl, pr.project(), pr.repository(), pr.pullRequestId()); JsonObject json = new JsonObject(); JsonArray jsonReviewers = new JsonArray(); for (StashUser reviewer : reviewers) { JsonObject reviewerName = new JsonObject(); reviewerName.put("name", reviewer.getName()); JsonObject user = new JsonObject(); user.put("user", reviewerName); jsonReviewers.add(user); } json.put("reviewers", jsonReviewers); json.put("id", pr.pullRequestId()); json.put("version", pullRequestVersion); put(request, json, MessageFormat.format(PULL_REQUEST_PUT_ERROR_MESSAGE, pr.repository(), pr.pullRequestId())); } public void approvePullRequest(PullRequestRef pr) { String request = MessageFormat.format(API_ONE_PR_APPROVAL, baseUrl, pr.project(), pr.repository(), pr.pullRequestId()); post(request, null, MessageFormat.format(PULL_REQUEST_APPROVAL_POST_ERROR_MESSAGE, pr.repository(), pr.pullRequestId())); } public void resetPullRequestApproval(PullRequestRef pr) { String request = MessageFormat.format(API_ONE_PR_APPROVAL, baseUrl, pr.project(), pr.repository(), pr.pullRequestId()); delete(request, HttpURLConnection.HTTP_OK, MessageFormat.format(PULL_REQUEST_APPROVAL_POST_ERROR_MESSAGE, pr.repository(), pr.pullRequestId())); } public void postTaskOnComment(String message, Long commentId) { String request = baseUrl + TASKS_API; JsonObject anchor = new JsonObject(); anchor.put("id", commentId); anchor.put("type", "COMMENT"); JsonObject json = new JsonObject(); json.put("anchor", anchor); json.put("text", message); postCreate(request, json, MessageFormat.format(TASK_POST_ERROR_MESSAGE, commentId)); } public void deleteTaskOnComment(StashTask task) { String request = baseUrl + TASKS_API + "/" + task.getId(); delete(request, MessageFormat.format(TASK_DELETION_ERROR_MESSAGE, task.getId())); } @Override public void close() { try { httpClient.close(); } catch (IOException e) { LOGGER.debug("Ignoring exception while closing StashClient: {}", e, e); } } private JsonObject get(String url, String errorMessage) { return performRequest(httpClient.prepareGet(url), null, HttpURLConnection.HTTP_OK, errorMessage); } private JsonObject post(String url, JsonObject body, String errorMessage) { return performRequest(httpClient.preparePost(url), body, HttpURLConnection.HTTP_OK, errorMessage); } private JsonObject postCreate(String url, JsonObject body, String errorMessage) { return performRequest(httpClient.preparePost(url), body, HttpURLConnection.HTTP_CREATED, errorMessage); } private JsonObject delete(String url, int expectedStatusCode, String errorMessage) { return performRequest(httpClient.prepareDelete(url), null, expectedStatusCode, errorMessage); } private JsonObject delete(String url, String errorMessage) { return delete(url, HttpURLConnection.HTTP_NO_CONTENT, errorMessage); } private JsonObject put(String url, JsonObject body, String errorMessage) { return performRequest(httpClient.preparePut(url), body, HttpURLConnection.HTTP_OK, errorMessage); } private void addAuth(BoundRequestBuilder requestBuilder) { if (credentials != null && credentials.getLogin() != null) { String password = Optional.ofNullable(credentials.getPassword()).orElse(""); Realm realm = new Realm.Builder(credentials.getLogin(), password).setUsePreemptiveAuth(true) .setScheme(Realm.AuthScheme.BASIC).build(); requestBuilder.setRealm(realm); } } private JsonObject performRequest(BoundRequestBuilder requestBuilder, JsonObject body, int expectedStatusCode, String errorMessage) { if (body != null) { requestBuilder.setBody(body.toJson()); } addAuth(requestBuilder); requestBuilder.setFollowRedirect(true); requestBuilder.addHeader("Content-Type", JSON.toString()); requestBuilder.addHeader("Accept", JSON.toString()); requestBuilder.setCharset(StandardCharsets.UTF_8); Request request = requestBuilder.build(); try { Response response = httpClient.executeRequest(request).get(stashTimeout, TimeUnit.MILLISECONDS); validateResponse(response, expectedStatusCode, errorMessage); return extractResponse(response); } catch (ExecutionException | TimeoutException e) { throw new StashClientException(request.getUrl(), e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new StashClientException(request.getUrl(), e); } } private static void validateResponse(Response response, int expectedStatusCode, String message) { int responseCode = response.getStatusCode(); if (responseCode != expectedStatusCode) { throw new StashClientException(message + " Received " + responseCode + ": " + formatStashApiError(response)); } } private static JsonObject extractResponse(Response response) { PeekableInputStream bodyStream = new PeekableInputStream(response.getResponseBodyAsStream()); try { if (!bodyStream.peek().isPresent()) { return null; } String contentType = response.getHeader("Content-Type"); if (!JSON.match(contentType.trim())) { throw new StashClientException("Received response with type " + contentType + " instead of JSON"); } Reader body = new InputStreamReader(bodyStream); Object obj = Jsoner.deserialize(body); return (JsonObject)obj; } catch (JsonException | ClassCastException | IOException e) { throw new StashClientException("Could not parse JSON response", e); } } private static String formatStashApiError(Response response) { JsonObject responseJson = extractResponse(response); // squid:S2259: making sure that we do not have a null value that would make a NullPointerException when used if (responseJson == null) { throw new StashClientException("The responseJson could not be extracted from the response !"); } JsonArray errors = (JsonArray)responseJson.get("errors"); if (errors == null) { throw new StashClientException("Error response did not contain an errors object '" + responseJson + "'"); } List errorParts = new ArrayList<>(); for (Object o : errors) { try { JsonObject error = (JsonObject)o; errorParts.add((String)error.get("exceptionName") + ": " + (String)error.get("message")); } catch (ClassCastException e) { throw new StashClientException("Error response contained invalid error", e); } } return String.join(", ", errorParts); } // We can't test this, as the manifest can only be loaded when deployed from a JAR-archive. // During unit testing this is not the case private static String getUserAgent(String sonarQubeVersion) { PluginInfo info = StashPluginUtils.getPluginInfo(); String name; String version; name = version = "unknown"; if (info != null) { name = info.getName(); version = info.getVersion(); } return MessageFormat.format("SonarQube/{0} {1}/{2} {3}", sonarQubeVersion == null ? "unknown" : sonarQubeVersion, name, version, AsyncHttpClientConfigDefaults.defaultUserAgent()); } AsyncHttpClient createHttpClient(String sonarQubeVersion) { return new DefaultAsyncHttpClient( new DefaultAsyncHttpClientConfig.Builder() .setUserAgent(getUserAgent(sonarQubeVersion)) .setCompressionEnforced(true) .setUseProxySelector(true) .setUseProxyProperties(true) .build() ); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/client/StashCredentials.java ================================================ package org.sonar.plugins.stash.client; public class StashCredentials { private final String login; private final String password; private final String userSlug; public StashCredentials(String login, String password, String userSlug) { this.login = login; this.password = password; this.userSlug = userSlug; } public String getLogin() { return login; } public String getPassword() { return password; } public String getUserSlug() { return userSlug; } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/exceptions/StashClientException.java ================================================ package org.sonar.plugins.stash.exceptions; public class StashClientException extends StashException { private static final long serialVersionUID = -9071818008552452736L; public StashClientException(String message) { super(message); } public StashClientException(Throwable cause) { super(cause); } public StashClientException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/exceptions/StashConfigurationException.java ================================================ package org.sonar.plugins.stash.exceptions; public class StashConfigurationException extends StashException { private static final long serialVersionUID = 8423412434061160213L; public StashConfigurationException(String message) { super(message); } public StashConfigurationException(Throwable cause) { super(cause); } public StashConfigurationException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/exceptions/StashException.java ================================================ package org.sonar.plugins.stash.exceptions; public class StashException extends RuntimeException { private static final long serialVersionUID = -4529815924057418321L; public StashException(String message) { super(message); } public StashException(Throwable cause) { super(cause); } public StashException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/exceptions/StashReportExtractionException.java ================================================ package org.sonar.plugins.stash.exceptions; public class StashReportExtractionException extends StashException { private static final long serialVersionUID = -8105232303389875137L; public StashReportExtractionException(String message) { super(message); } public StashReportExtractionException(Throwable cause) { super(cause); } public StashReportExtractionException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/issue/MarkdownPrinter.java ================================================ package org.sonar.plugins.stash.issue; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Arrays; import java.util.function.Function; import java.util.stream.Collectors; import com.google.common.collect.ArrayListMultimap; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.api.batch.rule.Severity; import org.sonar.api.rule.RuleKey; import org.sonar.plugins.stash.IssuePathResolver; import static org.sonar.plugins.stash.StashPluginUtils.countIssuesBySeverity; public final class MarkdownPrinter { private static final String NEW_LINE = "\n"; private static final String CODING_RULES_RULE_KEY = "coding_rules#rule_key="; private int issueThreshold; private String sonarQubeURL; private int includeFilesInOverview; private IssuePathResolver issuePathResolver; private Severity[] orderedSeverities; public MarkdownPrinter( int issueThreshold, String sonarQubeURL, int includeFilesInOverview, IssuePathResolver issuePathResolver ) { this.issueThreshold = issueThreshold; this.sonarQubeURL = sonarQubeURL; this.includeFilesInOverview = includeFilesInOverview; this.issuePathResolver = issuePathResolver; orderedSeverities = Severity.values(); Arrays.sort(orderedSeverities, Comparator.reverseOrder()); } public static String printSeverityMarkdown(org.sonar.api.batch.rule.Severity severity) { StringBuilder sb = new StringBuilder(); sb.append("*").append(severity.name().toUpperCase()).append("*").append(" - "); return sb.toString(); } public static String printIssueNumberBySeverityMarkdown(Collection report, Severity severity) { StringBuilder sb = new StringBuilder(); long issueNumber = countIssuesBySeverity(report, severity); sb.append("| ").append(severity).append(" | ").append(issueNumber).append(" |") .append(NEW_LINE); return sb.toString(); } public String printIssueMarkdown(PostJobIssue issue) { StringBuilder sb = new StringBuilder(); String message = issue.message(); if (message == null) { return "No message"; } sb.append(MarkdownPrinter.printSeverityMarkdown(issue.severity())) .append(message) .append(" [") .append(link(issue.ruleKey().toString(), sonarQubeURL + "/" + CODING_RULES_RULE_KEY + issue.ruleKey())) .append("]"); return sb.toString(); } public String printReportMarkdown(Collection allIssues) { StringBuilder sb = new StringBuilder("## SonarQube analysis Overview"); sb.append(NEW_LINE); if (allIssues.isEmpty()) { sb.append("### No new issues detected!"); sb.append(NEW_LINE).append(NEW_LINE); } else { int issueNumber = allIssues.size(); if (issueNumber >= issueThreshold) { sb.append("### Too many issues detected "); sb.append("(").append(issueNumber).append("/").append(issueThreshold).append(")"); sb.append(": Issues cannot be displayed in Diff view.").append(NEW_LINE).append(NEW_LINE); } sb.append("| Total New Issues | ").append(issueNumber).append(" |").append(NEW_LINE); sb.append("|-----------------|------|").append(NEW_LINE); for (Severity severity : orderedSeverities) { sb.append(printIssueNumberBySeverityMarkdown(allIssues, severity)); } ListMultimap uniqueInformation = allIssues.stream().collect(Multimaps.toMultimap(PostJobIssue::ruleKey, Function.identity(), ArrayListMultimap::create)); List>> uniqueSortedInformation = Multimaps.asMap(uniqueInformation) .entrySet().stream() .sorted(bySeverity.reversed()) .collect(Collectors.toList()); sb.append( formatTableList(uniqueSortedInformation)); } return sb.toString(); } private String formatTableList(List>> items) { if (items.isEmpty()) { return ""; } String caption = "Issues list"; StringBuilder sb = new StringBuilder(); sb.append(NEW_LINE).append(NEW_LINE); sb.append("| ").append(caption).append(" |").append(NEW_LINE); sb.append("|"); sb.append(repeatString(caption.length() + 2, "-")); sb.append("|"); sb.append(NEW_LINE); for (Map.Entry> item : items) { List issues = item.getValue(); sb.append("| ").append(printIssueMarkdown(issues.get(0))).append(" |").append(NEW_LINE); if (this.includeFilesInOverview > 0) { sb.append("|    *Files: ").append(fileNameList(issues)).append("* |").append(NEW_LINE); } } return sb.toString(); } private String fileNameList(List issues) { List names = new ArrayList<>(); issues.sort(issueFormatComparator); for (PostJobIssue issue: issues.subList(0, Math.min(includeFilesInOverview, issues.size()))) { String path = issuePathResolver.getIssuePath(issue); Integer line = issue.line(); if (line == null) { names.add(path); } else { names.add(String.format("%s:%s", path, line)); } } if (issues.size() > includeFilesInOverview) { names.add("..."); } return String.join(", ", names); } private String repeatString(int n, String s) { return String.join("", Collections.nCopies(n, s)); } private static String link(String title, String target) { return "[" + title + "](" + target + ")"; } private static Comparator>> bySeverity = Comparator.comparing( // List invariant is guaranteed by docs e -> ((List) e.getValue()).get(0).severity()); private Comparator fileNameLength = Comparator .comparing(i -> issuePathResolver.getIssuePath(i).length()); private Comparator issueLine = Comparator // -1 sorts before all issues with lines .comparing(issue -> firstNonNull(issue.line(), -1)); private Comparator fileNameLexical = Comparator .comparing(i -> issuePathResolver.getIssuePath(i)); private Comparator issueFormatComparator = fileNameLength .thenComparing(issueLine) .thenComparing(fileNameLexical) ; @SafeVarargs private static T firstNonNull(T... args) { for (T t: args) { if (t != null) { return t; } } throw new IllegalStateException("At least one of the arguments should have been non-null"); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/issue/StashComment.java ================================================ package org.sonar.plugins.stash.issue; import com.google.common.base.MoreObjects; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class StashComment { private final long id; private final String message; private final StashUser author; private final long version; private long line; private String path; private List tasks; public StashComment(long id, String message, String path, Long line, StashUser author, long version) { this.id = id; this.message = message; this.path = path; this.author = author; this.version = version; // Stash comment can be null if comment is global to all the file if (line == null) { this.line = 0; } else { this.line = line.longValue(); } tasks = new ArrayList<>(); } public long getId() { return id; } public void setLine(long line) { this.line = line; } public void setPath(String path) { this.path = path; } public String getMessage() { return message; } public String getPath() { return path; } public long getLine() { return line; } public StashUser getAuthor() { return author; } public long getVersion() { return version; } public List getTasks() { return Collections.unmodifiableList(tasks); } public void addTask(StashTask task) { tasks.add(task); } public boolean containsPermanentTasks() { boolean result = false; for (StashTask task : tasks) { if (!task.isDeletable()) { result = true; break; } } return result; } @Override public boolean equals(Object object) { boolean result = false; if (object instanceof StashComment) { StashComment stashComment = (StashComment)object; result = this.getId() == stashComment.getId(); } return result; } @Override public int hashCode() { return (int)this.getId(); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("id", id) .add("message", message) .add("author", author) .add("version", version) .add("line", line) .add("path", path) .add("tasks", tasks) .toString(); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/issue/StashCommentReport.java ================================================ package org.sonar.plugins.stash.issue; import java.util.Objects; import java.util.ArrayList; import java.util.List; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.plugins.stash.StashPlugin.IssueType; public class StashCommentReport { private static final Logger LOGGER = Loggers.get(StashCommentReport.class); private List comments; public StashCommentReport() { this.comments = new ArrayList<>(); } public List getComments() { return comments; } public void add(StashComment comment) { comments.add(comment); } public void add(StashCommentReport report) { for (StashComment comment : report.getComments()) { comments.add(comment); } } public boolean contains(String message, String path, long line) { boolean result = false; for (StashComment comment : comments) { if (Objects.equals(comment.getMessage(), message) && Objects.equals(comment.getPath(), path) && comment.getLine() == line) { result = true; break; } } return result; } public StashCommentReport applyDiffReport(StashDiffReport diffReport) { for (StashComment comment : comments) { StashDiff diff = diffReport.getDiffByComment(comment.getId()); if ((diff != null) && diff.getType() == IssueType.CONTEXT) { // By default comment line, with type == CONTEXT, is set to FROM value. // Set comment line to TO value to be compared with SonarQube issue. long destination = diff.getDestination(); comment.setLine(destination); LOGGER.debug("Update Stash comment \"{}\": set comment line to destination diff line ({})", comment.getId(), comment.getLine()); } } return this; } public int size() { return comments.size(); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/issue/StashDiff.java ================================================ package org.sonar.plugins.stash.issue; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.sonar.plugins.stash.StashPlugin.IssueType; public class StashDiff { private final IssueType type; private final String path; private final long source; private final long destination; private final List comments; public StashDiff(IssueType type, String path, long source, long destination) { this.type = type; this.path = path; this.source = source; this.destination = destination; this.comments = new ArrayList<>(); } public void addComment(StashComment comment) { this.comments.add(comment); } public String getPath() { return path; } public long getSource() { return source; } public long getDestination() { return destination; } public IssueType getType() { return type; } public List getComments() { return Collections.unmodifiableList(comments); } public boolean containsComment(long commentId) { return comments.stream().anyMatch(c -> c.getId() == commentId); } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/issue/StashDiffReport.java ================================================ package org.sonar.plugins.stash.issue; import com.google.common.collect.Range; import java.util.Collections; import java.util.Objects; import java.util.ArrayList; import java.util.List; import org.sonar.plugins.stash.StashPlugin.IssueType; /** * This class is a representation of the Stash Diff view. *

* Purpose is to check if a SonarQube issue belongs to the Stash diff view before posting. * Indeed, Stash Diff view displays only comments which belong to this view. */ public class StashDiffReport { public static final int VICINITY_RANGE_NONE = 0; private List diffs; public StashDiffReport() { this.diffs = new ArrayList<>(); } public List getDiffs() { return Collections.unmodifiableList(diffs); } public void add(StashDiff diff) { diffs.add(diff); } public void add(StashDiffReport report) { diffs.addAll(report.getDiffs()); } private static boolean inVicinityOfChangedDiff(StashDiff diff, long destination, int range) { if (range <= 0) { return false; } long lower = Math.min(diff.getSource(), diff.getDestination()); long upper = Math.max(diff.getSource(), diff.getDestination()); return Range.closed(lower - range, upper + range).contains(destination); } private static boolean isChangedDiff(StashDiff diff, long destination) { return diff.getDestination() == destination; } private static boolean lineIsChangedDiff(StashDiff diff) { return !diff.getType().equals(IssueType.CONTEXT); } public IssueType getType(String path, long destination, int vicinityRange) { boolean isInContextDiff = false; for (StashDiff diff : diffs) { if (Objects.equals(diff.getPath(), path)) { // Line 0 never belongs to Stash Diff view. // It is a global comment with a type set to CONTEXT. if (destination == 0) { return IssueType.CONTEXT; } else if (!lineIsChangedDiff(diff)) { // We only care about changed diff continue; } else if (isChangedDiff(diff, destination)) { return diff.getType(); } else if (inVicinityOfChangedDiff(diff, destination, vicinityRange)) { isInContextDiff = true; } } } return isInContextDiff ? IssueType.CONTEXT : null; } /** * Depends on the type of the diff. * If type == "CONTEXT", return the source line of the diff. * If type == "ADDED", return the destination line of the diff. */ public long getLine(String path, long destination) { for (StashDiff diff : diffs) { if (Objects.equals(diff.getPath(), path) && (diff.getDestination() == destination)) { if (diff.getType() == IssueType.CONTEXT) { return diff.getSource(); } else { return diff.getDestination(); } } } return 0; } public StashDiff getDiffByComment(long commentId) { for (StashDiff diff : diffs) { if (diff.containsComment(commentId)) { return diff; } } return null; } /** * Get all comments from the Stash differential report. */ public List getComments() { List result = new ArrayList<>(); for (StashDiff diff : this.diffs) { List comments = diff.getComments(); for (StashComment comment : comments) { if (!result.contains(comment)) { result.add(comment); } } } return result; } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/issue/StashPullRequest.java ================================================ package org.sonar.plugins.stash.issue; import org.sonar.plugins.stash.PullRequestRef; import java.util.ArrayList; import java.util.List; public class StashPullRequest { private final PullRequestRef ref; private long version; private final ArrayList reviewers; public int getId() { return ref.pullRequestId(); } public String getProject() { return ref.project(); } public String getRepository() { return ref.repository(); } public StashPullRequest(PullRequestRef pr) { this.ref = pr; this.reviewers = new ArrayList<>(); } public PullRequestRef getRef() { return ref; } public long getVersion() { return version; } public void setVersion(long version) { this.version = version; } public void addReviewer(StashUser reviewer) { this.reviewers.add(reviewer); } public List getReviewers() { return reviewers; } public StashUser getReviewer(String user) { StashUser result = null; for (StashUser stashReviewer : reviewers) { if (user.equals(stashReviewer.getSlug())) { result = stashReviewer; break; } } return result; } public boolean containsReviewer(StashUser reviewer) { boolean result = false; for (StashUser stashReviewer : reviewers) { if (reviewer.getId() == stashReviewer.getId()) { result = true; break; } } return result; } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/issue/StashTask.java ================================================ package org.sonar.plugins.stash.issue; public class StashTask { private final Long id; private final String text; private final String state; private final boolean deletable; public StashTask(Long id, String text, String state, boolean deletable) { this.id = id; this.text = text; this.state = state; this.deletable = deletable; } public Long getId() { return id; } public String getText() { return text; } public String getState() { return state; } public boolean isDeletable() { return deletable; } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/issue/StashUser.java ================================================ package org.sonar.plugins.stash.issue; public class StashUser { private final long id; private final String name; private final String slug; private final String email; public StashUser(long id, String name, String slug, String email) { this.id = id; this.name = name; this.slug = slug; this.email = email; } public long getId() { return id; } public String getName() { return name; } public String getSlug() { return slug; } public String getEmail() { return email; } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/issue/collector/SonarQubeCollector.java ================================================ package org.sonar.plugins.stash.issue.collector; import java.util.Set; import static org.sonar.plugins.stash.StashPluginUtils.isProjectWide; import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.plugins.stash.IssuePathResolver; public final class SonarQubeCollector { private static final Logger LOGGER = Loggers.get(SonarQubeCollector.class); private SonarQubeCollector() { // Hiding implicit public constructor with an explicit private one (squid:S1118) } /** * Create issue report according to issue list generated during SonarQube * analysis. */ public static List extractIssueReport( Iterable issues, IssuePathResolver issuePathResolver, boolean includeExistingIssues, Set excludedRules) { return StreamSupport.stream( issues.spliterator(), false) .filter(issue -> shouldIncludeIssue( issue, issuePathResolver, includeExistingIssues, excludedRules )) .collect(Collectors.toList()); } static boolean shouldIncludeIssue( PostJobIssue issue, IssuePathResolver issuePathResolver, boolean includeExistingIssues, Set excludedRules ) { if (!includeExistingIssues && !issue.isNew()) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Issue {} is not a new issue and so, not added to the report", issue.key()); } return false; } if (excludedRules.contains(issue.ruleKey())) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Issue {} is ignored, not added to the report", issue.key()); } return false; } String path = issuePathResolver.getIssuePath(issue); if (!isProjectWide(issue) && path == null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Issue {} is not linked to a file, not added to the report", issue.key()); } return false; } return true; } } ================================================ FILE: src/main/java/org/sonar/plugins/stash/issue/collector/StashCollector.java ================================================ package org.sonar.plugins.stash.issue.collector; import com.github.cliftonlabs.json_simple.JsonArray; import com.github.cliftonlabs.json_simple.JsonObject; import java.math.BigDecimal; import org.sonar.plugins.stash.PullRequestRef; import org.sonar.plugins.stash.StashPlugin.IssueType; import org.sonar.plugins.stash.exceptions.StashReportExtractionException; import org.sonar.plugins.stash.issue.StashComment; import org.sonar.plugins.stash.issue.StashCommentReport; import org.sonar.plugins.stash.issue.StashDiff; import org.sonar.plugins.stash.issue.StashDiffReport; import org.sonar.plugins.stash.issue.StashPullRequest; import org.sonar.plugins.stash.issue.StashTask; import org.sonar.plugins.stash.issue.StashUser; public final class StashCollector { private static final String AUTHOR = "author"; private static final String VERSION = "version"; private StashCollector() { // Hiding implicit public constructor with an explicit private one (squid:S1118) } public static StashCommentReport extractComments(JsonObject jsonComments) { StashCommentReport result = new StashCommentReport(); JsonArray jsonValues = (JsonArray)jsonComments.get("values"); if (jsonValues != null) { for (Object obj : jsonValues.toArray()) { JsonObject jsonComment = (JsonObject)obj; StashComment comment = extractComment(jsonComment); result.add(comment); } } return result; } public static StashComment extractComment(JsonObject jsonComment, String path, Long line) { long id = getLong(jsonComment, "id"); String message = (String)jsonComment.get("text"); long version = getLong(jsonComment, VERSION); JsonObject jsonAuthor = (JsonObject)jsonComment.get(AUTHOR); StashUser stashUser = extractUser(jsonAuthor); return new StashComment(id, message, path, line, stashUser, version); } public static StashComment extractComment(JsonObject jsonComment) { JsonObject jsonAnchor = (JsonObject)jsonComment.get("anchor"); if (jsonAnchor == null) { throw new StashReportExtractionException("JSON Comment does not contain any \"anchor\" tag" + " to describe comment \"line\" and \"path\""); } String path = (String)jsonAnchor.get("path"); // can be null if comment is attached to the global file Long line = getLong(jsonAnchor, "line"); return extractComment(jsonComment, path, line); } public static StashPullRequest extractPullRequest(PullRequestRef pr, JsonObject jsonPullRequest) { StashPullRequest result = new StashPullRequest(pr); long version = getLong(jsonPullRequest, VERSION); result.setVersion(version); JsonArray jsonReviewers = (JsonArray)jsonPullRequest.get("reviewers"); if (jsonReviewers != null) { for (Object objReviewer : jsonReviewers.toArray()) { JsonObject jsonReviewer = (JsonObject)objReviewer; JsonObject jsonUser = (JsonObject)jsonReviewer.get("user"); if (jsonUser != null) { StashUser reviewer = extractUser(jsonUser); result.addReviewer(reviewer); } } } return result; } public static StashUser extractUser(JsonObject jsonUser) { long id = getLong(jsonUser, "id"); String name = (String)jsonUser.get("name"); String slug = (String)jsonUser.get("slug"); String email = (String)jsonUser.get("email"); return new StashUser(id, name, slug, email); } public static StashDiffReport extractDiffs(JsonObject jsonObject) { StashDiffReport result = new StashDiffReport(); JsonArray jsonDiffs = (JsonArray)jsonObject.get("diffs"); if (jsonDiffs == null) { return null; } // Let's call this for loop "objdiff_loop" for (Object objDiff : jsonDiffs.toArray()) { JsonObject jsonDiff = (JsonObject)objDiff; // destination path in diff view // if status of the file is deleted, destination == null JsonObject destinationPath = (JsonObject)jsonDiff.get("destination"); if (destinationPath == null) { continue; // Let's process the next item in "objdiff_loop" } String path = (String)destinationPath.get("toString"); JsonArray jsonHunks = (JsonArray)jsonDiff.get("hunks"); if (jsonHunks == null) { continue; // Let's process the next item in "objdiff_loop" } // calling the extracted section to scan the jsonHunks & jsonDiff into usable diffs result.add(parseHunksIntoDiffs(path, jsonHunks, jsonDiff)); // Extract File Comments: this kind of comment will be attached to line 0 JsonArray jsonLineComments = (JsonArray)jsonDiff.get("fileComments"); if (jsonLineComments == null) { continue; // Let's process the next item in "objdiff_loop" } StashDiff initialDiff = new StashDiff(IssueType.CONTEXT, path, 0, 0); // Let's call this for loop "objlinc_loop" for (Object objLineComment : jsonLineComments.toArray()) { JsonObject jsonLineComment = (JsonObject)objLineComment; long lineCommentId = getLong(jsonLineComment, "id"); String lineCommentMessage = (String)jsonLineComment.get("text"); long lineCommentVersion = getLong(jsonLineComment, VERSION); JsonObject objAuthor = (JsonObject)jsonLineComment.get(AUTHOR); if (objAuthor == null) { continue; // Let's process the next item in "objlinc_loop" } StashUser author = extractUser(objAuthor); StashComment comment = new StashComment(lineCommentId, lineCommentMessage, path, (long)0, author, lineCommentVersion); initialDiff.addComment(comment); } result.add(initialDiff); } return result; } private static StashDiffReport parseHunksIntoDiffs(String path, JsonArray jsonHunks, JsonObject jsonDiff) { StashDiffReport result = new StashDiffReport(); // Let's call this for loop "objhunk_loop" for (Object objHunk : jsonHunks.toArray()) { JsonObject jsonHunk = (JsonObject)objHunk; JsonArray jsonSegments = (JsonArray)jsonHunk.get("segments"); if (jsonSegments == null) { continue; // Let's process the next item in "objhunk_loop" } // Let's call this for loop "objsegm_loop" for (Object objSegment : jsonSegments.toArray()) { JsonObject jsonSegment = (JsonObject)objSegment; // type of the diff in diff view // We filter REMOVED type, like useless for SQ analysis IssueType type = IssueType.valueOf((String)jsonSegment.get("type")); // JsonArray jsonLines = (JsonArray)jsonSegment.get("lines"); if (type == IssueType.REMOVED || jsonLines == null) { continue; // Let's process the next item in "objsegm_loop" } // Let's call this for loop "objline_loop" for (Object objLine : jsonLines.toArray()) { JsonObject jsonLine = (JsonObject)objLine; // destination line in diff view long source = getLong(jsonLine, "source"); long destination = getLong(jsonLine,"destination"); StashDiff diff = new StashDiff(type, path, source, destination); // Add comment attached to the current line JsonArray jsonCommentIds = (JsonArray)jsonLine.get("commentIds"); // To keep this method depth under control (squid:S134), we outsourced the comments extraction result.add(extractCommentsForDiff(diff, jsonDiff, jsonCommentIds)); } } } return result; } private static StashDiff extractCommentsForDiff(StashDiff diff, JsonObject jsonDiff, JsonArray jsonCommentIds) { // If there is no comments, we just return the diff as-is if (jsonCommentIds == null) { return diff; } // Let's call this for loop "objcomm_loop" for (Object objCommentId : jsonCommentIds.toArray()) { long commentId = getLong(objCommentId); JsonArray jsonLineComments = (JsonArray)jsonDiff.get("lineComments"); if (jsonLineComments == null) { continue; // Let's process the next item in "objcomm_loop" } // Let's call this for loop "objlico_loop" for (Object objLineComment : jsonLineComments.toArray()) { JsonObject jsonLineComment = (JsonObject)objLineComment; long lineCommentId = getLong(jsonLineComment, "id"); if (lineCommentId != commentId) { continue; // Let's process the next item in "objcomm_loop" } // Sending the JSON for processing into a nice comment StashComment comment = buildCommentFromJSON(jsonLineComment, diff); // If there is no valid comment in the JSON, we just consider the next element in "objlico_loop" if (comment == null) { continue; } // At this point, we can save the comment and add any relevant task to it diff.addComment(comment); // get the tasks linked to the current comment updateCommentTasks(comment, (JsonArray)jsonLineComment.get("tasks")); } } return diff; } private static StashComment buildCommentFromJSON(JsonObject jsonLineComment, StashDiff diff) { long lineCommentId = getLong(jsonLineComment, "id"); String lineCommentMessage = (String)jsonLineComment.get("text"); long lineCommentVersion = getLong(jsonLineComment, VERSION); JsonObject objAuthor = (JsonObject)jsonLineComment.get(AUTHOR); if (objAuthor == null) { return null; } StashUser author = extractUser(objAuthor); return new StashComment(lineCommentId, lineCommentMessage, diff.getPath(), diff.getDestination(), author, lineCommentVersion); } private static void updateCommentTasks(StashComment comment, JsonArray jsonTasks) { // No need to fail on NullPointerException but we want to keep caller's complexity down if (jsonTasks == null) { return; } for (Object objTask : jsonTasks.toArray()) { JsonObject jsonTask = (JsonObject)objTask; comment.addTask(extractTask(jsonTask)); } } public static StashTask extractTask(JsonObject jsonTask) { long taskId = getLong(jsonTask, "id"); String taskText = (String)jsonTask.get("text"); String taskState = (String)jsonTask.get("state"); boolean deletable = true; JsonObject objPermission = (JsonObject)jsonTask.get("permittedOperations"); if (objPermission != null) { deletable = (boolean)objPermission.get("deletable"); } return new StashTask(taskId, taskText, taskState, deletable); } public static boolean isLastPage(JsonObject jsonObject) { if (jsonObject.get("isLastPage") != null) { return (Boolean)jsonObject.get("isLastPage"); } return true; } public static long getNextPageStart(JsonObject jsonObject) { if (jsonObject.get("nextPageStart") != null) { return getLong(jsonObject,"nextPageStart"); } return 0; } private static final Long getLong(JsonObject o, String name) { return getLong(o.get(name)); } private static final Long getLong(Object o) { BigDecimal bd = (BigDecimal) o; if (bd == null) { return null; } return bd.longValue(); } } ================================================ FILE: src/main/resources/org/sonar/plugins/stash/sonar-stash.properties ================================================ project.version=${project.version} project.name=${project.name} ================================================ FILE: src/test/java/org/sonar/plugins/stash/CompleteITCase.java ================================================ package org.sonar.plugins.stash; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.sonar.plugins.stash.TestUtils.primeWireMock; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.google.gson.JsonObject; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.RegisterExtension; import org.sonar.plugins.stash.fixtures.DummyStashServer; import org.sonar.plugins.stash.fixtures.MavenSonarFixtures; import org.sonar.plugins.stash.fixtures.SonarQubeRule; import org.sonar.plugins.stash.fixtures.SonarScanner; import org.sonar.plugins.stash.fixtures.WireMockExtension; import org.sonar.plugins.stash.issue.StashUser; import org.sonar.plugins.stash.issue.collector.DiffReportSample; public class CompleteITCase { protected static SonarScanner sonarScanner; protected File sourcesDir; protected static final StashUser user = new StashUser( // has to match data in fixture 1,"sonarqube", "sonarqube", "sq@example.com" ); protected static final String stashPassword = "myPassword"; protected static final String stashProject = "PROJ"; protected static final String stashRepo = "REPO"; protected String projectKey; protected String projectName; protected static final int stashPullRequest = 42; protected DummyStashServer stash; protected static final PullRequestRef pr = new PullRequestRef.Builder() .setProject(stashProject) .setRepository(stashRepo) .setPullRequestId(stashPullRequest) .build(); @RegisterExtension public WireMockExtension wireMock = new WireMockExtension( DummyStashServer.extend(WireMockConfiguration.options().dynamicPort()), true); @RegisterExtension public static SonarQubeRule sonarqube = new SonarQubeRule(MavenSonarFixtures.getSonarqube(9000)); @BeforeAll public static void setUpClass() throws Exception { sonarqube.get().installPlugin(new File(System.getProperty("test.plugin.archive"))); sonarqube.start(); sonarScanner = MavenSonarFixtures.getSonarScanner(); } @BeforeEach public void setUp(TestInfo testInfo) throws Exception { sourcesDir = Paths.get( System.getProperty("test.sources.dir"), testInfo.getTestMethod().get().getName() ).toFile(); projectKey = testInfo.getTestMethod().get().getName(); projectName = testInfo.getDisplayName(); sourcesDir.mkdirs(); sonarqube.get().createProject(projectKey, projectName); primeWireMock(wireMock); stash = new DummyStashServer(wireMock); stash.mockUser(user); } @Test public void testBasic() throws Exception { stash.mockPrDiff(pr, DiffReportSample.baseReport); stash.noCommentsFor(pr); stash.expectCommentsUpdateFor(pr); Properties props = new Properties(); props.put("sonar.sources", "."); scan(true, true, props); wireMock.verify(WireMock.getRequestedFor(WireMock.urlPathMatching(".*" + user.getSlug() + "$"))); wireMock.verify(WireMock.getRequestedFor(WireMock.urlPathMatching(".*diff$"))); wireMock.verify(WireMock.postRequestedFor(WireMock.urlPathMatching(".*comments$"))); // Making sure we find the proper agent info in a string like: User-Agent: SonarQube/4.5.7 Stash/1.2.0 AHC/1.0 wireMock.verify(WireMock.getRequestedFor(WireMock.anyUrl()) .withHeader("User-Agent", WireMock.matching("^(.*)Stash/[0-9.]+(.*)$"))); wireMock.verify(WireMock.getRequestedFor(WireMock.anyUrl()) .withHeader("User-Agent", WireMock.matching("^(.*)SonarQube/[0-9.]+(.*)$"))); } @Test public void testMultiModule() throws Exception { installFile("sonar-project.properties"); targetLocation("module1/src/main/java").toFile().mkdirs(); targetLocation("module2/src/main/java").toFile().mkdirs(); scan(false, false, null); installFile("module1/src/main/java/Foo.java"); installFile("module2/src/main/java/Bar.java"); stash.mockPrDiff( pr, "{\"fromHash\":\"bf19fb766666d80486aa81bc728a4e394ffa7ea8\",\"toHash\":\"79748088d6810b6e29eaa0319228fb8fecce14bb\",\"contextLines\":10,\"whitespace\":\"SHOW\",\"diffs\":[{\"source\":null,\"destination\":{\"components\":[\"module1\",\"src\",\"main\",\"java\",\"Foo.java\"],\"parent\":\"module1/src/main/java\",\"name\":\"Foo.java\",\"extension\":\"java\",\"toString\":\"module1/src/main/java/Foo.java\"},\"hunks\":[{\"sourceLine\":0,\"sourceSpan\":0,\"destinationLine\":1,\"destinationSpan\":5,\"segments\":[{\"type\":\"ADDED\",\"lines\":[{\"source\":0,\"destination\":1,\"line\":\"public class Foo {\",\"truncated\":false},{\"source\":0,\"destination\":2,\"line\":\" public static void main(String[] args) {\",\"truncated\":false},{\"source\":0,\"destination\":3,\"line\":\" System.out.println(\\\"Foo\\\");\",\"truncated\":false},{\"source\":0,\"destination\":4,\"line\":\" }\",\"truncated\":false},{\"source\":0,\"destination\":5,\"line\":\"}\",\"truncated\":false}],\"truncated\":false}],\"truncated\":false}],\"truncated\":false},{\"source\":null,\"destination\":{\"components\":[\"module2\",\"src\",\"main\",\"java\",\"Bar.java\"],\"parent\":\"module2/src/main/java\",\"name\":\"Bar.java\",\"extension\":\"java\",\"toString\":\"module2/src/main/java/Bar.java\"},\"hunks\":[{\"sourceLine\":0,\"sourceSpan\":0,\"destinationLine\":1,\"destinationSpan\":5,\"segments\":[{\"type\":\"ADDED\",\"lines\":[{\"source\":0,\"destination\":1,\"line\":\"public class Bar {\",\"truncated\":false},{\"source\":0,\"destination\":2,\"line\":\" public static void main(String[] args) {\",\"truncated\":false},{\"source\":0,\"destination\":3,\"line\":\" System.out.println(\\\"Bar\\\");\",\"truncated\":false},{\"source\":0,\"destination\":4,\"line\":\" }\",\"truncated\":false},{\"source\":0,\"destination\":5,\"line\":\"}\",\"truncated\":false}],\"truncated\":false}],\"truncated\":false}],\"truncated\":false}],\"truncated\":false}" ); stash.noCommentsFor(pr); stash.expectCommentsUpdateFor(pr); scan(true, true, null); List comments = stash.getCreatedComments(); assertThat(comments).hasSize(5); List overviewComment = comments.stream().filter(c -> !c.has("anchor")).collect(Collectors.toList()); assertThat(overviewComment).hasSize(1); assertThat(overviewComment.get(0).get("text").getAsString()).contains("SonarQube analysis Overview"); List fileComments = comments.stream().filter(c -> c.has("anchor")).collect(Collectors.toList()); assertThat(fileComments).hasSize(4); assertThat(fileComments).extracting(o -> o.get("anchor").getAsJsonObject().get("path").getAsString()).containsOnly( "module1/src/main/java/Foo.java", "module2/src/main/java/Bar.java" ); } private Path targetLocation(String name) { return sourcesDir.toPath().resolve(name); } private void installFile(String name) throws IOException { Path target = targetLocation(name); target.getParent().toFile().mkdirs(); Files.copy( getClass().getClassLoader().getResourceAsStream("foo/" + name), target ); } private String repoPath(String project, String repo, String... parts) { return urlPath("rest", "api", "1.0", "projects", project, "repos", repo, urlPath(false, parts)); } private String urlPath(String... parts) { return urlPath(true, parts); } private String urlPath(boolean leading, String... parts) { String prefix = ""; if (leading) { prefix = "/"; } return prefix + StringUtils.join(parts, '/'); } protected void scan(boolean activateSonarStash, boolean issuesMode, Properties properties) throws Exception { List sources = new ArrayList<>(); sources.add(sourcesDir); Properties extraProps = new Properties(); //extraProps.setProperty("sonar.analysis.mode", "incremental"); if (activateSonarStash) { extraProps.setProperty("sonar.stash.url", "http://127.0.0.1:" + wireMock.port()); extraProps.setProperty("sonar.stash.login", user.getSlug()); extraProps.setProperty("sonar.stash.password", stashPassword); extraProps.setProperty("sonar.stash.notification", "true"); extraProps.setProperty("sonar.stash.project", stashProject); extraProps.setProperty("sonar.stash.repository", stashRepo); extraProps.setProperty("sonar.stash.pullrequest.id", String.valueOf(stashPullRequest)); } if (issuesMode) { extraProps.put("sonar.analysis.mode", "issues"); } extraProps.setProperty("sonar.log.level", "DEBUG"); if (properties != null) { extraProps.putAll(properties); } sonarScanner.scan(sonarqube.get(), sourcesDir, projectKey, projectName, "0.0.0Final39", extraProps); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/DefaultIssue.java ================================================ package org.sonar.plugins.stash; import org.sonar.api.batch.fs.InputComponent; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.api.batch.rule.Severity; import org.sonar.api.rule.RuleKey; public class DefaultIssue implements PostJobIssue { private String key, componentKey, message; private InputComponent inputComponent; private boolean isNew; private Severity severity; private Integer line; private RuleKey ruleKey; public String key() { return key; } public DefaultIssue setKey(String key) { this.key = key; return this; } public String componentKey() { return componentKey; } public DefaultIssue setComponentKey(String componentKey) { this.componentKey = componentKey; return this; } public String message() { return message; } public DefaultIssue setMessage(String message) { this.message = message; return this; } public InputComponent inputComponent() { return inputComponent; } public DefaultIssue setInputComponent(InputComponent inputComponent) { this.inputComponent = inputComponent; return this; } @Override public boolean isNew() { return isNew; } public DefaultIssue setNew(boolean aNew) { isNew = aNew; return this; } public Severity severity() { return severity; } public DefaultIssue setSeverity(Severity severity) { this.severity = severity; return this; } public RuleKey ruleKey() { return ruleKey; } public DefaultIssue setRuleKey(RuleKey ruleKey) { this.ruleKey = ruleKey; return this; } public DefaultIssue setLine(Integer line) { this.line = line; return this; } @Override public Integer line() { return line; } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/DummyStashProjectBuilder.java ================================================ package org.sonar.plugins.stash; import java.io.File; public class DummyStashProjectBuilder extends StashProjectBuilder { private File projectBaseDir; public DummyStashProjectBuilder(File projectBaseDir) { this.projectBaseDir = projectBaseDir; } @Override public File getProjectBaseDir() { return projectBaseDir; } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/JavaUtilLoggingCapture.java ================================================ package org.sonar.plugins.stash; import java.util.ArrayList; import java.util.List; import java.util.logging.ConsoleHandler; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.logging.MemoryHandler; public class JavaUtilLoggingCapture extends TestWatcherExtension { private boolean gobbleOnSuccess; private List originalHandlers = new ArrayList<>(); private Level originalLevel; private ConsoleHandler console = new ConsoleHandler(); private MemoryHandler spool = new MemoryHandler(console, 10000, Level.OFF); private Logger logger; public JavaUtilLoggingCapture(boolean gobbleOnSuccess) { this.gobbleOnSuccess = gobbleOnSuccess; logger = Logger.getGlobal(); Logger parent; while ((parent = logger.getParent()) != null) { logger = parent; } } public JavaUtilLoggingCapture() { this(true); } @Override protected void succeeded() { if (!gobbleOnSuccess) { emit(); } } @Override protected void failed() { emit(); } @Override protected void finished() { logger.removeHandler(spool); for (Handler h : originalHandlers) { logger.addHandler(h); } logger.setLevel(originalLevel); } @Override protected void starting() { for (Handler h : logger.getHandlers()) { originalHandlers.add(h); logger.removeHandler(h); } logger.addHandler(spool); originalLevel = logger.getLevel(); logger.setLevel(Level.ALL); } private void emit() { console.publish(new LogRecord(Level.SEVERE, "Replaying captured log entries")); spool.push(); spool.flush(); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/PeekableInputStreamTest.java ================================================ package org.sonar.plugins.stash; import java.io.IOException; import java.io.StringBufferInputStream; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; public class PeekableInputStreamTest { @Test public void testEmptyStream() throws IOException { PeekableInputStream s = fromString(""); assertFalse(s.peek().isPresent()); assertFalse(s.peek().isPresent()); assertFalse(s.peek().isPresent()); } @Test public void testStream() throws IOException { PeekableInputStream s = fromString("abcde"); assertEquals((Character)'a', s.peek().get()); assertEquals((Character)'a', s.peek().get()); assertEquals((Character)'a', s.peek().get()); } private PeekableInputStream fromString(String s) { return new PeekableInputStream(new StringBufferInputStream(s)); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/PluginInfoTest.java ================================================ package org.sonar.plugins.stash; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; public class PluginInfoTest { private static final String plugin_name = "sonar-stash"; private static final String plugin_vers = "1337.0"; @Test public void testPluginInfoRef_ConstructorAndAccessors() { PluginInfo PI = new PluginInfo(plugin_name, plugin_vers); assertEquals(plugin_name, PI.getName()); assertEquals(plugin_vers, PI.getVersion()); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/StashIssueReportingPostJobTest.java ================================================ package org.sonar.plugins.stash; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.sonar.api.batch.postjob.PostJobContext; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.api.batch.rule.Severity; import org.sonar.api.platform.Server; import org.sonar.plugins.stash.client.StashClient; import org.sonar.plugins.stash.client.StashCredentials; import org.sonar.plugins.stash.issue.StashDiffReport; import org.sonar.plugins.stash.issue.StashUser; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) public class StashIssueReportingPostJobTest extends StashTest { private StashIssueReportingPostJob myJob; @Mock StashUser stashUser; @Mock StashRequestFacade stashRequestFacade; @Mock StashPluginConfiguration config; @Mock StashDiffReport diffReport; @Mock List report; @Mock PostJobContext context; @Mock Server server; private static final String STASH_PROJECT = "Project"; private static final String STASH_REPOSITORY = "Repository"; private static final int STASH_PULLREQUEST_ID = 1; private static final String STASH_LOGIN = "login@email.com"; private static final String STASH_USER_SLUG = "login"; private static final String STASH_PASSWORD = "password"; private static final String STASH_URL = "http://url/to/stash"; private static final int STASH_TIMEOUT = 10000; private static final int STASH_ISSUE_THRESHOLD = 100; private static final PullRequestRef pr = PullRequestRef.builder() .setProject(STASH_PROJECT) .setRepository(STASH_REPOSITORY) .setPullRequestId(STASH_PULLREQUEST_ID) .build(); private static final String SONARQUBE_URL = "http://url/to/sonarqube"; @BeforeEach public void setUp() throws Exception { when(config.hasToNotifyStash()).thenReturn(true); when(config.canApprovePullRequest()).thenReturn(false); when(config.getStashURL()).thenReturn(STASH_URL); when(config.getSonarQubeURL()).thenReturn(SONARQUBE_URL); when(config.getStashTimeout()).thenReturn(STASH_TIMEOUT); when(config.resetComments()).thenReturn(false); when(config.hasToNotifyStash()).thenReturn(true); when(config.includeAnalysisOverview()).thenReturn(Boolean.TRUE); when(report.size()).thenReturn(10); when(stashRequestFacade.extractIssueReport(eq(report))) .thenReturn(report); when(context.issues()).thenReturn(report); when(stashRequestFacade.getIssueThreshold()).thenReturn(STASH_ISSUE_THRESHOLD); when(stashRequestFacade.getStashProject()).thenReturn(STASH_PROJECT); when(stashRequestFacade.getStashRepository()).thenReturn(STASH_REPOSITORY); when(stashRequestFacade.getStashPullRequestId()).thenReturn(STASH_PULLREQUEST_ID); when(stashRequestFacade.getCredentials()).thenReturn(new StashCredentials(STASH_LOGIN, STASH_PASSWORD, STASH_USER_SLUG)); when(stashRequestFacade .getSonarQubeReviewer(anyString(), any(StashClient.class))).thenReturn( stashUser); when(stashRequestFacade.getPullRequestDiffReport(eq(pr), any())) .thenReturn(diffReport); when(stashRequestFacade.getIssueThreshold()).thenReturn(STASH_ISSUE_THRESHOLD); when(stashRequestFacade.getStashURL()).thenReturn(STASH_URL); when(stashRequestFacade.getPullRequest()).thenReturn(pr); } @Test public void testExecuteOn() throws Exception { myJob = new StashIssueReportingPostJob(config, stashRequestFacade, server); myJob.execute(context); verify(stashRequestFacade, times(0)) .resetComments(eq(pr), eq(diffReport), eq(stashUser), any()); verify(stashRequestFacade, times(1)) .postSonarQubeReport(eq(pr), eq(report), eq(diffReport), any()); verify(stashRequestFacade, times(1)) .postAnalysisOverview(eq(pr), eq(report), any()); verify(stashRequestFacade, times(0)) .approvePullRequest(eq(pr), any()); verify(stashRequestFacade, times(0)) .resetPullRequestApproval(eq(pr), any()); } @Test public void testExecuteOnWithReachedThreshold() throws Exception { when(stashRequestFacade.getIssueThreshold()).thenReturn(10); List report = mock(ArrayList.class); when(report.size()).thenReturn(55); when(stashRequestFacade.extractIssueReport(eq(report))) .thenReturn(report); myJob = new StashIssueReportingPostJob(config, stashRequestFacade, server); myJob.execute(context); verify(stashRequestFacade, times(0)) .resetComments(eq(pr), eq(diffReport), eq(stashUser), any()); verify(stashRequestFacade, times(0)) .postSonarQubeReport(eq(pr), eq(report), eq(diffReport), any()); verify(stashRequestFacade, times(1)) .postAnalysisOverview(eq(pr), any(Collection.class), any() ); } @Test public void testExecuteOnWithNoPluginActivation() throws Exception { when(config.hasToNotifyStash()).thenReturn(false); myJob = new StashIssueReportingPostJob(config, stashRequestFacade, server); myJob.execute(context); verify(stashRequestFacade, times(0)) .resetComments(eq(pr), eq(diffReport), eq(stashUser), any()); verify(stashRequestFacade, times(0)) .postSonarQubeReport(eq(pr), eq(report), eq(diffReport), any()); verify(stashRequestFacade, times(0)) .postAnalysisOverview(eq(pr), eq(report), any()); verify(stashRequestFacade, times(0)) .approvePullRequest(eq(pr), any()); verify(stashRequestFacade, times(0)) .resetPullRequestApproval(eq(pr), any()); } @Test public void testExecuteOnWithNoStashUserDefined() throws Exception { when(stashRequestFacade.getSonarQubeReviewer(anyString(), any())).thenReturn(null); myJob = new StashIssueReportingPostJob(config, stashRequestFacade, server); myJob.execute(context); verify(stashRequestFacade, times(0)) .resetComments(eq(pr), eq(diffReport), eq(stashUser), any()); verify(stashRequestFacade, times(0)) .postSonarQubeReport(eq(pr), eq(report), eq(diffReport), any()); verify(stashRequestFacade, times(0)) .postAnalysisOverview(eq(pr), eq(report), any()); verify(stashRequestFacade, times(0)) .approvePullRequest(eq(pr), any()); verify(stashRequestFacade, times(0)) .resetPullRequestApproval(eq(pr), any()); } @Test public void testExecuteOnWithResetCommentActivated() throws Exception { when(config.resetComments()).thenReturn(true); myJob = new StashIssueReportingPostJob(config, stashRequestFacade, server); myJob.execute(context); verify(stashRequestFacade, times(1)) .resetComments(eq(pr), eq(diffReport), eq(stashUser), any()); verify(stashRequestFacade, times(1)) .postSonarQubeReport(eq(pr), eq(report), eq(diffReport), any()); verify(stashRequestFacade, times(1)) .postAnalysisOverview(eq(pr), eq(report), any()); } @Test public void testExecuteOnWithNoDiffReport() throws Exception { diffReport = null; when(stashRequestFacade.getPullRequestDiffReport(eq(pr), any())) .thenReturn(diffReport); myJob = new StashIssueReportingPostJob(config, stashRequestFacade, server); myJob.execute(context); verify(stashRequestFacade, times(0)) .resetComments(eq(pr), eq(diffReport), eq(stashUser), any()); verify(stashRequestFacade, times(0)) .postSonarQubeReport(eq(pr), eq(report), eq(diffReport), any()); verify(stashRequestFacade, times(0)) .postAnalysisOverview(eq(pr), eq(report), any()); verify(stashRequestFacade, times(0)) .approvePullRequest(eq(pr), any()); verify(stashRequestFacade, times(0)) .resetPullRequestApproval(eq(pr), any()); } @Test public void testShouldApprovePullRequest() { PostJobIssue minorIssue = new DefaultIssue().setSeverity(Severity.MINOR); PostJobIssue majorIssue = new DefaultIssue().setSeverity(Severity.MAJOR); report = new ArrayList<>(); report.add(minorIssue); report.add(majorIssue); assertFalse( StashIssueReportingPostJob.shouldApprovePullRequest(Optional.empty(), report) ); report.clear(); assertTrue( StashIssueReportingPostJob.shouldApprovePullRequest(Optional.empty(), report) ); report.add(minorIssue); assertTrue( StashIssueReportingPostJob.shouldApprovePullRequest(Optional.of(Severity.MINOR), report) ); report.add(majorIssue); assertFalse( StashIssueReportingPostJob.shouldApprovePullRequest(Optional.of(Severity.MINOR), report) ); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/StashPluginConfigurationTest.java ================================================ package org.sonar.plugins.stash; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Optional; import org.junit.jupiter.api.Test; import org.sonar.api.CoreProperties; import org.sonar.api.batch.rule.Severity; import org.sonar.api.config.Settings; import org.sonar.api.config.internal.MapSettings; public class StashPluginConfigurationTest { @Test public void testStashPluginConfiguration_ConstructorAndAccessors() { Integer SPRI = 1337; Settings settings = new MapSettings(); settings.setProperty(StashPlugin.STASH_NOTIFICATION, true); settings.setProperty(StashPlugin.STASH_PROJECT, "take-over-the-world"); settings.setProperty(StashPlugin.STASH_REPOSITORY, "death-ray"); settings.setProperty(StashPlugin.STASH_PULL_REQUEST_ID, SPRI); settings.setProperty(StashPlugin.STASH_URL, "https://stash"); settings.setProperty(StashPlugin.STASH_LOGIN, "me"); settings.setProperty(StashPlugin.STASH_USER_SLUG, "mini.me"); settings.setProperty(StashPlugin.STASH_PASSWORD, "unsecure"); settings.setProperty(StashPlugin.STASH_PASSWORD_ENVIRONMENT_VARIABLE, "you-should-not"); settings.setProperty(CoreProperties.LOGIN, "him"); settings.setProperty(CoreProperties.PASSWORD, "notsafe"); settings.setProperty(StashPlugin.STASH_ISSUE_THRESHOLD, 42000); settings.setProperty(StashPlugin.STASH_ISSUE_SEVERITY_THRESHOLD, "MINOR"); settings.setProperty(StashPlugin.STASH_TIMEOUT, 42); settings.setProperty(StashPlugin.STASH_REVIEWER_APPROVAL, true); settings.setProperty(StashPlugin.STASH_RESET_COMMENTS, false); settings.setProperty(StashPlugin.STASH_TASK_SEVERITY_THRESHOLD, "MINOR"); settings.setProperty(StashPlugin.STASH_INCLUDE_ANALYSIS_OVERVIEW, true); //Optional getRepositoryRoot() ??? StashPluginConfiguration SPC = new StashPluginConfiguration(settings, null); assertTrue(SPC.hasToNotifyStash()); assertEquals("take-over-the-world", SPC.getStashProject()); assertEquals("death-ray", SPC.getStashRepository()); assertEquals(SPRI, SPC.getPullRequestId()); assertEquals("https://stash", SPC.getStashURL()); assertEquals("me", SPC.getStashLogin()); assertEquals("mini.me", SPC.getStashUserSlug()); assertEquals("unsecure", SPC.getStashPassword()); assertEquals("you-should-not", SPC.getStashPasswordEnvironmentVariable()); assertEquals("him", SPC.getSonarQubeLogin()); assertEquals("notsafe", SPC.getSonarQubePassword()); assertEquals(42000, SPC.getIssueThreshold()); assertEquals(Severity.MINOR, SPC.getIssueSeverityThreshold()); assertEquals(42, SPC.getStashTimeout()); assertTrue(SPC.canApprovePullRequest()); assertFalse(SPC.resetComments()); assertEquals(Optional.of(Severity.MINOR), SPC.getTaskIssueSeverityThreshold()); assertTrue(SPC.includeAnalysisOverview()); //assertEquals(, SPC.getRepositoryRoot()); settings.setProperty(StashPlugin.STASH_TASK_SEVERITY_THRESHOLD, "NONE"); assertEquals(Optional.empty(), SPC.getTaskIssueSeverityThreshold()); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/StashPluginUtilsTest.java ================================================ package org.sonar.plugins.stash; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.sonar.plugins.stash.StashPluginUtils.formatPercentage; import static org.sonar.plugins.stash.StashPluginUtils.roundedPercentageGreaterThan; public class StashPluginUtilsTest { @Test public void testFormatPercentage() { assertEquals("10.9", formatPercentage(10.90)); assertEquals("10.9", formatPercentage(10.94)); assertEquals("11.0", formatPercentage(10.96)); assertEquals("11.0", formatPercentage(11.0)); assertEquals("31.3", formatPercentage(31.25)); assertEquals("50.3", formatPercentage(50.29)); // This test can fail with 50.2 instead of 50.3 if run with an older version of the JDK 8 // (failed with v25 & worked with v131) } @Test public void testRoundedPercentageGreaterThan() { assertTrue(roundedPercentageGreaterThan(0.1, 0.0)); assertTrue(roundedPercentageGreaterThan(0.2, 0.1)); assertTrue(roundedPercentageGreaterThan(1, 0.1)); assertTrue(roundedPercentageGreaterThan(1.1, 0.1)); assertFalse(roundedPercentageGreaterThan(0.0, 0.1)); assertFalse(roundedPercentageGreaterThan(0.1, 0.2)); assertFalse(roundedPercentageGreaterThan(1.1, 1.2)); assertFalse(roundedPercentageGreaterThan(1.01, 1.00)); assertFalse(roundedPercentageGreaterThan(1.04, 1.00)); assertTrue(roundedPercentageGreaterThan(1.05, 1.00)); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/StashRequestFacadeTest.java ================================================ package org.sonar.plugins.stash; import java.nio.file.FileSystems; import java.nio.file.Path; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.api.batch.rule.Severity; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.System2; import org.sonar.plugins.stash.StashPlugin.IssueType; import org.sonar.plugins.stash.client.StashClient; import org.sonar.plugins.stash.client.StashCredentials; import org.sonar.plugins.stash.exceptions.StashClientException; import org.sonar.plugins.stash.exceptions.StashConfigurationException; import org.sonar.plugins.stash.fixtures.DummyIssuePathResolver; import org.sonar.plugins.stash.issue.MarkdownPrinter; import org.sonar.plugins.stash.issue.StashComment; import org.sonar.plugins.stash.issue.StashCommentReport; import org.sonar.plugins.stash.issue.StashDiffReport; import org.sonar.plugins.stash.issue.StashPullRequest; import org.sonar.plugins.stash.issue.StashTask; import org.sonar.plugins.stash.issue.StashUser; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.sonar.plugins.stash.TestUtils.inputFile; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) public class StashRequestFacadeTest extends StashTest { StashRequestFacade myFacade; @Mock StashPluginConfiguration config; @Mock StashClient stashClient; @Mock StashDiffReport diffReport; @Mock StashCommentReport stashCommentsReport1; @Mock StashCommentReport stashCommentsReport2; @Mock StashComment comment1; @Mock StashComment comment2; @Mock StashComment comment3; @Mock StashUser stashUser; @Mock System2 system; String stashCommentMessage1; String stashCommentMessage2; String stashCommentMessage3; List report; StashProjectBuilder spr; private static final String STASH_PROJECT = "Project"; private static final String STASH_REPOSITORY = "Repository"; private static final int STASH_PULLREQUEST_ID = 1; private static final PullRequestRef pr = PullRequestRef.builder() .setProject(STASH_PROJECT) .setRepository(STASH_REPOSITORY) .setPullRequestId(STASH_PULLREQUEST_ID) .build(); private static final IssueType STASH_DIFF_TYPE = IssueType.CONTEXT; private static final String STASH_USER = "SonarQube"; private static final String STASH_URL = "http://stash/url"; private static final String SONARQUBE_URL = "http://sonar/url"; private static final String FILE_PATH_1 = "path/to/file1"; private static final String FILE_PATH_2 = "path/to/file2"; @BeforeEach public void setUp() throws Exception { config = mock(StashPluginConfiguration.class); when(config.getTaskIssueSeverityThreshold()).thenReturn(Optional.empty()); when(config.getIssueSeverityThreshold()).thenReturn(Severity.INFO); when(config.getSonarQubeURL()).thenReturn(SONARQUBE_URL); when(config.getStashURL()).thenReturn(STASH_URL); when(config.getStashProject()).thenReturn(STASH_PROJECT); when(config.getStashRepository()).thenReturn(STASH_REPOSITORY); when(config.getRepositoryRoot()).thenReturn(Optional.empty()); when(system.envVariable(any())).thenReturn(null); spr = new DummyStashProjectBuilder(new File("/basedir")); StashRequestFacade facade = new StashRequestFacade(config, spr, system); myFacade = spy(facade); stashClient = mock(StashClient.class); diffReport = mock(StashDiffReport.class); when(diffReport.getType(anyString(), anyLong(), anyInt())).thenReturn(STASH_DIFF_TYPE); when(diffReport.getLine(FILE_PATH_1, 1)).thenReturn((long)1); when(diffReport.getLine(FILE_PATH_1, 2)).thenReturn((long)2); when(diffReport.getLine(FILE_PATH_2, 1)).thenReturn((long)1); stashUser = mock(StashUser.class); when(stashUser.getId()).thenReturn((long) 1234); MarkdownPrinter printer = new MarkdownPrinter(100, SONARQUBE_URL, 0, new DummyIssuePathResolver()); report = new ArrayList(); Path moduleBaseDir = FileSystems.getDefault().getPath("some", "dir"); PostJobIssue issue1 = new DefaultIssue().setKey("key1") .setSeverity(Severity.CRITICAL) .setMessage("message1") .setRuleKey(RuleKey.of("foo", "rule1")) .setInputComponent(inputFile("module1", moduleBaseDir, "/basedir/" + FILE_PATH_1)) .setLine(1); stashCommentMessage1 = printer.printIssueMarkdown(issue1); report.add(issue1); PostJobIssue issue2 = new DefaultIssue().setKey("key2") .setSeverity(Severity.MAJOR) .setMessage("message2") .setRuleKey(RuleKey.of("foo", "rule2")) .setInputComponent(inputFile("module2", moduleBaseDir, "/basedir/" + FILE_PATH_1)) .setLine(2); stashCommentMessage2 = printer.printIssueMarkdown(issue2); report.add(issue2); PostJobIssue issue3 = new DefaultIssue().setKey("key3") .setSeverity(Severity.INFO) .setMessage("message3") .setRuleKey(RuleKey.of("foo", "rule3")) .setInputComponent(inputFile("module3", moduleBaseDir, "/basedir/" + FILE_PATH_2)) .setLine(1); stashCommentMessage3 = printer.printIssueMarkdown(issue3); report.add(issue3); StashTask task1 = mock(StashTask.class); when(task1.getId()).thenReturn((long)1111); List taskList1 = new ArrayList<>(); taskList1.add(task1); comment1 = mock(StashComment.class); when(comment1.getId()).thenReturn((long)1111); when(comment1.getAuthor()).thenReturn(stashUser); when(stashClient.postCommentLineOnPullRequest(pr, stashCommentMessage1, FILE_PATH_1, 1, STASH_DIFF_TYPE)).thenReturn(comment1); when(comment1.getTasks()).thenReturn(taskList1); when(comment1.containsPermanentTasks()).thenReturn(false); StashTask task2 = mock(StashTask.class); when(task1.getId()).thenReturn((long)2222); List taskList2 = new ArrayList<>(); taskList2.add(task2); comment2 = mock(StashComment.class); when(comment2.getId()).thenReturn((long)2222); when(comment2.getAuthor()).thenReturn(stashUser); when(stashClient.postCommentLineOnPullRequest(pr, stashCommentMessage2, FILE_PATH_1, 2, STASH_DIFF_TYPE)).thenReturn(comment2); when(comment2.getTasks()).thenReturn(taskList2); when(comment2.containsPermanentTasks()).thenReturn(false); StashTask task3 = mock(StashTask.class); when(task3.getId()).thenReturn((long)3333); List taskList3 = new ArrayList<>(); taskList3.add(task3); comment3 = mock(StashComment.class); when(comment3.getId()).thenReturn((long)3333); when(comment3.getAuthor()).thenReturn(stashUser); when(stashClient.postCommentLineOnPullRequest(pr, stashCommentMessage3, FILE_PATH_2, 1, STASH_DIFF_TYPE)).thenReturn(comment3); when(comment3.getTasks()).thenReturn(taskList3); when(comment3.containsPermanentTasks()).thenReturn(false); ArrayList comments = new ArrayList<>(); comments.add(comment1); comments.add(comment2); comments.add(comment3); when(diffReport.getComments()).thenReturn(comments); stashCommentsReport1 = mock(StashCommentReport.class); when(stashCommentsReport1.getComments()).thenReturn(comments); when(stashCommentsReport1.applyDiffReport(diffReport)).thenReturn(stashCommentsReport1); when(stashClient.getPullRequestComments(pr, "path/to/file1")).thenReturn(stashCommentsReport1); stashCommentsReport2 = mock(StashCommentReport.class); when(stashCommentsReport1.getComments()).thenReturn(comments); when(stashCommentsReport2.applyDiffReport(diffReport)).thenReturn(stashCommentsReport2); when(stashClient.getPullRequestComments(pr, "path/to/file2")).thenReturn(stashCommentsReport2); doNothing().when(stashClient).deletePullRequestComment(eq(pr), any(StashComment.class)); doNothing().when(stashClient).deleteTaskOnComment(any(StashTask.class)); } @Test public void testGetCredentials() throws StashConfigurationException { when(config.getStashLogin()).thenReturn("login"); when(config.getStashPassword()).thenReturn("password"); StashCredentials credentials = myFacade.getCredentials(); assertEquals("login", credentials.getLogin()); assertEquals("password", credentials.getPassword()); } @Test public void testGetNoCredentials() throws StashConfigurationException { when(config.getStashLogin()).thenReturn(null); when(config.getStashPassword()).thenReturn(null); StashCredentials credentials = myFacade.getCredentials(); assertNull(credentials.getLogin()); assertNull(credentials.getPassword()); } @Test public void testGetPasswordFromEnvironment() throws StashConfigurationException { when(config.getStashLogin()).thenReturn("login"); when(config.getStashPasswordEnvironmentVariable()).thenReturn("SONAR_STASH_PASSWORD"); when(system.envVariable("SONAR_STASH_PASSWORD")).thenReturn("envPassword"); StashCredentials credentials = myFacade.getCredentials(); assertEquals("envPassword", credentials.getPassword()); when(config.getStashPassword()).thenReturn("password"); credentials = myFacade.getCredentials(); assertEquals("envPassword", credentials.getPassword()); } @Test public void testGetPasswordFromUnconfiguredEnvironment() throws StashConfigurationException { when(config.getStashLogin()).thenReturn("login"); when(config.getStashPasswordEnvironmentVariable()).thenReturn("SONAR_STASH_PASSWORD"); assertThrows(StashConfigurationException.class, () -> myFacade.getCredentials() ); } @Test public void testGetIssueThreshold() throws StashConfigurationException { when(config.getIssueThreshold()).thenReturn(1); assertEquals(1, myFacade.getIssueThreshold()); } @Test public void testGetIssueThresholdThrowsException() throws StashConfigurationException { when(config.getIssueThreshold()).thenThrow(new NumberFormatException()); assertThrows(StashConfigurationException.class, () -> myFacade.getIssueThreshold() ); } @Test public void testGetStashURL() throws StashConfigurationException { when(config.getStashURL()).thenReturn("http://url"); assertEquals("http://url", myFacade.getStashURL()); when(config.getStashURL()).thenReturn("http://url/"); assertEquals("http://url", myFacade.getStashURL()); } @Test public void testGetStashURLThrowsException() throws StashConfigurationException { when(config.getStashURL()).thenReturn(null); assertThrows(StashConfigurationException.class, () -> myFacade.getStashURL() ); } @Test public void testGetStashProject() throws StashConfigurationException { when(config.getStashProject()).thenReturn("project"); assertEquals("project", myFacade.getStashProject()); } @Test public void testGetStashProjectThrowsException() throws StashConfigurationException { when(config.getStashProject()).thenReturn(null); assertThrows(StashConfigurationException.class, () -> myFacade.getStashProject() ); } @Test public void testGetStashRepository() throws StashConfigurationException { when(config.getStashRepository()).thenReturn("repository"); assertEquals("repository", myFacade.getStashRepository()); } @Test public void testGetStashRepositoryThrowsException() throws StashConfigurationException { when(config.getStashRepository()).thenReturn(null); assertThrows(StashConfigurationException.class, () -> myFacade.getStashRepository() ); } @Test public void testGetStashPullRequestId() throws StashConfigurationException { when(config.getPullRequestId()).thenReturn(12345); assertEquals(12345, myFacade.getStashPullRequestId()); } @Test public void testGetStashPullRequestIdThrowsException() throws StashConfigurationException { when(config.getPullRequestId()).thenReturn(null); assertThrows(StashConfigurationException.class, () -> myFacade.getStashPullRequestId() ); } @Test public void testPostCommentPerIssue() throws Exception { when(stashCommentsReport1.contains(stashCommentMessage1, FILE_PATH_1, 1)).thenReturn(true); when(stashCommentsReport1.contains(stashCommentMessage2, FILE_PATH_1, 2)).thenReturn(false); when(stashCommentsReport2.contains(stashCommentMessage3, FILE_PATH_2, 1)).thenReturn(false); myFacade.postCommentPerIssue(pr, report, diffReport, stashClient); verify(stashClient, times(0)).postCommentLineOnPullRequest(pr, stashCommentMessage1, FILE_PATH_1, 1, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage2, "path/to/file1", 2, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage3, "path/to/file2", 1, STASH_DIFF_TYPE); } @Test public void testPostCommentPerIssueWithNoStashCommentAlreadyPushed() throws Exception { when(stashCommentsReport1.contains(stashCommentMessage1, FILE_PATH_1, 1)).thenReturn(true); when(stashCommentsReport1.contains(stashCommentMessage2, FILE_PATH_1, 2)).thenReturn(true); when(stashCommentsReport2.contains(stashCommentMessage3, FILE_PATH_2, 1)).thenReturn(true); myFacade.postCommentPerIssue(pr, report, diffReport, stashClient); verify(stashClient, times(0)).postCommentLineOnPullRequest(pr, stashCommentMessage1, FILE_PATH_1, 1, STASH_DIFF_TYPE); verify(stashClient, times(0)).postCommentLineOnPullRequest(pr, stashCommentMessage2, FILE_PATH_1, 2, STASH_DIFF_TYPE); verify(stashClient, times(0)).postCommentLineOnPullRequest(pr, stashCommentMessage3, FILE_PATH_2, 1, STASH_DIFF_TYPE); } @Test public void testPostCommentPerIssueIgnoresLowerSeverityIssues() throws Exception { when(config.getIssueSeverityThreshold()).thenReturn(Severity.MAJOR); when(stashCommentsReport1.contains(stashCommentMessage1, FILE_PATH_1, 1)).thenReturn(true); when(stashCommentsReport1.contains(stashCommentMessage2, FILE_PATH_1, 2)).thenReturn(false); when(stashCommentsReport2.contains(stashCommentMessage3, FILE_PATH_2, 1)).thenReturn(false); myFacade.postCommentPerIssue(pr, report, diffReport, stashClient); verify(stashClient, times(0)).postCommentLineOnPullRequest(pr, stashCommentMessage1, FILE_PATH_1, 1, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage2, "path/to/file1", 2, STASH_DIFF_TYPE); verify(stashClient, times(0)).postCommentLineOnPullRequest(pr, stashCommentMessage3, "path/to/file2", 1, STASH_DIFF_TYPE); } @Test public void testPostSonarQubeReport() throws StashClientException, StashConfigurationException { myFacade.postSonarQubeReport(pr, report, diffReport, stashClient); verify(myFacade, times(1)).postCommentPerIssue(eq(pr), anyCollection(), eq(diffReport), eq(stashClient)); } @Test public void testPostTaskOnComment() throws Exception { when(config.getTaskIssueSeverityThreshold()).thenReturn(Optional.of(Severity.INFO)); myFacade.postSonarQubeReport(pr, report, diffReport, stashClient); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage1, FILE_PATH_1, 1, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage2, FILE_PATH_1, 2, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage3, FILE_PATH_2, 1, STASH_DIFF_TYPE); verify(stashClient, times(1)).postTaskOnComment("message3", (long)3333); verify(stashClient, times(1)).postTaskOnComment("message2", (long)2222); verify(stashClient, times(1)).postTaskOnComment("message1", (long)1111); } @Test public void testPostTaskOnCommentWithRestrictedLevel() throws Exception { when(config.getTaskIssueSeverityThreshold()).thenReturn(Optional.of(Severity.MAJOR)); myFacade.postSonarQubeReport(pr, report, diffReport, stashClient); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage1, FILE_PATH_1, 1, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage2, FILE_PATH_1, 2, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage3, FILE_PATH_2, 1, STASH_DIFF_TYPE); verify(stashClient, times(0)).postTaskOnComment("message3", (long)3333); verify(stashClient, times(1)).postTaskOnComment("message2", (long)2222); verify(stashClient, times(1)).postTaskOnComment("message1", (long)1111); } @Test public void testPostTaskOnCommentWithNotPresentLevel() throws Exception { when(config.getTaskIssueSeverityThreshold()).thenReturn(Optional.of(Severity.BLOCKER)); myFacade.postSonarQubeReport(pr, report, diffReport, stashClient); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage1, FILE_PATH_1, 1, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage2, FILE_PATH_1, 2, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage3, FILE_PATH_2, 1, STASH_DIFF_TYPE); verify(stashClient, times(0)).postTaskOnComment("message3", (long)3333); verify(stashClient, times(0)).postTaskOnComment("message2", (long)2222); verify(stashClient, times(0)).postTaskOnComment("message1", (long)1111); } @Test public void testPostTaskOnCommentWithSeverityNone() throws Exception { when(config.getTaskIssueSeverityThreshold()).thenReturn(Optional.empty()); myFacade.postSonarQubeReport(pr, report, diffReport, stashClient); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage1, FILE_PATH_1, 1, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage2, FILE_PATH_1, 2, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage3, FILE_PATH_2, 1, STASH_DIFF_TYPE); verify(stashClient, times(0)).postTaskOnComment("message3", (long)3333); verify(stashClient, times(0)).postTaskOnComment("message2", (long)2222); verify(stashClient, times(0)).postTaskOnComment("message1", (long)1111); } @Test public void testPostSonarQubeReportWithNoType() throws Exception { when(stashCommentsReport1.contains(stashCommentMessage1, FILE_PATH_1, 1)).thenReturn(false); when(stashCommentsReport1.contains(stashCommentMessage2, FILE_PATH_1, 2)).thenReturn(false); when(stashCommentsReport2.contains(stashCommentMessage3, FILE_PATH_2, 1)).thenReturn(false); when(diffReport.getType(FILE_PATH_1, 1, config.issueVicinityRange())).thenReturn(null); when(diffReport.getType(FILE_PATH_1, 2, config.issueVicinityRange())).thenReturn(STASH_DIFF_TYPE); when(diffReport.getType(FILE_PATH_2, 1, config.issueVicinityRange())).thenReturn(null); myFacade.postSonarQubeReport(pr, report, diffReport, stashClient); verify(stashClient, times(0)).postCommentLineOnPullRequest(pr, stashCommentMessage1, FILE_PATH_1, 1, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage2, FILE_PATH_1, 2, STASH_DIFF_TYPE); verify(stashClient, times(0)).postCommentLineOnPullRequest(pr, stashCommentMessage3, FILE_PATH_2, 1, STASH_DIFF_TYPE); } @Test public void testPostSonarQubeReportWithNoSonarQubeIssues() throws Exception { myFacade.postSonarQubeReport(pr, new ArrayList<>(), diffReport, stashClient); verify(stashClient, times(0)).postCommentLineOnPullRequest(pr, stashCommentMessage1, FILE_PATH_1, 1, STASH_DIFF_TYPE); verify(stashClient, times(0)).postCommentLineOnPullRequest(pr, stashCommentMessage2, FILE_PATH_1, 2, STASH_DIFF_TYPE); verify(stashClient, times(0)).postCommentLineOnPullRequest(pr, stashCommentMessage3, FILE_PATH_2, 1, STASH_DIFF_TYPE); } @Test public void testGetSonarQubeReviewer() throws Exception { when(stashClient.getUser(STASH_USER)).thenReturn(stashUser); StashUser reviewer = myFacade.getSonarQubeReviewer(STASH_USER, stashClient); assertEquals(1234, reviewer.getId()); } @Test public void testGetPullRequestDiffReport() throws Exception { when(stashClient.getPullRequestDiffs(pr)).thenReturn(diffReport); StashDiffReport result = myFacade.getPullRequestDiffReport(pr, stashClient); assertEquals(1, result.getLine(FILE_PATH_1, 1)); assertEquals(2, result.getLine(FILE_PATH_1, 2)); assertEquals(1, result.getLine(FILE_PATH_2, 1)); } @Test public void testResetComments() throws Exception { myFacade.resetComments(pr, diffReport, stashUser, stashClient); verify(stashClient, times(3)).deleteTaskOnComment(any(StashTask.class)); verify(stashClient, times(3)).deletePullRequestComment(eq(pr), any(StashComment.class)); } @Test public void testResetCommentsWithNotDeletableTasks() throws Exception { when(comment1.containsPermanentTasks()).thenReturn(true); myFacade.resetComments(pr, diffReport, stashUser, stashClient); verify(stashClient, times(2)).deleteTaskOnComment(any(StashTask.class)); verify(stashClient, times(2)).deletePullRequestComment(eq(pr), any(StashComment.class)); } @Test public void testResetCommentsWithNoTasks() throws Exception { when(comment1.getTasks()).thenReturn(new ArrayList()); myFacade.resetComments(pr, diffReport, stashUser, stashClient); verify(stashClient, times(2)).deleteTaskOnComment(any(StashTask.class)); verify(stashClient, times(3)).deletePullRequestComment(eq(pr), any(StashComment.class)); } @Test public void testResetCommentsWithDifferentStashUsers() throws Exception { StashUser stashUser2 = mock(StashUser.class); when(stashUser2.getId()).thenReturn((long)4321); StashComment comment = mock(StashComment.class); when(comment.getAuthor()).thenReturn(stashUser2); ArrayList comments = new ArrayList<>(); comments.add(comment); when(diffReport.getComments()).thenReturn(comments); myFacade.resetComments(pr, diffReport, stashUser, stashClient); verify(stashClient, times(0)).deletePullRequestComment(eq(pr), any(StashComment.class)); } @Test public void testResetCommentsWithoutAnyComments() throws Exception { when(diffReport.getComments()).thenReturn(new ArrayList()); myFacade.resetComments(pr, diffReport, stashUser, stashClient); verify(stashClient, times(0)).deletePullRequestComment(eq(pr), any(StashComment.class)); } @Test public void testApprovePullRequest() throws Exception { myFacade.approvePullRequest(pr, stashClient); verify(stashClient, times(1)).approvePullRequest(pr); } @Test public void addResetPullRequestApproval() throws Exception { myFacade.resetPullRequestApproval(pr, stashClient); verify(stashClient, times(1)).resetPullRequestApproval(pr); } @Test public void testAddPullRequestReviewer() throws Exception { ArrayList reviewers = new ArrayList<>(); StashUser stashUser = mock(StashUser.class); reviewers.add(stashUser); StashPullRequest pullRequest = mock(StashPullRequest.class); when(pullRequest.getReviewer(STASH_USER)).thenReturn(null); when(pullRequest.getVersion()).thenReturn((long)1); when(stashClient.getPullRequest(pr)).thenReturn(pullRequest); when(stashClient.getUser(STASH_USER)).thenReturn(stashUser); myFacade.addPullRequestReviewer(pr, STASH_USER, stashClient); verify(stashClient, times(1)).addPullRequestReviewer(pr, 1, reviewers); } @Test public void testAddPullRequestReviewerWithReviewerAlreadyAdded() throws Exception { ArrayList reviewers = new ArrayList<>(); StashUser stashUser = mock(StashUser.class); reviewers.add(stashUser); StashPullRequest pullRequest = mock(StashPullRequest.class); when(pullRequest.getReviewer(STASH_USER)).thenReturn(stashUser); when(pullRequest.getVersion()).thenReturn((long)1); when(stashClient.getPullRequest(pr)).thenReturn(pullRequest); when(stashClient.getUser(STASH_USER)).thenReturn(stashUser); myFacade.addPullRequestReviewer(pr, STASH_USER, stashClient); verify(stashClient, times(0)).addPullRequestReviewer(pr, 1, reviewers); } @Test public void testGetPullRequest() throws Exception { doReturn("SonarQube").when(myFacade).getStashProject(); doReturn("sonar-stash-plugin").when(myFacade).getStashRepository(); doReturn(1337).when(myFacade).getStashPullRequestId(); assertTrue(myFacade.getPullRequest() instanceof PullRequestRef); } /* FIXME @Test public void testGetIssuePathWithoutExplicitSourceRootDir() { when(config.getRepositoryRoot()).thenReturn(Optional.empty()); inputFileCache.putInputFile( "key", new DefaultInputFile("some/relative/path").setAbsolutePath("/root/some/absolute/path") ); assertEquals("some/absolute/path", myFacade.getIssuePath(new DefaultIssue().setComponentKey("key"))); } @Test public void testGetIssuePathWithExplicitSourceRootDir() { when(config.getRepositoryRoot()).thenReturn(Optional.of(new File("/root/some/"))); inputFileCache.putInputFile( "key", new DefaultInputFile("some/relative/path").setAbsolutePath("/root/some/absolute/path") ); assertEquals("absolute/path", myFacade.getIssuePath(new DefaultIssue().setComponentKey("key"))); } */ @Test public void testPostCommentPerIssueWithIncludeVicinityIssues() throws Exception { when(config.issueVicinityRange()).thenReturn(10); when(stashCommentsReport1.contains(stashCommentMessage1, FILE_PATH_1, 1)).thenReturn(false); when(stashCommentsReport1.contains(stashCommentMessage2, FILE_PATH_1, 2)).thenReturn(false); when(stashCommentsReport2.contains(stashCommentMessage3, FILE_PATH_2, 1)).thenReturn(false); when(diffReport.getType(FILE_PATH_1, 1, config.issueVicinityRange())).thenReturn(STASH_DIFF_TYPE); when(diffReport.getType(FILE_PATH_1, 2, config.issueVicinityRange())).thenReturn(STASH_DIFF_TYPE); when(diffReport.getType(FILE_PATH_2, 1, config.issueVicinityRange())).thenReturn(STASH_DIFF_TYPE); myFacade.postCommentPerIssue(pr, report, diffReport, stashClient); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage1, FILE_PATH_1, 1, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage2, FILE_PATH_1, 2, STASH_DIFF_TYPE); verify(stashClient, times(1)).postCommentLineOnPullRequest(pr, stashCommentMessage3, FILE_PATH_2, 1, STASH_DIFF_TYPE); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/StashTest.java ================================================ package org.sonar.plugins.stash; import org.junit.jupiter.api.extension.RegisterExtension; public abstract class StashTest { @RegisterExtension public JavaUtilLoggingCapture logRule = new JavaUtilLoggingCapture(); } ================================================ FILE: src/test/java/org/sonar/plugins/stash/TestUtils.java ================================================ package org.sonar.plugins.stash; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.file.Path; import java.util.List; import java.util.Map; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.TestInputFileBuilder; public class TestUtils { private TestUtils() {} public static T notNull(T t) { assertNotNull(t); return t; } public static void assertContains(String s, String expected) { assertNotNull(s); assertNotNull(expected); assertTrue(s.contains(expected)); } // The first request to wiremock may be slow. // We could increase the timeout on our StashClient but then all the timeout test take longer. // So instead we perform a dummy request on each test invocation with a high timeout. // We now have many more request than before, but are faster anyways. public static void primeWireMock(WireMockServer wireMock) throws Exception { StubMapping primingMapping = WireMock.get(WireMock.urlPathEqualTo("/")).build(); wireMock.addStubMapping(primingMapping); HttpURLConnection conn = (HttpURLConnection)new URL("http://127.0.0.1:" + wireMock.port()).openConnection(); conn.setConnectTimeout(1000); conn.setConnectTimeout(1000); conn.connect(); conn.getResponseCode(); wireMock.removeStub(primingMapping); wireMock.resetRequests(); } public static WrappedProcessBuilder createProcess(String prefix, String... command) { ProcessBuilder pb = new ProcessBuilder(command); return new WrappedProcessBuilder(prefix, pb); } public static class WrappedProcessBuilder { private final String prefix; private final ProcessBuilder wrapped; private WrappedProcessBuilder(String prefix, ProcessBuilder wrapped) { this.prefix = prefix; this.wrapped = wrapped; } public WrappedProcessBuilder directory(File directory) { wrapped.directory(directory); return this; } public Process start() throws IOException { return new WrappedProcess(prefix, wrapped.start()); } public List command() { return wrapped.command(); } public Map environment() { return wrapped.environment(); } } public static class WrappedProcess extends Process { private final Process wrapped; private final ForwarderThread outputLogger; private final ForwarderThread errorLogger; private WrappedProcess(String prefix, Process wrapped) { this.wrapped = wrapped; errorLogger = new ForwarderThread(prefix, wrapped.getErrorStream()); errorLogger.start(); outputLogger = new ForwarderThread(prefix, wrapped.getInputStream()); outputLogger.start(); } @Override public OutputStream getOutputStream() { return wrapped.getOutputStream(); } @Override public InputStream getInputStream() { return wrapped.getInputStream(); } @Override public InputStream getErrorStream() { return wrapped.getErrorStream(); } @Override public int waitFor() throws InterruptedException { int exitCode = wrapped.waitFor(); stopLoggers(); return exitCode; } @Override public int exitValue() { return wrapped.exitValue(); } @Override public void destroy() { wrapped.destroy(); } private void stopLoggers() throws InterruptedException { outputLogger.interrupt(); errorLogger.interrupt(); outputLogger.join(); errorLogger.join(); } } private static class ForwarderThread extends Thread implements AutoCloseable { private final String prefix; private final InputStream input; private ForwarderThread(String prefix, InputStream input) { this.prefix = "[" + prefix + "] "; this.input = input; setDaemon(true); } @Override public void run() { try (BufferedReader lineReader = new BufferedReader(new InputStreamReader(input))) { while (!Thread.interrupted()) { String line = lineReader.readLine(); if (line != null) { System.out.println(prefix + line); //logger.info(line); } } } catch (IOException e) { /* ignored */ } } @Override public void close() throws InterruptedException { interrupt(); join(); } } public static InputFile inputFile(String moduleKey, String path) { return TestInputFileBuilder.create(moduleKey, path).build(); } public static InputFile inputFile(String moduleKey, Path moduleBaseDir, String path) { return TestInputFileBuilder.create(moduleKey, path).setModuleBaseDir(moduleBaseDir).build(); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/TestWatcherExtension.java ================================================ package org.sonar.plugins.stash; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; public abstract class TestWatcherExtension implements BeforeEachCallback, AfterEachCallback { @Override public void beforeEach(ExtensionContext context) throws Exception { starting(); } @Override public void afterEach(ExtensionContext context) throws Exception { finished(); if (context.getExecutionException().isPresent()) { failed(); } else { succeeded(); } } abstract void starting(); abstract void finished(); abstract void succeeded(); abstract void failed(); } ================================================ FILE: src/test/java/org/sonar/plugins/stash/client/ContentTypeTest.java ================================================ package org.sonar.plugins.stash.client; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; public class ContentTypeTest { @Test public void testContentType() { ContentType json = new ContentType("application", "json", null); assertTrue(json.match("application/json")); assertTrue(json.match("application/json;charset=utf-8")); assertTrue(json.match("APPLICATION/JSON")); assertTrue(json.match("ApPlIcAtIoN/jSoN")); assertFalse(json.match("")); assertFalse(json.match("/")); assertFalse(json.match(";")); assertFalse(json.match("/;")); assertFalse(json.match("foo")); assertFalse(json.match("12!4")); assertFalse(json.match("foo/json")); assertFalse(json.match("application/foo")); assertFalse(json.match("application/foo;charset=utf-8")); } @Test public void testConstructorFailure() { assertThrows(IllegalArgumentException.class, () -> new ContentType("application", "json", "what-is-the-point-if-I-must-be-null?") ); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/client/StashClientTest.java ================================================ package org.sonar.plugins.stash.client; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.any; import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.putRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; import static java.net.HttpURLConnection.HTTP_CREATED; import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.net.HttpURLConnection.HTTP_OK; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.sonar.plugins.stash.TestUtils.assertContains; import static org.sonar.plugins.stash.TestUtils.primeWireMock; import com.github.tomakehurst.wiremock.client.MappingBuilder; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.matching.EqualToPattern; import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.sonar.plugins.stash.PullRequestRef; import org.sonar.plugins.stash.StashPlugin.IssueType; import org.sonar.plugins.stash.StashTest; import org.sonar.plugins.stash.exceptions.StashClientException; import org.sonar.plugins.stash.exceptions.StashReportExtractionException; import org.sonar.plugins.stash.fixtures.WireMockExtension; import org.sonar.plugins.stash.issue.StashComment; import org.sonar.plugins.stash.issue.StashCommentReport; import org.sonar.plugins.stash.issue.StashDiffReport; import org.sonar.plugins.stash.issue.StashPullRequest; import org.sonar.plugins.stash.issue.StashTask; import org.sonar.plugins.stash.issue.StashUser; import org.sonar.plugins.stash.issue.collector.DiffReportSample; public class StashClientTest extends StashTest { private static final int timeout = 800; private static final int errorTimeout = timeout + 10; PullRequestRef pr = PullRequestRef.builder() .setProject("Project") .setRepository("Repository") .setPullRequestId(1) .build(); @RegisterExtension public WireMockExtension wireMock = new WireMockExtension(WireMockConfiguration.options().dynamicPort()); StashClient client; StashUser testUser = new StashUser(1, "userName", "userSlug", "email"); @BeforeEach public void setUp() throws Exception { primeWireMock(wireMock); client = new StashClient("http://127.0.0.1:" + wireMock.port(), new StashCredentials("login@email.com", "password", "login"), timeout, "dummyVersion"); } @Test public void testPostCommentOnPullRequest() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withStatus(HttpURLConnection.HTTP_CREATED))); assertDoesNotThrow(() -> client.postCommentOnPullRequest(pr, "Report") ); } @Test public void testGetBaseUrl() { assertEquals("http://127.0.0.1:" + wireMock.port(), client.getBaseUrl()); } @Test public void testGetLogin() { assertEquals("login@email.com", client.getLogin()); } @Test public void testPostCommentOnPullRequestWithWrongHTTPResult() throws Exception { addErrorResponse(any(anyUrl()), HTTP_NOT_IMPLEMENTED); try { client.postCommentOnPullRequest(pr, "Report"); fail("Wrong HTTP result should raised StashClientException"); } catch (StashClientException e) { assertContains(e.getMessage(), String.valueOf(HttpURLConnection.HTTP_NOT_IMPLEMENTED)); assertContains(e.getMessage(), "detailed error"); assertContains(e.getMessage(), "seriousException"); } } @Test public void testPostCommentOnPullRequestWithException() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withFixedDelay(errorTimeout))); assertThrows(StashClientException.class, () -> client.postCommentOnPullRequest(pr, "Report") ); } @Test public void testGetPullRequestComments() throws Exception { String stashJsonComment = "{\"values\": [{\"id\":1234, \"text\":\"message\", \"anchor\": {\"path\":\"path\", \"line\":5}," + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\": 0}]}"; wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withBody(stashJsonComment))); StashCommentReport report = client.getPullRequestComments(pr, "path"); assertTrue(report.contains("message", "path", 5)); assertEquals(1, report.size()); } @Test public void testGetPullRequestCommentsWithoutAuthor() throws Exception { String stashJsonComment = "{\"values\": [{\"id\":1234, \"text\":\"message\"," + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\": 0}]}"; wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withBody(stashJsonComment))); assertThrows(StashReportExtractionException.class, () -> client.getPullRequestComments(pr, "path") ); } @Test public void testGetPullRequestCommentsWithNextPage() throws Exception { String stashJsonComment1 = "{\"values\": [{\"id\":1234, \"text\":\"message1\", \"anchor\": {\"path\":\"path\", \"line\":1}," + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\": 0}], \"isLastPage\": false, \"nextPageStart\": 1}"; String stashJsonComment2 = "{\"values\": [{\"id\":4321, \"text\":\"message2\", \"anchor\": {\"path\":\"path\", \"line\":2}," + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\": 0}], \"isLastPage\": true}"; wireMock.stubFor(get( urlPathEqualTo("/rest/api/1.0/projects/Project/repos/Repository/pull-requests/1/comments")) .withQueryParam("start", equalTo(String.valueOf(0))).willReturn( aJsonResponse().withStatus(HttpURLConnection.HTTP_OK).withBody(stashJsonComment1) )); wireMock.stubFor(get( urlPathEqualTo("/rest/api/1.0/projects/Project/repos/Repository/pull-requests/1/comments")) .withQueryParam("start", equalTo(String.valueOf(1))).willReturn( aJsonResponse().withStatus(HttpURLConnection.HTTP_OK).withBody(stashJsonComment2) )); StashCommentReport report = client.getPullRequestComments(pr, "path"); assertTrue(report.contains("message1", "path", 1)); assertTrue(report.contains("message2", "path", 2)); assertEquals(2, report.size()); } @Test public void testGetPullRequestCommentsWithNoNextPage() throws Exception { String stashJsonComment1 = "{\"values\": [{\"id\":1234, \"text\":\"message1\", \"anchor\": {\"path\":\"path\", \"line\":5}," + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\": 0}], \"isLastPage\": true, \"nextPageStart\": 1}"; String stashJsonComment2 = "{\"values\": [{\"id\":4321, \"text\":\"message2\", \"anchor\": {\"path\":\"path\", \"line\":10}," + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\": 0}], \"isLastPage\": true}"; wireMock.stubFor(get(anyUrl()).withQueryParam("start", equalTo(String.valueOf(0))).willReturn( aJsonResponse().withStatus(HttpURLConnection.HTTP_OK).withBody(stashJsonComment1))); wireMock.stubFor(get(anyUrl()).withQueryParam("start", equalTo(String.valueOf(1))).willReturn( aJsonResponse().withStatus(HttpURLConnection.HTTP_OK).withBody(stashJsonComment2))); StashCommentReport report = client.getPullRequestComments(pr, "path"); assertTrue(report.contains("message1", "path", 5)); assertFalse(report.contains("message2", "path", 10)); assertEquals(1, report.size()); } @Test public void testGetPullRequestCommentsWithWrongHTTPResult() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withStatus(HTTP_FORBIDDEN))); assertThrows(StashClientException.class, () -> client.getPullRequestComments(pr, "path") ); } @Test public void testGetPullRequestCommentsWithWrongContentType() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aXMLResponse().withStatus(HTTP_OK))); assertThrows(StashClientException.class, () -> client.getPullRequestComments(pr, "path") ); } @Test public void testGetPullRequestCommentsWithException() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withFixedDelay(errorTimeout))); assertThrows(StashClientException.class, () -> client.getPullRequestComments(pr, "path") ); } @Test public void testGetPullRequestDiffs() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withStatus(HTTP_OK) .withBody(DiffReportSample.baseReport))); StashDiffReport report = client.getPullRequestDiffs(pr); assertEquals(4, report.getDiffs().size()); } @Test public void testGetPullRequestDiffsWithMalformedTasks() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withStatus(HTTP_OK) .withBody(DiffReportSample.baseReportWithMalformedTasks))); assertThrows(StashClientException.class, () -> client.getPullRequestDiffs(pr) ); } @Test public void testGetPullRequestDiffsWithWrongHTTPResult() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withStatus(HTTP_FORBIDDEN) .withBody(DiffReportSample.baseReport))); assertThrows(StashClientException.class, () -> client.getPullRequestDiffs(pr) ); } @Test public void testGetPullRequestDiffsWithException() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withFixedDelay(errorTimeout))); assertThrows(StashClientException.class, () -> client.getPullRequestDiffs(pr) ); } @Test public void testPostCommentLineOnPullRequest() throws Exception { String stashJsonComment = "{\"id\":1234, \"text\":\"message\", \"anchor\": {\"path\":\"path\", \"line\":5}," + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\": 0}"; wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withStatus(HTTP_CREATED).withBody(stashJsonComment))); StashComment comment = client.postCommentLineOnPullRequest(pr, "message", "path", 5, IssueType.CONTEXT); assertEquals(1234, comment.getId()); } @Test public void testPostCommentLineOnPullRequestWithWrongHTTPResult() throws Exception { addErrorResponse(any(anyUrl()), HTTP_FORBIDDEN); try { client.postCommentLineOnPullRequest(pr, "message", "path", 5, IssueType.CONTEXT); fail("Wrong HTTP result should raised StashClientException"); } catch (StashClientException e) { assertContains(e.getMessage(), "detailed error"); assertContains(e.getMessage(), "seriousException"); } } @Test public void testPostCommentLineOnPullRequestWithException() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withStatus(HTTP_CREATED).withFixedDelay(errorTimeout))); assertThrows(StashClientException.class, () -> client.postCommentLineOnPullRequest(pr, "message", "path", 5, IssueType.CONTEXT) ); } @Test public void testGetUser() throws Exception { String jsonUser = "{\"name\":\"SonarQube\", \"email\":\"sq@email.com\", \"id\":1, \"slug\":\"sonarqube\"}"; wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withBody(jsonUser))); StashUser user = client.getUser("sonarqube"); assertEquals(1, user.getId()); assertEquals("SonarQube", user.getName()); assertEquals("sq@email.com", user.getEmail()); assertEquals("sonarqube", user.getSlug()); } @Test public void testGetUserWithWrongHTTPResult() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withStatus(HTTP_FORBIDDEN))); assertThrows(StashClientException.class, () -> client.getUser("sonarqube") ); } @Test public void testDeletePullRequestComment() throws Exception { StashComment stashComment = new StashComment(1234, "message", "path", 42L, testUser, 0); wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withStatus(HTTP_NO_CONTENT))); client.deletePullRequestComment(pr, stashComment); wireMock.verify(deleteRequestedFor(anyUrl())); } @Test public void testGetPullRequest() throws Exception { String jsonPullRequest = "{\"version\": 1, \"title\":\"PR-Test\", \"description\":\"PR-test\", \"reviewers\": []}"; wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withBody(jsonPullRequest))); StashPullRequest pullRequest = client.getPullRequest(pr); assertEquals(1, pullRequest.getId()); assertEquals("Project", pullRequest.getProject()); assertEquals("Repository", pullRequest.getRepository()); assertEquals(1, pullRequest.getVersion()); } @Test public void testApprovePullRequest() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse())); client.approvePullRequest(pr); wireMock.verify(postRequestedFor(anyUrl())); } @Test public void testResetPullRequestApproval() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse())); client.resetPullRequestApproval(pr); wireMock.verify(deleteRequestedFor(anyUrl())); } @Test public void testAddPullRequestReviewer() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse())); List reviewers = new ArrayList<>(); reviewers.add(testUser); client.addPullRequestReviewer(pr, 1L, reviewers); wireMock.verify(putRequestedFor(anyUrl())); } @Test public void testAddPullRequestReviewerWithNoReviewer() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse())); client.addPullRequestReviewer(pr, 1L, new ArrayList()); wireMock.verify(putRequestedFor(anyUrl())); } @Test public void testPostTaskOnComment() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withStatus(HTTP_CREATED))); client.postTaskOnComment("message", 1111L); wireMock.verify(postRequestedFor(anyUrl())); } @Test public void testDeleteTaskOnComment() throws Exception { wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withStatus(HTTP_NO_CONTENT))); StashTask task = new StashTask(1111L, "some text", "some state", true); client.deleteTaskOnComment(task); wireMock.verify(deleteRequestedFor(anyUrl())); } @Test public void testFollowInternalRedirection() throws Exception { String jsonUser = "{\"name\":\"SonarQube\", \"email\":\"sq@email.com\", \"id\":1, \"slug\":\"sonarqube\"}"; wireMock.stubFor(get(anyUrl()).atPriority(2).willReturn( aJsonResponse().withStatus(HTTP_MOVED_TEMP).withHeader("Location", "/foo"))); wireMock.stubFor(get(urlPathEqualTo("/foo")).atPriority(1).willReturn(aJsonResponse().withBody(jsonUser))); client.getUser("does not matter"); wireMock.verify(getRequestedFor(urlPathEqualTo("/foo"))); } @Test public void testPullRequestHugePullRequestId() throws Exception { // See https://github.com/AmadeusITGroup/sonar-stash/issues/98 int hugePullRequestId = 1234567890; wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse())); PullRequestRef pr = PullRequestRef.builder() .setProject("Project") .setRepository("Repository") .setPullRequestId(hugePullRequestId) .build(); client.getPullRequestComments(pr, "something"); wireMock.verify(getRequestedFor(urlPathMatching(".*/pull-requests/1234567890/comments.*"))); } @Test public void testClientWithoutCredentials() throws Exception { String jsonUser = "{\"name\":\"SonarQube\", \"email\":\"sq@email.com\", \"id\":1, \"slug\":\"sonarqube\"}"; wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withBody(jsonUser))); client = new StashClient("http://127.0.0.1:" + wireMock.port(), new StashCredentials(null, null, null), timeout, "dummyVersion"); client.getUser("test"); wireMock.verify(getRequestedFor(anyUrl()).withoutHeader("Authorization")); } @Test public void testClientWithoutPassword() throws Exception { String jsonUser = "{\"name\":\"SonarQube\", \"email\":\"sq@email.com\", \"id\":1, \"slug\":\"sonarqube\"}"; wireMock.stubFor(any(anyUrl()).willReturn(aJsonResponse().withBody(jsonUser))); client = new StashClient("http://127.0.0.1:" + wireMock.port(), new StashCredentials("foo", null, "foo"), timeout, "dummyVersion"); client.getUser("test"); wireMock.verify(getRequestedFor(anyUrl()).withHeader("Authorization", new EqualToPattern("Basic Zm9vOg=="))); } private void addErrorResponse(MappingBuilder mapping, int statusCode) { wireMock.stubFor(mapping.willReturn(aJsonResponse() .withStatus(statusCode) .withHeader("Content-Type", "application/json") .withBody("{\n" + " \"errors\": [\n" + " {\n" + " \"context\": null,\n" + " \"message\": \"A detailed error message.\",\n" + " \"exceptionName\": \"seriousException\"\n" + " }\n" + " ]\n" + "}") )); } public static ResponseDefinitionBuilder aJsonResponse() { return aResponse().withHeader("Content-Type", "application/json").withBody("{}"); } public static ResponseDefinitionBuilder aXMLResponse() { return aResponse().withHeader("Content-Type", "application/xml") .withBody(""); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/end2end/EndToEndTest.java ================================================ package org.sonar.plugins.stash.end2end; import static org.sonar.plugins.stash.TestUtils.notNull; import static org.sonar.plugins.stash.TestUtils.primeWireMock; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.google.common.io.Resources; import com.google.gson.JsonObject; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.List; import java.util.function.Consumer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; import org.picocontainer.DefaultPicoContainer; import org.sonar.api.batch.postjob.PostJobContext; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.api.batch.sensor.internal.MockAnalysisMode; import org.sonar.api.config.Settings; import org.sonar.api.config.internal.MapSettings; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; import org.sonar.plugins.stash.DummyStashProjectBuilder; import org.sonar.plugins.stash.PullRequestRef; import org.sonar.plugins.stash.StashIssueReportingPostJob; import org.sonar.plugins.stash.StashPlugin; import org.sonar.plugins.stash.StashPluginConfiguration; import org.sonar.plugins.stash.StashRequestFacade; import org.sonar.plugins.stash.fixtures.DummyPostJobContext; import org.sonar.plugins.stash.fixtures.DummyPostJobIssue; import org.sonar.plugins.stash.fixtures.DummyServer; import org.sonar.plugins.stash.fixtures.DummyStashServer; import org.sonar.plugins.stash.fixtures.WireMockExtension; import org.sonar.plugins.stash.issue.StashUser; public abstract class EndToEndTest { @RegisterExtension public WireMockExtension wireMock = new WireMockExtension( DummyStashServer.extend(WireMockConfiguration.options().dynamicPort()), true); @BeforeEach public void setUp() throws Exception { primeWireMock(wireMock); } private File repoRoot = new File("/invalid/"); protected RunResults run(String fixtureName, Consumer settingsCustomizer, Collection issues) throws Exception { StashUser user = new StashUser( // has to match data in fixture 1,"some.user", "some.user", "some.user@example.org" ); PullRequestRef pr = PullRequestRef.builder() .setProject("a") .setRepository("b") .setPullRequestId(42).build(); DummyStashServer sqServer = new DummyStashServer(wireMock); sqServer.mockUser(user); sqServer.mockPrDiff(pr, readFixture(fixtureName)); sqServer.noCommentsFor(pr); sqServer.expectCommentsUpdateFor(pr); Settings settings = new MapSettings(); settings.setProperty(StashPlugin.STASH_NOTIFICATION, true); settings.setProperty(StashPlugin.STASH_URL, "http://127.0.0.1:" + wireMock.port()); settings.setProperty(StashPlugin.STASH_TIMEOUT, 400); settings.setProperty(StashPlugin.STASH_LOGIN, user.getName()); settings.setProperty(StashPlugin.STASH_PROJECT, pr.project()); settings.setProperty(StashPlugin.STASH_REPOSITORY, pr.repository()); settings.setProperty(StashPlugin.STASH_PULL_REQUEST_ID, pr.pullRequestId()); settings.setProperty(StashPlugin.STASH_ISSUE_THRESHOLD, Integer.MAX_VALUE); settings.setProperty(StashPlugin.STASH_ISSUE_SEVERITY_THRESHOLD, Severity.MINOR); settings.setProperty(StashPlugin.STASH_REPOSITORY_ROOT, repoRoot.toString()); settings.setProperty(StashPlugin.STASH_TASK_SEVERITY_THRESHOLD, Severity.CRITICAL); settingsCustomizer.accept(settings); MockAnalysisMode mode = new MockAnalysisMode(); mode.setPreviewOrIssue(true); DefaultPicoContainer container = new DefaultPicoContainer(); container.addComponent(settings); container.addComponent(DummyServer.class); container.addComponent(new DummyStashProjectBuilder(repoRoot)); container.addComponent(StashPluginConfiguration.class); container.addComponent(StashRequestFacade.class); container.addComponent(StashIssueReportingPostJob.class); PostJobContext context = new DummyPostJobContext(settings, mode, issues); StashIssueReportingPostJob job = notNull(container.getComponent(StashIssueReportingPostJob.class)); job.executeThrowing(context); RunResults results = new RunResults(); results.comments = sqServer.getCreatedComments(); return results; } protected static class RunResults { public List comments; } private static String readFixture(String name) throws IOException { // https://stackoverflow.com/a/6068228 return Resources.toString( Resources.getResource("fixtures/" + name + ".json"), StandardCharsets.UTF_8 ); } protected PostJobIssue issue(String key, String ruleKey, String module, String moduleSubDir, String path, int line) { return new DummyPostJobIssue( repoRoot.toPath().resolve(moduleSubDir), key, RuleKey.of("squid", ruleKey), module, path, line,"some message", org.sonar.api.batch.rule.Severity.MAJOR); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/end2end/Issue194.java ================================================ package org.sonar.plugins.stash.end2end; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.gson.JsonObject; import java.util.Arrays; import java.util.Optional; import org.junit.jupiter.api.Test; import org.sonar.plugins.stash.StashPlugin; public class Issue194 extends EndToEndTest { @Test public void testWithVicinitiyRange() throws Exception { run(0); } @Test public void testAfterVicinityRange() throws Exception { run(5); } private void run(int vicinityRange) throws Exception { RunResults results = run("issue194_stash_diff", (settings) -> settings.setProperty(StashPlugin.STASH_INCLUDE_VICINITY_RANGE, vicinityRange), Arrays.asList( issue("01673981387EDCCDB0", "ModifiersOrderCheck", "com.gdn.package:module-api-client", "aModule-api-client", "src/main/java/com/gdn/package/aModule/client/aModuleClient.java", 56), issue("01673981387EDCCDB1", "S106", "com.gdn.package:module-api-client", "aModule-api-client", "src/main/java/com/gdn/package/aModule/client/aModuleClient.java", 109), issue("01673981387EDCCDB2", "S106", "com.gdn.package:module-api-client", "aModule-api-client", "src/main/java/com/gdn/package/aModule/client/aModuleClient.java", 120), issue("01673981387EDCCDB3", "S2583", "com.gdn.package:module-api-client", "aModule-api-client", "src/main/java/com/gdn/package/aModule/client/aModuleClient.java", 108), issue("01673981387EDCCDB4", "S1481", "com.gdn.package:module-api-client", "aModule-api-client", "src/main/java/com/gdn/package/aModule/client/aModuleClient.java", 106), issue("01673981387EDCCDB5", "S1185", "com.gdn.package:module-api-client", "aModule-api-client", "src/main/java/com/gdn/package/aModule/client/aModuleClient.java", 207), issue("01673981387EDCCDB6", "S00117", "com.gdn.package:module-api-client", "aModule-api-client", "src/main/java/com/gdn/package/aModule/client/aModuleClient.java", 106), issue("01673981387EDCCDB7", "S00117", "com.gdn.package:module-api-client", "aModule-api-client", "src/main/java/com/gdn/package/aModule/client/aModuleClient.java", 119) )); assertEquals(6, results.comments.size()); Optional comment1 = results.comments.stream().filter((c) -> c.get("text").getAsString().contains("S1481")).findFirst(); assertTrue(comment1.isPresent()); comment1.ifPresent(c -> { assertNotNull(c); assertEquals(106, c.get("anchor").getAsJsonObject().get("line").getAsLong()); }); for (JsonObject o: results.comments) { assertEquals("TO", o.get("anchor").getAsJsonObject().get("fileType").getAsString()); } } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/fixtures/DummyIssuePathResolver.java ================================================ package org.sonar.plugins.stash.fixtures; import org.sonar.api.batch.fs.InputComponent; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.plugins.stash.IssuePathResolver; public class DummyIssuePathResolver implements IssuePathResolver { @Override public String getIssuePath(PostJobIssue issue) { InputComponent ic = issue.inputComponent(); if (ic == null) { return null; } return ((InputFile) ic).relativePath(); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/fixtures/DummyPostJobContext.java ================================================ package org.sonar.plugins.stash.fixtures; import java.util.Collection; import java.util.Collections; import org.sonar.api.batch.AnalysisMode; import org.sonar.api.batch.postjob.PostJobContext; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.api.config.Configuration; import org.sonar.api.config.Settings; import org.sonar.api.config.internal.ConfigurationBridge; public class DummyPostJobContext implements PostJobContext { private final Settings settings; private final AnalysisMode mode; private final Collection issues; public DummyPostJobContext(Settings settings, AnalysisMode mode, Collection issues) { this.settings = settings; this.mode = mode; this.issues = issues; } @Override public Settings settings() { return settings; } @Override public Configuration config() { return new ConfigurationBridge(settings); } @Override public AnalysisMode analysisMode() { return mode; } @Override public Iterable issues() { return issues; } @Override public Iterable resolvedIssues() { return Collections.emptyList(); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/fixtures/DummyPostJobIssue.java ================================================ package org.sonar.plugins.stash.fixtures; import static org.sonar.plugins.stash.TestUtils.inputFile; import java.nio.file.Path; import javax.annotation.CheckForNull; import org.sonar.api.batch.fs.InputComponent; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.api.batch.rule.Severity; import org.sonar.api.rule.RuleKey; public class DummyPostJobIssue implements PostJobIssue { private final String key; private final RuleKey ruleKey; private final String componentKey; private final Integer line; private final String message; private final Severity severity; private final InputComponent component; public DummyPostJobIssue(Path moduleBaseDir, String key, RuleKey ruleKey, String module, String relativePath, Integer line, String message, Severity severity) { this.key = key; this.ruleKey = ruleKey; this.componentKey = module + ":" + relativePath; this.line = line; this.message = message; this.severity = severity; component = inputFile(componentKey, moduleBaseDir, relativePath); } @Override public String key() { return key; } @Override public RuleKey ruleKey() { return ruleKey; } @Override public String componentKey() { return componentKey; } @CheckForNull @Override public InputComponent inputComponent() { return component; } @CheckForNull @Override public Integer line() { return line; } @CheckForNull @Override public String message() { return message; } @Override public Severity severity() { return severity; } @Override public boolean isNew() { return true; } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/fixtures/DummyServer.java ================================================ package org.sonar.plugins.stash.fixtures; import java.io.File; import java.util.Date; import javax.annotation.CheckForNull; import org.sonar.api.platform.Server; public class DummyServer extends Server { public DummyServer() {} @Override public String getId() { return null; } @CheckForNull @Override public String getPermanentServerId() { return null; } @Override public String getVersion() { return null; } @Override public Date getStartedAt() { return null; } @Override public File getRootDir() { return null; } @Override public String getContextPath() { return null; } @Override public String getPublicRootUrl() { return null; } @Override public boolean isDev() { return false; } @Override public boolean isSecured() { return false; } @Override public String getURL() { return null; } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/fixtures/DummyStashServer.java ================================================ package org.sonar.plugins.stash.fixtures; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static org.junit.jupiter.api.Assertions.assertEquals; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.Response; import com.google.common.base.Joiner; import com.google.gson.Gson; import com.google.gson.JsonObject; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.sonar.plugins.stash.PullRequestRef; import org.sonar.plugins.stash.issue.StashUser; public class DummyStashServer { private final WireMockServer wireMock; private final Map users = new HashMap<>(); private final WireMockResponseCallback responseCallback = new WireMockResponseCallback(); private final List createdComments = new ArrayList<>(); private int commentId = 0; public DummyStashServer(WireMockServer wireMock) { this.wireMock = wireMock; } public void mockUser(StashUser user) { users.put(user.getSlug(), user); mockEntity(user, "users", user.getSlug()); } public void mockPrDiff(PullRequestRef pr, String x) { mockResponse(x, "projects", pr.project(), "repos", pr.repository(), "pull-requests", String.valueOf(pr.pullRequestId()), "diff"); } public void noCommentsFor(PullRequestRef pr) { mockResponse("{\"comments\": []}", "projects", pr.project(), "repos", pr.repository(), "pull-requests", String.valueOf(pr.pullRequestId()), "comments"); } public void expectCommentsUpdateFor(PullRequestRef pr) { wireMock.stubFor( post(urlPathEqualTo( coreApi( "projects", pr.project(), "repos", pr.repository(), "pull-requests", String.valueOf(pr.pullRequestId()), "comments"))) .willReturn(responseCallback.callback(aJsonResponse().withStatus(201), (request, response, fileSource, parameters) -> { Gson gson = new Gson(); JsonObject d = gson.fromJson(request.getBodyAsString(), JsonObject.class); Map result = new HashMap<>(); result.put("id", commentId++); result.put("text", d.get("text").getAsString()); result.put("version", 1); result.put("author", getUserFromRequest(request)); createdComments.add(d); return Response.Builder.like(response) .but() .body(gson.toJson(result)) .build(); }) )); } private StashUser getUserFromRequest(Request request) { assertEquals(1, users.size()); return users.values().iterator().next(); } private void mockResponse(String response, String... urlParts) { wireMock.stubFor( get(urlPathEqualTo( coreApi(urlParts))) .willReturn(aJsonResponse().withBody(response) )); } private void mockEntity(Object entity, String... urlParts) { wireMock.stubFor( get(urlPathEqualTo( coreApi(urlParts))) .willReturn(aJsonResponse(entity) )); } public List getCreatedComments() { return Collections.unmodifiableList(createdComments); } private static String urlPath(String... parts) { return urlPath(true, parts); } private static String coreApi(String... parts) { List apiParts = Arrays.asList("rest", "api", "1.0"); ArrayList args = new ArrayList<>(Arrays.asList(parts)); args.addAll(0, apiParts); return urlPath(args.toArray(new String[] {})); } private static String urlPath(boolean leading, String... parts) { String prefix = ""; if (leading) { prefix = "/"; } return prefix + Joiner.on('/').join(parts); } public static ResponseDefinitionBuilder aJsonResponse() { return aResponse().withHeader("Content-Type", "application/json").withBody("{}"); } public static ResponseDefinitionBuilder aJsonResponse(Object entity) { Gson gson = new Gson(); String body = gson.toJson(entity); return aJsonResponse().withBody(body); } public static WireMockConfiguration extend(WireMockConfiguration options) { return options.extensions(WireMockResponseCallback.getExtension()); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/fixtures/MavenSonarFixtures.java ================================================ package org.sonar.plugins.stash.fixtures; import java.nio.file.FileSystems; import java.nio.file.Path; public class MavenSonarFixtures { protected static SonarQube sonarqube = null; protected static SonarScanner sonarScanner = null; public synchronized static SonarQube getSonarqube(int port) { if (sonarqube == null) { String outputDir = System.getProperty("test.sonarqube.dist.outputdir"); if (outputDir == null) { throw new IllegalArgumentException("Location of unpacked SonarQube is not specified"); } String version = System.getProperty("test.sonarqube.dist.version"); Path installationDir = FileSystems.getDefault().getPath(outputDir, "sonarqube-" + version); sonarqube = new SonarQube(installationDir, version, port); } return sonarqube; } public synchronized static SonarScanner getSonarScanner() { if (sonarScanner == null) { String outputDir = System.getProperty("test.sonarscanner.dist.outputdir"); if (outputDir == null) { throw new IllegalArgumentException("Location of unpacked Sonar Scanner is not specified"); } String version = System.getProperty("test.sonarscanner.dist.version"); Path installationDir = FileSystems.getDefault().getPath(outputDir, "sonar-scanner-" + version); sonarScanner = new SonarScanner(installationDir); } return sonarScanner; } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/fixtures/SonarQube.java ================================================ package org.sonar.plugins.stash.fixtures; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Properties; import java.util.concurrent.TimeUnit; import org.awaitility.Awaitility; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; import org.sonar.plugins.stash.TestUtils; public class SonarQube { private final static Logger LOGGER = LoggerFactory.getLogger(SonarQube.class); protected Path installDir; protected Process process; protected String version; protected Properties config = new Properties(); protected final static String PORT_PROPERTY = "sonar.web.port"; protected final static String HOST_PROPERTY = "sonar.web.host"; public SonarQube(Path installDir, String version, int port) { this.installDir = installDir; this.version = version; config.setProperty(PORT_PROPERTY, String.valueOf(port)); config.setProperty(HOST_PROPERTY, "127.0.0.1"); } public int getPort() { return Integer.parseInt(config.getProperty(PORT_PROPERTY)); } public String getHost() { return config.getProperty(HOST_PROPERTY); } protected File getExecutable() { String os = System.getProperty("os.name"); os = os.toLowerCase(); String arch = System.getProperty("os.arch"); if (arch.equals("amd64")) { arch = "x86-64"; } String binary; if (os.equals("windows")) { binary = "sonar.bat"; } else { binary = "sonar.sh"; } File exec = installDir.resolve("bin").resolve(os + "-" + arch).resolve(binary).toFile(); if (!exec.exists()) { throw new IllegalArgumentException(); } if (!exec.canExecute()) { throw new IllegalArgumentException(); } return exec; } public void setUp() { // noop } public Properties getConfig() { return config; } protected void writeConfig() throws Exception { File configFile = installDir.resolve("conf").resolve("sonar.properties").toFile(); configFile.delete(); try (OutputStream configStream = new FileOutputStream(configFile)) { config.store(configStream, null); } } public void startAsync() throws Exception { writeConfig(); process = TestUtils.createProcess("sonarqube", this.getExecutable().toString(), "start") .directory(installDir.toFile()) .start(); if (process.waitFor() != 0) { throw new Exception(); } } public void stop() throws Exception { TestUtils.createProcess("sonarqube", this.getExecutable().toString(), "stop") .directory(installDir.toFile()) .start().waitFor(); } public void installPlugin(File sourceArchive) throws IOException { if (!sourceArchive.exists()) { throw new IllegalArgumentException(); } Files.copy(sourceArchive.toPath(), installDir.resolve("extensions").resolve("plugins").resolve(sourceArchive.toPath().getFileName()), StandardCopyOption.REPLACE_EXISTING); } public void waitForReady() throws MalformedURLException { LOGGER.info("Waiting for SonarQube to be available at {}", getUrl()); Awaitility .await("SonarQube is ready") .atMost(3, TimeUnit.MINUTES) .pollInterval(5, TimeUnit.SECONDS) .ignoreExceptionsInstanceOf(ConnectException.class) .until(this::isReady) ; } private boolean isReady() throws IOException { HttpURLConnection conn = (HttpURLConnection) getUrl("/api/settings/values.protobuf").openConnection(); conn.connect(); int code = conn.getResponseCode(); return code == 200; } private URL getUrl(String file) throws MalformedURLException { return new URL("http", getHost(), getPort(), file); } public URL getUrl() throws MalformedURLException { return getUrl("/"); } public boolean createProject(String key, String name) throws IOException { URL url = new URL(getUrl(), "/api/projects/create?" + "key=" + URLEncoder.encode(key, "UTF-8") + "&" + "name=" + URLEncoder.encode(key, "UTF-8") ); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setRequestMethod("POST"); conn.connect(); return (conn.getResponseCode() == 200); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/fixtures/SonarQubeRule.java ================================================ package org.sonar.plugins.stash.fixtures; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SonarQubeRule implements BeforeAllCallback, AfterAllCallback { private static final Logger LOGGER = LoggerFactory.getLogger(SonarQubeRule.class); private SonarQube sonarqube; public SonarQubeRule(SonarQube sonarqube) { this.sonarqube = sonarqube; } public void start() throws Exception { sonarqube.startAsync(); sonarqube.waitForReady(); } public SonarQube get() { return sonarqube; } @Override public void beforeAll(ExtensionContext context) throws Exception { sonarqube.setUp(); } @Override public void afterAll(ExtensionContext context) throws Exception { sonarqube.stop(); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/fixtures/SonarScanner.java ================================================ package org.sonar.plugins.stash.fixtures; import com.google.common.base.Joiner; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Properties; import org.sonar.plugins.stash.TestUtils; import org.sonar.plugins.stash.TestUtils.WrappedProcessBuilder; public class SonarScanner { protected Path installDir; public SonarScanner(Path installationDir) { this.installDir = installationDir; } protected Path getBinary(String name) { String ext = ""; if (System.getProperty("os.name").equals("Windows")) { ext = ".bat"; } return installDir.resolve("bin").resolve(name + ext); } private void addCliProperty(List cli, String name, Object value) { cli.add("-D" + name + "=" + value); } public void scan(SonarQube sonarqube, File baseDir, String projectKey, String projectName, String projectVersion, Properties properties) throws IOException, InterruptedException { WrappedProcessBuilder pb = TestUtils.createProcess("sonar-scanner", getBinary("sonar-scanner").toString()) .directory(installDir.toFile()) ; Map env = pb.environment(); env.remove("SONAR_TOKEN"); env.remove("SONARQUBE_SCANNER_PARAMS"); env.remove("SONAR_SCANNER_HOME"); List command = pb.command(); command.add("-e"); addCliProperty(command, "sonar.projectBaseDir", baseDir); addCliProperty(command, "sonar.host.url", sonarqube.getUrl()); addCliProperty(command, "sonar.projectKey", projectKey); addCliProperty(command, "sonar.projectName", projectName); addCliProperty(command, "sonar.projectVersion", projectVersion); addCliProperty(command, "sonar.scm.provider", "git"); if (properties != null) { for (String p : properties.stringPropertyNames()) { addCliProperty(command, p, properties.getProperty(p)); } } Process process = pb.start(); process.waitFor(); if (process.exitValue() != 0) { throw new IOException("Sonar Scanner failed with " + process.exitValue()); } } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/fixtures/WireMockExtension.java ================================================ package org.sonar.plugins.stash.fixtures; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.VerificationException; import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.verification.LoggedRequest; import com.github.tomakehurst.wiremock.verification.NearMiss; import java.util.List; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; // mostly copied from WireMockRule.java public class WireMockExtension extends WireMockServer implements BeforeEachCallback, AfterEachCallback { private final boolean failOnUnmatchedRequests; public WireMockExtension(Options options) { this(options, true); } public WireMockExtension(Options options, boolean failOnUnmatchedRequests) { super(options); this.failOnUnmatchedRequests = failOnUnmatchedRequests; } @Override public void beforeEach(ExtensionContext context) throws Exception { start(); } @Override public void afterEach(ExtensionContext context) throws Exception { checkForUnmatchedRequests(); } private void checkForUnmatchedRequests() { if (failOnUnmatchedRequests) { List unmatchedRequests = findAllUnmatchedRequests(); if (!unmatchedRequests.isEmpty()) { List nearMisses = findNearMissesForAllUnmatchedRequests(); if (nearMisses.isEmpty()) { throw VerificationException.forUnmatchedRequests(unmatchedRequests); } else { throw VerificationException.forUnmatchedNearMisses(nearMisses); } } } } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/fixtures/WireMockResponseCallback.java ================================================ package org.sonar.plugins.stash.fixtures; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.common.FileSource; import com.github.tomakehurst.wiremock.extension.Extension; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.extension.ResponseTransformer; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.Response; public class WireMockResponseCallback { public interface ResponseTransformerCallback { Response transform(Request request, Response response, FileSource files, Parameters parameters); } private static final String WIREMOCK_EXTENSION_NAME = "foo"; private static final String CALLBACK_PARAMETER_NAME = "callback-xxx"; private static final Extension transformer = new DummySQResponseTransformer(); public static Extension getExtension() { return transformer; } public ResponseDefinitionBuilder callback(ResponseDefinitionBuilder rdb, ResponseTransformerCallback callback) { return rdb.withTransformer( WIREMOCK_EXTENSION_NAME, CALLBACK_PARAMETER_NAME, new ToStringWrapper(callback) ); } private static class DummySQResponseTransformer extends ResponseTransformer { @Override public String getName() { return WIREMOCK_EXTENSION_NAME; } @Override public Response transform(Request request, Response response, FileSource files, Parameters parameters) { if (parameters == null) { return response; } Object callback = parameters.get(CALLBACK_PARAMETER_NAME); if (callback == null) { return response; } if (!(callback instanceof ResponseTransformerCallback)) { throw new IllegalStateException(); } return ((ResponseTransformerCallback) callback).transform(request, response, files, parameters); } } private static class ToStringWrapper implements ResponseTransformerCallback { private ResponseTransformerCallback wrapped; private ToStringWrapper( ResponseTransformerCallback wrapped) { this.wrapped = wrapped; } public String getLamba() { return wrapped.getClass().getName(); } @Override public Response transform(Request request, Response response, FileSource files, Parameters parameters) { return wrapped.transform(request, response, files, parameters); } } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/issue/MarkdownPrinterTest.java ================================================ package org.sonar.plugins.stash.issue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.sonar.plugins.stash.TestUtils.inputFile; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.api.batch.rule.Severity; import org.sonar.api.rule.RuleKey; import org.sonar.plugins.stash.DefaultIssue; import org.sonar.plugins.stash.fixtures.DummyIssuePathResolver; public class MarkdownPrinterTest { PostJobIssue issue; List report = new ArrayList<>(); private static final String SONAR_URL = "sonarqube/URL"; private MarkdownPrinter printer; private int issueThreshold; @BeforeEach public void setUp() { PostJobIssue issueBlocker = new DefaultIssue().setKey("key1") .setSeverity(Severity.BLOCKER) .setMessage("messageBlocker") .setRuleKey(RuleKey.of("RepoBlocker", "RuleBlocker")) .setInputComponent(inputFile("foo1", "scripts/file1.example")) .setLine(1); PostJobIssue issueCritical = new DefaultIssue().setKey("key2") .setSeverity(Severity.CRITICAL) .setMessage("messageCritical") .setRuleKey(RuleKey.of("RepoCritical", "RuleCritical")) .setInputComponent(inputFile("foo2", "scripts/file2.example")) .setLine(1); PostJobIssue issueMajor = new DefaultIssue().setKey("key3") .setSeverity(Severity.MAJOR) .setMessage("messageMajor") .setRuleKey(RuleKey.of("RepoMajor", "RuleMajor")) .setInputComponent(inputFile("foo3", "scripts/file3.example")) .setLine(1); PostJobIssue issueSameFile = new DefaultIssue().setKey("key3") .setSeverity(Severity.MAJOR) .setMessage("messageMajor") .setRuleKey(RuleKey.of("RepoMajor", "RuleMajor")) .setInputComponent(inputFile("foo3", "scripts/tests/file3.example")) .setLine(5); PostJobIssue issueSameFileHidden = new DefaultIssue().setKey("key3") .setSeverity(Severity.MAJOR) .setMessage("messageMajor") .setRuleKey(RuleKey.of("RepoMajor", "RuleMajor")) .setInputComponent(inputFile("foo3", "scripts/file3.example")) .setLine(15); report.add(issueBlocker); report.add(issueCritical); report.add(issueMajor); report.add(issueSameFile); report.add(issueSameFileHidden); issue = issueBlocker; issueThreshold = 100; printer = new MarkdownPrinter(issueThreshold, SONAR_URL, 2, new DummyIssuePathResolver()); } @Test public void testPrintIssueMarkdown() { assertEquals( "*BLOCKER* - messageBlocker [[RepoBlocker:RuleBlocker](sonarqube/URL/coding_rules#rule_key=RepoBlocker:RuleBlocker)]", printer.printIssueMarkdown(report.get(0)) ); } @Test public void testPrintIssueMarkdownWithEmptyMessage() { assertEquals( "No message", printer.printIssueMarkdown(new DefaultIssue() .setMessage(null) .setSeverity(Severity.MINOR) .setRuleKey(RuleKey.of("foo", "bar"))) ); } @Test public void testPrintIssueNumberBySeverityMarkdown() { assertEquals( "| BLOCKER | 1 |\n", MarkdownPrinter.printIssueNumberBySeverityMarkdown(report, Severity.BLOCKER) ); assertEquals( "| MAJOR | 3 |\n", MarkdownPrinter.printIssueNumberBySeverityMarkdown(report, Severity.MAJOR) ); assertEquals( "| INFO | 0 |\n", MarkdownPrinter.printIssueNumberBySeverityMarkdown(report, Severity.INFO) ); } @Test public void testPrintIssueNumberBySeverityMarkdownWithNoIssues() { Collection report = new ArrayList<>(); assertEquals("| BLOCKER | 0 |\n", MarkdownPrinter.printIssueNumberBySeverityMarkdown(report, Severity.BLOCKER)); assertEquals("| CRITICAL | 0 |\n", MarkdownPrinter.printIssueNumberBySeverityMarkdown(report, Severity.CRITICAL)); assertEquals("| MAJOR | 0 |\n", MarkdownPrinter.printIssueNumberBySeverityMarkdown(report, Severity.MAJOR)); assertEquals("| MINOR | 0 |\n", MarkdownPrinter.printIssueNumberBySeverityMarkdown(report, Severity.MINOR)); assertEquals("| INFO | 0 |\n", MarkdownPrinter.printIssueNumberBySeverityMarkdown(report, Severity.INFO)); } @Test public void testPrintReportMarkdown() { String issueReportMarkdown = printer.printReportMarkdown(report); String reportString = "## SonarQube analysis Overview\n" + "| Total New Issues | 5 |\n" + "|-----------------|------|\n" + "| BLOCKER | 1 |\n" + "| CRITICAL | 1 |\n" + "| MAJOR | 3 |\n" + "| MINOR | 0 |\n" + "| INFO | 0 |\n\n\n" + "| Issues list |\n" + "|-------------|\n" + "| *BLOCKER* - messageBlocker [[RepoBlocker:RuleBlocker](sonarqube/URL/coding_rules#rule_key=RepoBlocker:RuleBlocker)] |\n" + "|    *Files: scripts/file1.example:1* |\n" + "| *CRITICAL* - messageCritical [[RepoCritical:RuleCritical](sonarqube/URL/coding_rules#rule_key=RepoCritical:RuleCritical)] |\n" + "|    *Files: scripts/file2.example:1* |\n" + "| *MAJOR* - messageMajor [[RepoMajor:RuleMajor](sonarqube/URL/coding_rules#rule_key=RepoMajor:RuleMajor)] |\n" + "|    *Files: scripts/file3.example:1, scripts/file3.example:15, ...* |\n"; assertEquals(reportString, issueReportMarkdown); } @Test public void testPrintReportMarkdownWithIssueLimitation() { printer = new MarkdownPrinter(3, SONAR_URL, 0, new DummyIssuePathResolver()); String issueReportMarkdown = printer.printReportMarkdown(report); String reportString = "## SonarQube analysis Overview\n" + "### Too many issues detected (5/3): Issues cannot be displayed in Diff view.\n\n" + "| Total New Issues | 5 |\n" + "|-----------------|------|\n" + "| BLOCKER | 1 |\n" + "| CRITICAL | 1 |\n" + "| MAJOR | 3 |\n" + "| MINOR | 0 |\n" + "| INFO | 0 |\n\n\n" + "| Issues list |\n" + "|-------------|\n" + "| *BLOCKER* - messageBlocker [[RepoBlocker:RuleBlocker](sonarqube/URL/coding_rules#rule_key=RepoBlocker:RuleBlocker)] |\n" + "| *CRITICAL* - messageCritical [[RepoCritical:RuleCritical](sonarqube/URL/coding_rules#rule_key=RepoCritical:RuleCritical)] |\n" + "| *MAJOR* - messageMajor [[RepoMajor:RuleMajor](sonarqube/URL/coding_rules#rule_key=RepoMajor:RuleMajor)] |\n"; assertEquals(reportString, issueReportMarkdown); } @Test public void testPrintReportMarkdownWithFileWideIssues() { PostJobIssue issueWithoutLine = new DefaultIssue().setKey("key36") .setSeverity(Severity.CRITICAL) .setMessage("messageCritical") .setRuleKey(RuleKey.of("RepoCritical", "RuleCritical")) .setInputComponent(inputFile("foo2", "scripts/file2.example")) .setLine(null); report.add(issueWithoutLine); printer = new MarkdownPrinter(100, SONAR_URL, 100, new DummyIssuePathResolver()); String issueReportMarkdown = printer.printReportMarkdown(report); String reportString = "## SonarQube analysis Overview\n" + "| Total New Issues | 6 |\n" + "|-----------------|------|\n" + "| BLOCKER | 1 |\n" + "| CRITICAL | 2 |\n" + "| MAJOR | 3 |\n" + "| MINOR | 0 |\n" + "| INFO | 0 |\n\n\n" + "| Issues list |\n" + "|-------------|\n" + "| *BLOCKER* - messageBlocker [[RepoBlocker:RuleBlocker](sonarqube/URL/coding_rules#rule_key=RepoBlocker:RuleBlocker)] |\n" + "|    *Files: scripts/file1.example:1* |\n" + "| *CRITICAL* - messageCritical [[RepoCritical:RuleCritical](sonarqube/URL/coding_rules#rule_key=RepoCritical:RuleCritical)] |\n" + "|    *Files: scripts/file2.example, scripts/file2.example:1* |\n" + "| *MAJOR* - messageMajor [[RepoMajor:RuleMajor](sonarqube/URL/coding_rules#rule_key=RepoMajor:RuleMajor)] |\n" + "|    *Files: scripts/file3.example:1, scripts/file3.example:15, scripts/tests/file3.example:5* |\n"; assertEquals(reportString, issueReportMarkdown); } @Test public void testPrintEmptyReportMarkdown() { report = new ArrayList<>(); String issueReportMarkdown = printer.printReportMarkdown(report); String reportString = "## SonarQube analysis Overview\n" + "### No new issues detected!\n\n"; assertEquals(reportString, issueReportMarkdown); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/issue/StashCommentReportTest.java ================================================ package org.sonar.plugins.stash.issue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.sonar.plugins.stash.StashPlugin.IssueType; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class StashCommentReportTest { @Mock StashComment comment1; @Mock StashComment comment2; @BeforeEach public void setUp() { comment1 = mock(StashComment.class); when(comment1.getId()).thenReturn((long)123456); when(comment1.getLine()).thenReturn((long)1); when(comment1.getMessage()).thenReturn("message1"); when(comment1.getPath()).thenReturn("path1"); comment2 = mock(StashComment.class); when(comment2.getId()).thenReturn((long)987654); when(comment2.getLine()).thenReturn((long)2); when(comment2.getMessage()).thenReturn("message2"); when(comment2.getPath()).thenReturn("path2"); } @Test public void testContains() { StashCommentReport report = new StashCommentReport(); report.add(comment1); assertTrue(report.contains("message1", "path1", 1)); report = new StashCommentReport(); report.add(comment2); assertTrue(report.contains("message2", "path2", 2)); assertFalse(report.contains("message3", "path2", 2)); } @Test public void testNotContains() { StashCommentReport report = new StashCommentReport(); report.add(comment1); assertFalse(report.contains("message", "path1", 1)); assertFalse(report.contains("message1", "path", 1)); assertFalse(report.contains("message1", "path1", (long)2)); } @Test public void testSize() { StashCommentReport report = new StashCommentReport(); report.add(comment1); assertEquals(1, report.size()); report.add(comment2); assertEquals(2, report.size()); } @Test public void testSizeOfEmptyReport() { StashCommentReport report = new StashCommentReport(); assertEquals(0, report.size()); } @Test public void testAddReport() { StashCommentReport report1 = new StashCommentReport(); report1.add(comment1); report1.add(comment2); StashComment comment3 = mock(StashComment.class); when(comment3.getLine()).thenReturn((long)3); when(comment3.getMessage()).thenReturn("message3"); when(comment3.getPath()).thenReturn("path3"); StashCommentReport report2 = new StashCommentReport(); report2.add(comment3); report2.add(report1); assertEquals(3, report2.size()); assertTrue(report2.contains("message1", "path1", (long)1)); assertTrue(report2.contains("message2", "path2", (long)2)); assertTrue(report2.contains("message3", "path3", (long)3)); assertFalse(report2.contains("message4", "path4", (long)4)); } @Test public void testAddEmptyReportToNotEmptyReport() { StashCommentReport report1 = new StashCommentReport(); report1.add(comment1); report1.add(comment2); StashCommentReport report2 = new StashCommentReport(); report2.add(report1); assertEquals(2, report2.size()); } @Test public void testAddNotEmptyReportToEmptyReport() { StashCommentReport report1 = new StashCommentReport(); StashCommentReport report2 = new StashCommentReport(); report2.add(comment1); report1.add(report2); assertEquals(1, report1.size()); } @Test public void testAddEmptyReportToEmptyReport() { StashCommentReport report1 = new StashCommentReport(); StashCommentReport report2 = new StashCommentReport(); report1.add(report2); assertEquals(0, report1.size()); } @Test public void applyDiffReportWithCONTEXT() { StashDiff diff = mock(StashDiff.class); when(diff.getType()).thenReturn(IssueType.CONTEXT); when(diff.getDestination()).thenReturn((long)10); StashDiffReport diffReport = mock(StashDiffReport.class); when(diffReport.getDiffByComment(987654)).thenReturn(diff); StashUser stashUser = mock(StashUser.class); comment2 = new StashComment(987654, "message2", "path2", (long)2, stashUser, (long)0); StashCommentReport report = new StashCommentReport(); report.add(comment1); report.add(comment2); report.applyDiffReport(diffReport); assertTrue(report.contains("message1", "path1", 1)); assertTrue(report.contains("message2", "path2", 10)); } @Test public void applyDiffReportWithADDED() { StashDiff diff = mock(StashDiff.class); when(diff.getType()).thenReturn(IssueType.ADDED); when(diff.getDestination()).thenReturn((long)10); StashDiffReport diffReport = mock(StashDiffReport.class); when(diffReport.getDiffByComment(987654)).thenReturn(diff); StashCommentReport report = new StashCommentReport(); report.add(comment1); report.add(comment2); report.applyDiffReport(diffReport); assertTrue(report.contains("message1", "path1", 1)); assertTrue(report.contains("message2", "path2", 2)); assertFalse(report.contains("message2", "path2", 10)); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/issue/StashCommentTest.java ================================================ package org.sonar.plugins.stash.issue; import org.junit.jupiter.api.Test; import org.mockito.Mock; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class StashCommentTest { @Mock StashUser stashUser = mock(StashUser.class); @Test public void testGetLine() { StashComment comment = new StashComment(1, "message", "path", (long)123456, stashUser, 0); assertEquals(123456, comment.getLine()); } @Test public void testGetNoLine() { StashComment comment = new StashComment(1, "message", "path", null, stashUser, 0); assertEquals(0, comment.getLine()); } @Test public void testEquals() { StashComment comment1 = new StashComment(1, "message", "path", (long)1, stashUser, 0); StashComment comment2 = new StashComment(2, "message", "path", (long)1, stashUser, 0); assertNotEquals(comment1, comment2); StashComment comment3 = new StashComment(1, "message", "path", (long)1, stashUser, 0); assertEquals(comment1, comment3); } @Test public void testAddTask() { StashTask task1 = mock(StashTask.class); when(task1.getId()).thenReturn((long)1111); StashTask task2 = mock(StashTask.class); when(task2.getId()).thenReturn((long)2222); StashComment comment = new StashComment(1, "message", "path", (long)1, stashUser, 0); assertEquals(0, comment.getTasks().size()); comment.addTask(task1); assertEquals(1, comment.getTasks().size()); assertEquals(1111, (long) comment.getTasks().get(0).getId()); comment.addTask(task2); assertEquals(2, comment.getTasks().size()); assertEquals(1111, (long) comment.getTasks().get(0).getId()); assertEquals(2222, (long) comment.getTasks().get(1).getId()); } @Test public void testContainsNotDeletableTasks() { StashComment comment = new StashComment(1, "message", "path", (long)1, stashUser, 0); StashTask task1 = mock(StashTask.class); when(task1.isDeletable()).thenReturn(true); comment.addTask(task1); StashTask task2 = mock(StashTask.class); when(task2.isDeletable()).thenReturn(true); comment.addTask(task2); assertFalse(comment.containsPermanentTasks()); StashTask task3 = mock(StashTask.class); when(task3.isDeletable()).thenReturn(false); comment.addTask(task3); assertTrue(comment.containsPermanentTasks()); } @Test public void testContainsNotDeletableTasksWithoutTasks() { StashComment comment = new StashComment(1, "message", "path", (long)1, stashUser, 0); assertFalse(comment.containsPermanentTasks()); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/issue/StashDiffReportTest.java ================================================ package org.sonar.plugins.stash.issue; import com.google.common.collect.Range; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.sonar.plugins.stash.StashPlugin.IssueType; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class StashDiffReportTest { StashDiff diff1; StashDiff diff2; StashDiff diff3; StashDiff diff4; StashDiff diff5; StashDiff diff6; StashDiffReport report1 = new StashDiffReport(); StashDiffReport report2 = new StashDiffReport(); private static final String FILE_PATH = "path/to/diff"; @BeforeEach public void setUp() { StashComment comment1 = mock(StashComment.class); when(comment1.getId()).thenReturn((long)12345); StashComment comment2 = mock(StashComment.class); when(comment2.getId()).thenReturn((long)54321); diff1 = new StashDiff(IssueType.CONTEXT, "path/to/diff1", (long)10, (long)20); diff1.addComment(comment1); diff2 = new StashDiff(IssueType.ADDED, "path/to/diff2", (long)20, (long)30); diff2.addComment(comment2); diff3 = new StashDiff(IssueType.CONTEXT, "path/to/diff3", (long)30, (long)40); report1.add(diff1); report1.add(diff2); report1.add(diff3); diff4 = new StashDiff(IssueType.CONTEXT, FILE_PATH, (long)10, (long)10); diff5 = new StashDiff(IssueType.ADDED, FILE_PATH, (long)11, (long)11); diff6 = new StashDiff(IssueType.CONTEXT, FILE_PATH, (long)11, (long)12); report2.add(diff4); report2.add(diff5); report2.add(diff6); } @Test public void testAdd() { StashDiffReport report = new StashDiffReport(); assertEquals(0, report.getDiffs().size()); report.add(diff1); assertEquals(1, report.getDiffs().size()); StashDiff result1 = report.getDiffs().get(0); assertEquals("path/to/diff1", result1.getPath()); assertEquals(IssueType.CONTEXT, result1.getType()); assertEquals(10, result1.getSource()); assertEquals(20, result1.getDestination()); report.add(diff2); assertEquals(2, report.getDiffs().size()); StashDiff result2 = report.getDiffs().get(1); assertEquals("path/to/diff2", result2.getPath()); assertEquals(IssueType.ADDED, result2.getType()); assertEquals(20, result2.getSource()); assertEquals(30, result2.getDestination()); } @Test public void testAddReport() { assertEquals(3, report1.getDiffs().size()); StashDiffReport report = new StashDiffReport(); assertEquals(0, report.getDiffs().size()); report.add(report1); assertEquals(3, report.getDiffs().size()); } @Test public void testGetType() { assertNull(report1.getType("path/to/diff1", 20, StashDiffReport.VICINITY_RANGE_NONE)); assertEquals(IssueType.ADDED, report1.getType("path/to/diff2", 30, StashDiffReport.VICINITY_RANGE_NONE)); assertNull(report1.getType("path/to/diff2", 20, StashDiffReport.VICINITY_RANGE_NONE)); assertNull(report1.getType("path/to/diff1", 30, StashDiffReport.VICINITY_RANGE_NONE)); assertNull(report1.getType("path/to/diff4", 60, StashDiffReport.VICINITY_RANGE_NONE)); } @Test public void testGetTypeWithNoDestination() { assertEquals(IssueType.CONTEXT, report1.getType("path/to/diff1", 0, StashDiffReport.VICINITY_RANGE_NONE)); assertNull(report1.getType("path/to/diff", 0, StashDiffReport.VICINITY_RANGE_NONE)); } @Test public void testGetTypeVicinity() { assertEquals(IssueType.CONTEXT, report2.getType(FILE_PATH, 10, 1)); assertNull(report2.getType(FILE_PATH, 10, StashDiffReport.VICINITY_RANGE_NONE)); assertEquals(IssueType.ADDED, report2.getType(FILE_PATH, 11, 1)); assertEquals(IssueType.CONTEXT, report2.getType(FILE_PATH, 12, 1)); assertNull(report2.getType(FILE_PATH, 12, StashDiffReport.VICINITY_RANGE_NONE)); } @Test public void testGetLine() { assertEquals(10, report1.getLine("path/to/diff1", 20)); assertEquals(30, report1.getLine("path/to/diff2", 30)); assertEquals(30, report1.getLine("path/to/diff3", 40)); assertEquals(0, report1.getLine("path/to/diff1", 50)); } @Test public void testGetDiffByComment() { StashDiff diff1 = report1.getDiffByComment(12345); assertEquals("path/to/diff1", diff1.getPath()); assertEquals(IssueType.CONTEXT, diff1.getType()); assertEquals(10, diff1.getSource()); assertEquals(20, diff1.getDestination()); StashDiff diff2 = report1.getDiffByComment(123456); assertNull(diff2); } @Test public void testGetComments() { List comments = report1.getComments(); assertEquals(2, comments.size()); assertEquals(12345, comments.get(0).getId()); assertEquals(54321, comments.get(1).getId()); } @Test public void testGetCommentsWithoutAnyIssues() { StashDiffReport report = new StashDiffReport(); List comments = report.getComments(); assertEquals(0, comments.size()); } @Test public void testGetCommentsWithDuplicatedComments() { StashComment comment1 = new StashComment((long)12345, "message", "path", (long)1, mock(StashUser.class), (long)1); diff1.addComment(comment1); StashComment comment2 = new StashComment((long)12345, "message", "path", (long)1, mock(StashUser.class), (long)1); diff2.addComment(comment2); StashComment comment3 = new StashComment((long)54321, "message", "path", (long)1, mock(StashUser.class), (long)1); diff3.addComment(comment3); List comments = report1.getComments(); assertEquals(2, comments.size()); assertEquals(12345, comments.get(0).getId()); assertEquals(54321, comments.get(1).getId()); } @Test public void testVicinitySourceBeforeDestination() { // https://github.com/AmadeusITGroup/sonar-stash/issues/189 final String path = "some/path"; StashDiffReport report = new StashDiffReport(); report.add( new StashDiff(IssueType.REMOVED, path, 165, 132) ); assertEquals( IssueType.CONTEXT, report.getType("some/path", 164, 2) ); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/issue/StashDiffTest.java ================================================ package org.sonar.plugins.stash.issue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.sonar.plugins.stash.StashPlugin.IssueType; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class StashDiffTest { StashDiff diff1; StashDiff diff2; StashDiff diff3; @BeforeEach public void setUp() { StashComment comment1 = mock(StashComment.class); when(comment1.getId()).thenReturn((long)12345); StashComment comment2 = mock(StashComment.class); when(comment2.getId()).thenReturn((long)54321); diff1 = new StashDiff(IssueType.CONTEXT, "path/to/diff1", (long)10, (long)20); diff1.addComment(comment1); diff2 = new StashDiff(IssueType.ADDED, "path/to/diff2", (long)20, (long)30); diff2.addComment(comment2); diff3 = new StashDiff(IssueType.CONTEXT, "path/to/diff3", (long)30, (long)40); } @Test public void testIsTypeOfContext() { assertEquals(IssueType.CONTEXT, diff1.getType()); assertNotEquals(IssueType.CONTEXT, diff2.getType()); } @Test public void testContainsComment() { assertTrue(diff1.containsComment(12345)); assertFalse(diff1.containsComment(54321)); assertFalse(diff3.containsComment(12345)); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/issue/StashPullRequestTest.java ================================================ package org.sonar.plugins.stash.issue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.sonar.plugins.stash.PullRequestRef; public class StashPullRequestTest { StashPullRequest myPullRequest; StashUser stashUser1; StashUser stashUser2; @BeforeEach public void setUp() { PullRequestRef pr = PullRequestRef.builder() .setProject("Project") .setRepository("Repository") .setPullRequestId(123) .build(); myPullRequest = new StashPullRequest(pr); stashUser1 = new StashUser(1, "SonarQube1", "sonarqube1", "sq1@email.com"); stashUser2 = new StashUser(2, "SonarQube2", "sonarqube2", "sq2@email.com"); } @Test public void testGetProject() { assertEquals("Project", myPullRequest.getProject()); } @Test public void testGetRepository() { assertEquals("Repository", myPullRequest.getRepository()); } @Test public void testGetId() { assertEquals(123, myPullRequest.getId()); } @Test public void testGetVersion() { assertEquals(0, myPullRequest.getVersion()); myPullRequest.setVersion(5); assertEquals(5, myPullRequest.getVersion()); } @Test public void testAddReviewer() { assertEquals(0, myPullRequest.getReviewers().size()); myPullRequest.addReviewer(stashUser1); assertEquals(1, myPullRequest.getReviewers().size()); assertEquals(1, myPullRequest.getReviewers().get(0).getId()); } @Test public void testAddReviewerTwice() { assertEquals(0, myPullRequest.getReviewers().size()); myPullRequest.addReviewer(stashUser1); myPullRequest.addReviewer(stashUser2); assertEquals(2, myPullRequest.getReviewers().size()); assertEquals(1, myPullRequest.getReviewers().get(0).getId()); assertEquals(2, myPullRequest.getReviewers().get(1).getId()); } @Test public void testAddReviewerSameTwice() { assertEquals(0, myPullRequest.getReviewers().size()); myPullRequest.addReviewer(stashUser1); myPullRequest.addReviewer(stashUser1); assertEquals(2, myPullRequest.getReviewers().size()); assertEquals(1, myPullRequest.getReviewers().get(0).getId()); assertEquals(1, myPullRequest.getReviewers().get(1).getId()); } @Test public void testGetReviewer() { assertEquals(0, myPullRequest.getReviewers().size()); assertNull(myPullRequest.getReviewer("sonarqube1")); assertNull(myPullRequest.getReviewer("sonarqube2")); myPullRequest.addReviewer(stashUser1); assertEquals(1, myPullRequest.getReviewers().size()); assertEquals(1, myPullRequest.getReviewer("sonarqube1").getId()); assertNull(myPullRequest.getReviewer("sonarqube2")); myPullRequest.addReviewer(stashUser2); assertEquals(2, myPullRequest.getReviewers().size()); assertEquals(1, myPullRequest.getReviewer("sonarqube1").getId()); assertEquals(2, myPullRequest.getReviewer("sonarqube2").getId()); } @Test public void testContainsReviewer() { assertEquals(0, myPullRequest.getReviewers().size()); assertFalse(myPullRequest.containsReviewer(stashUser1)); assertFalse(myPullRequest.containsReviewer(stashUser2)); myPullRequest.addReviewer(stashUser1); assertEquals(1, myPullRequest.getReviewers().size()); assertTrue(myPullRequest.containsReviewer(stashUser1)); assertFalse(myPullRequest.containsReviewer(stashUser2)); myPullRequest.addReviewer(stashUser2); assertEquals(2, myPullRequest.getReviewers().size()); assertTrue(myPullRequest.containsReviewer(stashUser1)); assertTrue(myPullRequest.containsReviewer(stashUser2)); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/issue/StashTaskTest.java ================================================ package org.sonar.plugins.stash.issue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class StashTaskTest { StashTask myTask; @BeforeEach public void setUp() { myTask = new StashTask((long)1111, "Text", "State", true); } @Test public void testGetId() { assertEquals(1111, (long)myTask.getId()); } @Test public void testGetState() { assertEquals("State", myTask.getState()); } @Test public void testGetText() { assertEquals("Text", myTask.getText()); } @Test public void testIsDeletable() { assertTrue(myTask.isDeletable()); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/issue/StashUserTest.java ================================================ package org.sonar.plugins.stash.issue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class StashUserTest { StashUser myUser; @BeforeEach public void setUp() { myUser = new StashUser(1, "SonarQube", "sonarqube", "sq@email.com"); } @Test public void testGetId() { assertEquals(1, myUser.getId()); } @Test public void testGetName() { assertEquals("SonarQube", myUser.getName()); } @Test public void testGetSlug() { assertEquals("sonarqube", myUser.getSlug()); } @Test public void testGetEmail() { assertEquals("sq@email.com", myUser.getEmail()); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/issue/collector/DiffReportSample.java ================================================ package org.sonar.plugins.stash.issue.collector; public class DiffReportSample { public static String baseReport = "{\"diffs\": [" + " {" + " \"source\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"destination\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"hunks\": [" + " {" + " \"segments\": [" + " {" + " \"type\": \"CONTEXT\"," + " \"lines\": [" + " {" + " \"source\": 10," + " \"destination\": 20," + " \"line\": \"System.out.println(test);\"," + " \"commentIds\": [" + " 12345" + " ]" + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 20," + " \"destination\": 30," + " }" + " ]," + " }," + " {" + " \"type\": \"ADDED\"," + " \"lines\": [" + " {" + " \"source\": 30," + " \"destination\": 40," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " {" + " \"type\": \"CONTEXT\"," + " \"lines\": [" + " {" + " \"source\": 40," + " \"destination\": 50," + " \"line\": \"System.out.println(test);\"," + " \"commentIds\": [" + " 54321" + " ]" + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 50," + " \"destination\": 60," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " {" + " \"type\": \"ADDED\"," + " \"lines\": [" + " {" + " \"source\": 60," + " \"destination\": 70," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " ]," + " }" + " ]," + " \"lineComments\": [" + " {" + " \"id\": 12345," + " \"text\": \"Test comment\"," + " \"version\": 1," + " \"author\": " + " {" + " \"id\": 12345," + " \"name\": \"SonarQube\"," + " \"slug\": \"sonarqube\"," + " \"email\": \"sq@email.com\"," + " }," + " \"tasks\": [" + " {" + " \"id\": 12345," + " \"text\": \"Complete the task associated to this TODO comment.\"," + " \"state\": \"OPENED\"," + " \"permittedOperations\": {" + " \"deletable\": true" + " }" + " }," + " {" + " \"id\": 54321," + " \"text\": \"Complete the task associated to this TODO comment.\"," + " \"state\": \"OPENED\"," + " \"permittedOperations\": {" + " \"deletable\": true" + " }" + " }]" + " }," + " {" + " \"id\": 54321," + " \"text\": \"Test comment 2\"," + " \"version\": 1," + " \"author\": " + " {" + " \"id\": 54321," + " \"name\": \"SonarQube2\"," + " \"slug\": \"sonarqube2\"," + " \"email\": \"sq2@email.com\"," + " }," + " }" + " ]" + " }" + " ]" + "}"; public static String baseReportWithMalformedTasks = "{\"diffs\": [" + " {" + " \"source\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"destination\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"hunks\": [" + " {" + " \"segments\": [" + " {" + " \"type\": \"CONTEXT\"," + " \"lines\": [" + " {" + " \"source\": 10," + " \"destination\": 20," + " \"line\": \"System.out.println(test);\"," + " \"commentIds\": [" + " 12345" + " ]" + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 20," + " \"destination\": 30," + " }" + " ]," + " }," + " {" + " \"type\": \"ADDED\"," + " \"lines\": [" + " {" + " \"source\": 30," + " \"destination\": 40," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " {" + " \"type\": \"CONTEXT\"," + " \"lines\": [" + " {" + " \"source\": 40," + " \"destination\": 50," + " \"line\": \"System.out.println(test);\"," + " \"commentIds\": [" + " 54321" + " ]" + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 50," + " \"destination\": 60," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " {" + " \"type\": \"ADDED\"," + " \"lines\": [" + " {" + " \"source\": 60," + " \"destination\": 70," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " ]," + " }" + " ]," + " \"lineComments\": [" + " {" + " \"id\": 12345," + " \"text\": \"Test comment\"," + " \"version\": 1," + " \"author\": " + " {" + " \"id\": 12345," + " }," + " \"tasks\": [" + " {" + " \"id\": 12345" + " \"text\": \"Complete the task associated to this TODO comment.\"" + " \"state\": \"OPENED\"," + " }," + " \"permittedOperations\" {" + " \"deletable\": true" + " }" + " }]" + " }," + " {" + " \"id\": 54321," + " \"text\": \"Test comment 2\"," + " \"version\": 1," + " \"author\": " + " {" + " \"id\": 54321," + " }," + " }" + " ]" + " }" + " ]" + "}"; public static String baseReportWithFileComments = "{\"diffs\": [" + " {" + " \"source\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"destination\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"hunks\": [" + " {" + " \"segments\": [" + " {" + " \"type\": \"CONTEXT\"," + " \"lines\": [" + " {" + " \"source\": 10," + " \"destination\": 20," + " \"line\": \"System.out.println(test);\"," + " \"commentIds\": [" + " 12345" + " ]" + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 20," + " \"destination\": 30," + " }" + " ]," + " }," + " {" + " \"type\": \"ADDED\"," + " \"lines\": [" + " {" + " \"source\": 30," + " \"destination\": 40," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " {" + " \"type\": \"CONTEXT\"," + " \"lines\": [" + " {" + " \"source\": 40," + " \"destination\": 50," + " \"line\": \"System.out.println(test);\"," + " \"commentIds\": [" + " 54321" + " ]" + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 50," + " \"destination\": 60," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " {" + " \"type\": \"ADDED\"," + " \"lines\": [" + " {" + " \"source\": 60," + " \"destination\": 70," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " ]," + " }" + " ]," + " \"lineComments\": [" + " {" + " \"id\": 12345," + " \"text\": \"Test comment\"," + " \"version\": 1," + " \"author\": " + " {" + " \"id\": 12345," + " \"name\": \"SonarQube\"," + " \"slug\": \"sonarqube\"," + " \"email\": \"sq@email.com\"," + " }," + " }," + " {" + " \"id\": 54321," + " \"text\": \"Test comment 2\"," + " \"version\": 1," + " \"author\": " + " {" + " \"id\": 54321," + " \"name\": \"SonarQube2\"," + " \"slug\": \"sonarqube2\"," + " \"email\": \"sq2@email.com\"," + " }," + " }" + " ]" + " \"fileComments\": [" + " {" + " \"id\": 123456," + " \"text\": \"Test File comment\"," + " \"version\": 1," + " \"author\": " + " {" + " \"id\": 12345," + " \"name\": \"SonarQube\"," + " \"slug\": \"sonarqube\"," + " \"email\": \"sq@email.com\"," + " }," + " }," + " {" + " \"id\": 654321," + " \"text\": \"Test File comment 2\"," + " \"version\": 1," + " \"author\": " + " {" + " \"id\": 54321," + " \"name\": \"SonarQube2\"," + " \"slug\": \"sonarqube2\"," + " \"email\": \"sq2@email.com\"," + " }," + " }" + " ]" + " }" + " ]" + "}"; public static String baseReportWithEmptyFileComments = "{\"diffs\": [" + " {" + " \"source\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"destination\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"hunks\": [" + " {" + " \"segments\": [" + " {" + " \"type\": \"CONTEXT\"," + " \"lines\": [" + " {" + " \"source\": 10," + " \"destination\": 20," + " \"line\": \"System.out.println(test);\"," + " \"commentIds\": [" + " 12345" + " ]" + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 20," + " \"destination\": 30," + " }" + " ]," + " }," + " {" + " \"type\": \"ADDED\"," + " \"lines\": [" + " {" + " \"source\": 30," + " \"destination\": 40," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " {" + " \"type\": \"CONTEXT\"," + " \"lines\": [" + " {" + " \"source\": 40," + " \"destination\": 50," + " \"line\": \"System.out.println(test);\"," + " \"commentIds\": [" + " 54321" + " ]" + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 50," + " \"destination\": 60," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " {" + " \"type\": \"ADDED\"," + " \"lines\": [" + " {" + " \"source\": 60," + " \"destination\": 70," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " ]," + " }" + " ]," + " \"lineComments\": [" + " {" + " \"id\": 12345," + " \"text\": \"Test comment\"," + " \"version\": 1," + " \"author\": " + " {" + " \"id\": 12345," + " \"name\": \"SonarQube\"," + " \"slug\": \"sonarqube\"," + " \"email\": \"sq@email.com\"," + " }," + " }," + " {" + " \"id\": 54321," + " \"text\": \"Test comment 2\"," + " \"version\": 1," + " \"author\": " + " {" + " \"id\": 54321," + " \"name\": \"SonarQube2\"," + " \"slug\": \"sonarqube2\"," + " \"email\": \"sq2@email.com\"," + " }," + " }" + " ]" + " \"fileComments\": []" + " }" + " ]" + "}"; public static String emptyReport = "{\"diffs\": [" + " {" + " \"source\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"destination\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"hunks\": []," + " \"lineComments\": []" + " }" + " ]" + "}"; public static String multipleFileReport = "{\"diffs\": [" + " {" + " \"source\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"destination\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"hunks\": [" + " {" + " \"segments\": [" + " {" + " \"type\": \"CONTEXT\"," + " \"lines\": [" + " {" + " \"source\": 10," + " \"destination\": 20," + " \"line\": \"System.out.println(test);\"," + " \"commentIds\": [" + " 12345" + " ]" + " }" + " ]," + " }" + " ]," + " }" + " ]," + " \"lineComments\": [" + " {" + " \"id\": 12345," + " \"text\": \"Test comment\"," + " \"version\": 1," + " \"author\": " + " {" + " \"id\": 12345," + " \"name\": \"SonarQube\"," + " \"slug\": \"sonarqube\"," + " \"email\": \"sq@email.com\"," + " }," + " }" + " ]" + " }," + " {" + " \"source\": {" + " \"components\": [" + " \"Test1.java\"" + " ]," + " \"toString\": \"stash-plugin/Test1.java\"" + " }," + " \"destination\": {" + " \"components\": [" + " \"Test1.java\"" + " ]," + " \"toString\": \"stash-plugin/Test1.java\"" + " }," + " \"hunks\": [" + " {" + " \"segments\": [" + " {" + " \"type\": \"ADDED\"," + " \"lines\": [" + " {" + " \"source\": 20," + " \"destination\": 30," + " \"line\": \"System.out.println(test);\"," + " \"commentIds\": []" + " }" + " ]," + " }" + " ]," + " }" + " ]," + " \"lineComments\": []" + " }" + " ]" + "}"; public static String baseReportWithNoComments = "{\"diffs\": [" + " {" + " \"source\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"destination\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"hunks\": [" + " {" + " \"segments\": [" + " {" + " \"type\": \"CONTEXT\"," + " \"lines\": [" + " {" + " \"source\": 10," + " \"destination\": 20," + " \"line\": \"System.out.println(test);\"" + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 20," + " \"destination\": 30," + " }" + " ]," + " }," + " {" + " \"type\": \"ADDED\"," + " \"lines\": [" + " {" + " \"source\": 30," + " \"destination\": 40," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " {" + " \"type\": \"CONTEXT\"," + " \"lines\": [" + " {" + " \"source\": 40," + " \"destination\": 50," + " \"line\": \"System.out.println(test);\"" + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 50," + " \"destination\": 60," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " {" + " \"type\": \"ADDED\"," + " \"lines\": [" + " {" + " \"source\": 60," + " \"destination\": 70," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " ]," + " }" + " ]," + " \"lineComments\": []" + " }" + " ]" + "}"; public static String deletedFileReport = "{\"diffs\": [" + " {" + " \"source\": {" + " \"components\": [" + " \"Test.java\"" + " ]," + " \"toString\": \"stash-plugin/Test.java\"" + " }," + " \"destination\": null," + " \"hunks\": [" + " {" + " \"segments\": [" + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 1," + " \"destination\": 0," + " \"line\": \"System.out.println(test);\"" + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 2," + " \"destination\": 0," + " \"line\": \"System.out.println(test);\"" + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 3," + " \"destination\": 0," + " \"line\": \"System.out.println(test);\"" + " }" + " ]," + " }," + " ]," + " }" + " ]," + " }" + " {" + " \"source\": {" + " \"components\": [" + " \"Test2.java\"" + " ]," + " \"toString\": \"stash-plugin/Test2.java\"" + " }," + " \"destination\": {" + " \"components\": [" + " \"Test2.java\"" + " ]," + " \"toString\": \"stash-plugin/Test2.java\"" + " }," + " \"hunks\": [" + " {" + " \"segments\": [" + " {" + " \"type\": \"CONTEXT\"," + " \"lines\": [" + " {" + " \"source\": 10," + " \"destination\": 20," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " {" + " \"type\": \"REMOVED\"," + " \"lines\": [" + " {" + " \"source\": 20," + " \"destination\": 30," + " }" + " ]," + " }," + " {" + " \"type\": \"ADDED\"," + " \"lines\": [" + " {" + " \"source\": 30," + " \"destination\": 40," + " \"line\": \"System.out.println(test);\"," + " }" + " ]," + " }," + " ]," + " }" + " ]," + " }" + " ]" + "}"; } ================================================ FILE: src/test/java/org/sonar/plugins/stash/issue/collector/SonarQubeCollectorTest.java ================================================ package org.sonar.plugins.stash.issue.collector; import java.util.HashSet; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.sonar.plugins.stash.StashPluginUtils.countIssuesBySeverity; import static org.sonar.plugins.stash.TestUtils.inputFile; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.sonar.api.batch.fs.InputComponent; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.postjob.issue.PostJobIssue; import org.sonar.api.batch.rule.Severity; import org.sonar.api.rule.RuleKey; import org.sonar.plugins.stash.DefaultIssue; import org.sonar.plugins.stash.IssuePathResolver; import org.sonar.plugins.stash.fixtures.DummyIssuePathResolver; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) public class SonarQubeCollectorTest { @Mock DefaultIssue issue1; @Mock DefaultIssue issue2; @Mock InputFile inputFile1; @Mock InputFile inputFile2; Set excludedRules; IssuePathResolver ipr = new DummyIssuePathResolver(); @BeforeEach public void setUp() throws Exception { ///////// SonarQube issues ///////// when(issue1.line()).thenReturn(1); when(issue1.message()).thenReturn("message1"); when(issue1.key()).thenReturn("key1"); when(issue1.severity()).thenReturn(Severity.BLOCKER); when(issue1.componentKey()).thenReturn("module1:component1"); when(issue1.isNew()).thenReturn(true); when(issue1.inputComponent()).thenReturn(inputFile("module1", "file1")); RuleKey rule1 = mock(RuleKey.class); when(rule1.toString()).thenReturn("rule1"); when(issue1.ruleKey()).thenReturn(rule1); when(issue2.line()).thenReturn(2); when(issue2.message()).thenReturn("message2"); when(issue2.key()).thenReturn("key2"); when(issue2.severity()).thenReturn(Severity.CRITICAL); when(issue2.componentKey()).thenReturn("module2:component2"); when(issue2.isNew()).thenReturn(true); when(issue2.inputComponent()).thenReturn(inputFile("module2", "file2")); RuleKey rule2 = mock(RuleKey.class); when(rule2.toString()).thenReturn("rule2"); when(issue2.ruleKey()).thenReturn(rule2); inputFile1 = inputFile("module1", "project/path1"); inputFile2 = inputFile("module2", "project/path2"); when(issue1.inputComponent()).thenReturn(inputFile1); when(issue2.inputComponent()).thenReturn(inputFile2); excludedRules = new HashSet<>(); } @Test public void testExtractEmptyIssueReport() { ArrayList issues = new ArrayList<>(); List report = SonarQubeCollector.extractIssueReport(issues, ipr, false, excludedRules); assertEquals(0, report.size()); } @Test public void testExtractIssueReport() { ArrayList issues = new ArrayList<>(); issues.add(issue1); issues.add(issue2); List report = SonarQubeCollector.extractIssueReport(issues, ipr, false, excludedRules); assertEquals(2, report.size()); assertEquals(1, countIssuesBySeverity(report, Severity.BLOCKER)); assertEquals(1, countIssuesBySeverity(report, Severity.CRITICAL)); PostJobIssue sqIssue1 = report.get(0); assertEquals("message1", sqIssue1.message()); assertEquals("project/path1", ipr.getIssuePath(sqIssue1)); assertEquals((Integer) 1, sqIssue1.line()); PostJobIssue sqIssue2 = report.get(1); assertEquals("message2", sqIssue2.message()); assertEquals("project/path2", ipr.getIssuePath(sqIssue2)); assertEquals((Integer) 2, sqIssue2.line()); } @Test public void testExtractIssueReportWithNoLine() { when(issue1.line()).thenReturn(null); ArrayList issues = new ArrayList<>(); issues.add(issue1); List report = SonarQubeCollector.extractIssueReport(issues, ipr, false, excludedRules); assertEquals(1, report.size()); assertEquals(1, countIssuesBySeverity(report, Severity.BLOCKER)); assertEquals(0, countIssuesBySeverity(report, Severity.CRITICAL)); PostJobIssue sqIssue1 = report.get(0); assertEquals("message1", sqIssue1.message()); assertEquals("project/path1", ipr.getIssuePath(sqIssue1)); assertNull(sqIssue1.line()); } @Test public void testExtractIssueReportWithOldOption() { when(issue1.isNew()).thenReturn(false); when(issue2.isNew()).thenReturn(true); ArrayList issues = new ArrayList<>(); issues.add(issue1); issues.add(issue2); List report = SonarQubeCollector.extractIssueReport(issues, ipr, false, excludedRules); assertEquals(1, report.size()); assertEquals(0, countIssuesBySeverity(report, Severity.BLOCKER)); assertEquals(1, countIssuesBySeverity(report, Severity.CRITICAL)); } @Test public void testExtractIssueReportWithOneIssueWithoutInputFile() { when(issue1.inputComponent()).thenReturn(null); ArrayList issues = new ArrayList<>(); issues.add(issue1); issues.add(issue2); List report = SonarQubeCollector.extractIssueReport(issues, ipr, false, excludedRules); assertEquals(1, report.size()); assertEquals(0, countIssuesBySeverity(report, Severity.BLOCKER)); assertEquals(1, countIssuesBySeverity(report, Severity.CRITICAL)); PostJobIssue sqIssue2 = report.get(0); assertEquals("message2", sqIssue2.message()); assertEquals("project/path2", ipr.getIssuePath(sqIssue2)); assertEquals((Integer) 2, sqIssue2.line()); } @Test public void testExtractIssueReportWithIncludeExistingIssuesOption() { when(issue1.isNew()).thenReturn(false); when(issue2.isNew()).thenReturn(true); ArrayList issues = new ArrayList<>(); issues.add(issue1); issues.add(issue2); List report = SonarQubeCollector.extractIssueReport(issues, ipr, true, excludedRules); assertEquals(2, report.size()); } @Test public void testExtractIssueReportWithExcludedRules() { when(issue1.ruleKey()).thenReturn(RuleKey.of("foo", "bar")); when(issue2.ruleKey()).thenReturn(RuleKey.of("foo", "baz")); ArrayList issues = new ArrayList<>(); issues.add(issue1); issues.add(issue2); excludedRules.add(RuleKey.of("foo", "bar")); List report = SonarQubeCollector.extractIssueReport(issues, ipr, true, excludedRules); assertEquals(1, report.size()); assertEquals("key2", report.get(0).key()); } @Test public void testShouldIncludeIssue() { Set er = new HashSet<>(); InputComponent ic = inputFile("module", "some/path"); assertFalse( SonarQubeCollector.shouldIncludeIssue( new DefaultIssue().setNew(false).setInputComponent(ic), ipr, false, er ) ); assertTrue( SonarQubeCollector.shouldIncludeIssue( new DefaultIssue().setNew(false).setInputComponent(ic), ipr, true, er ) ); assertTrue( SonarQubeCollector.shouldIncludeIssue( new DefaultIssue().setNew(true).setInputComponent(ic), ipr, false, er ) ); assertTrue( SonarQubeCollector.shouldIncludeIssue( new DefaultIssue().setNew(true).setInputComponent(ic), ipr, true, er ) ); } } ================================================ FILE: src/test/java/org/sonar/plugins/stash/issue/collector/StashCollectorTest.java ================================================ package org.sonar.plugins.stash.issue.collector; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.github.cliftonlabs.json_simple.JsonObject; import com.github.cliftonlabs.json_simple.Jsoner; import org.junit.jupiter.api.Test; import org.sonar.plugins.stash.PullRequestRef; import org.sonar.plugins.stash.StashPlugin.IssueType; import org.sonar.plugins.stash.exceptions.StashReportExtractionException; import org.sonar.plugins.stash.issue.StashComment; import org.sonar.plugins.stash.issue.StashCommentReport; import org.sonar.plugins.stash.issue.StashDiff; import org.sonar.plugins.stash.issue.StashDiffReport; import org.sonar.plugins.stash.issue.StashPullRequest; import org.sonar.plugins.stash.issue.StashTask; import org.sonar.plugins.stash.issue.StashUser; public class StashCollectorTest { private static final long STASH_USER_ID = 1; PullRequestRef pr = PullRequestRef.builder() .setProject("project") .setRepository("repository") .setPullRequestId(123) .build(); @Test public void testExtractCommentReport() throws Exception { String commentString = "{\"values\": [{\"id\":1234, \"text\":\"message\", \"anchor\": {\"path\":\"path\", \"line\":5}," + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\":0}]}"; StashCommentReport commentReport = StashCollector.extractComments(parse(commentString)); assertEquals(1, commentReport.size()); StashComment comment = commentReport.getComments().get(0); assertEquals(1234, comment.getId()); assertEquals("message", comment.getMessage()); assertEquals("path", comment.getPath()); assertEquals(0, comment.getVersion()); assertEquals(STASH_USER_ID, comment.getAuthor().getId()); assertEquals(5, comment.getLine()); } @Test public void testExtractCommentReportWithSeveralComment() throws Exception { String commentString = "{\"values\": [" + "{\"id\":1234, \"text\":\"message1\", \"anchor\": {\"path\":\"path1\", \"line\":1}," + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\":1}, " + "{\"id\":5678, \"text\":\"message2\", \"anchor\": {\"path\":\"path2\", \"line\":2}," + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\":2}]}"; StashCommentReport commentReport = StashCollector.extractComments(parse(commentString)); assertEquals(2, commentReport.size()); StashComment comment1 = commentReport.getComments().get(0); assertEquals(1234, comment1.getId()); assertEquals("message1", comment1.getMessage()); assertEquals("path1", comment1.getPath()); assertEquals(1, comment1.getVersion()); assertEquals(STASH_USER_ID, comment1.getAuthor().getId()); assertEquals(1, comment1.getLine()); StashComment comment2 = commentReport.getComments().get(1); assertEquals(5678, comment2.getId()); assertEquals("message2", comment2.getMessage()); assertEquals("path2", comment2.getPath()); assertEquals(2, comment2.getVersion()); assertEquals(STASH_USER_ID, comment2.getAuthor().getId()); assertEquals(2, comment2.getLine()); } @Test public void testExtractEmptyCommentReport() throws Exception { String commentString = "{\"values\": []}"; StashCommentReport commentReport = StashCollector.extractComments(parse(commentString)); assertEquals(0, commentReport.size()); } @Test public void testExtractComment() throws Exception { String commentString = "{\"id\":1234, \"text\":\"message\", \"anchor\": {\"path\":\"path\", \"line\":5}," + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\":0}"; StashComment comment = StashCollector.extractComment(parse(commentString)); assertEquals(1234, comment.getId()); assertEquals("message", comment.getMessage()); assertEquals("path", comment.getPath()); assertEquals(0, comment.getVersion()); assertEquals(STASH_USER_ID, comment.getAuthor().getId()); assertEquals(5, comment.getLine()); } @Test public void testExtractEmptyCommentWithNoAnchor() throws Exception { String commentString = "{\"id\":1234, \"text\":\"message\", " + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\":0}"; assertThrows(StashReportExtractionException.class, () -> StashCollector.extractComment(parse(commentString)) ); } @Test public void testExtractCommentWithPathAndLineAsParameters() throws Exception { String commentString = "{\"id\":1234, \"text\":\"message\", \"anchor\": {\"path\":\"path\", \"line\":5}," + "\"author\": {\"id\":1, \"name\":\"SonarQube\", \"slug\":\"sonarqube\", \"email\":\"sq@email.com\"}, \"version\":0}"; StashComment comment = StashCollector.extractComment(parse(commentString), "pathAsParameter", (long)1111); assertEquals(1234, comment.getId()); assertEquals("message", comment.getMessage()); assertEquals("pathAsParameter", comment.getPath()); assertEquals(0, comment.getVersion()); assertEquals(STASH_USER_ID, comment.getAuthor().getId()); assertEquals(1111, comment.getLine()); } @Test public void testIsLastPage() throws Exception { String jsonBody = "{\"isLastPage\": true}"; assertTrue(StashCollector.isLastPage(parse(jsonBody))); jsonBody = "{\"isLastPage\": false}"; assertFalse(StashCollector.isLastPage(parse(jsonBody))); jsonBody = "{\"values\": []}"; assertTrue(StashCollector.isLastPage(parse(jsonBody))); } @Test public void testNextPageStart() throws Exception { String jsonBody = "{\"nextPageStart\": 3}"; assertEquals(3, StashCollector.getNextPageStart(parse(jsonBody))); jsonBody = "{\"values\": []}"; assertEquals(0, StashCollector.getNextPageStart(parse(jsonBody))); } @Test public void testExtractDiffsWithBaseReport() throws Exception { StashDiffReport report = StashCollector.extractDiffs(parse(DiffReportSample.baseReport)); assertEquals(4, report.getDiffs().size()); StashDiff diff1 = report.getDiffs().get(0); assertEquals((long)10, diff1.getSource()); assertEquals((long)20, diff1.getDestination()); assertEquals("stash-plugin/Test.java", diff1.getPath()); assertEquals(IssueType.CONTEXT, diff1.getType()); assertEquals(1, diff1.getComments().size()); assertTrue(diff1.containsComment(12345)); assertFalse(diff1.containsComment(54321)); StashComment comment1 = diff1.getComments().get(0); assertEquals(12345, comment1.getId()); assertEquals("Test comment", comment1.getMessage()); assertEquals(1, comment1.getVersion()); StashTask task1 = comment1.getTasks().get(0); assertEquals(12345, (long)task1.getId()); assertEquals("Complete the task associated to this TODO comment.", task1.getText()); assertEquals("OPENED", task1.getState()); StashTask task2 = comment1.getTasks().get(1); assertEquals(54321, (long)task2.getId()); assertEquals("Complete the task associated to this TODO comment.", task2.getText()); assertEquals("OPENED", task2.getState()); StashUser author1 = comment1.getAuthor(); assertEquals(12345, author1.getId()); assertEquals("SonarQube", author1.getName()); assertEquals("sonarqube", author1.getSlug()); assertEquals("sq@email.com", author1.getEmail()); StashDiff diff2 = report.getDiffs().get(1); assertEquals((long)30, diff2.getSource()); assertEquals((long)40, diff2.getDestination()); assertEquals("stash-plugin/Test.java", diff2.getPath()); assertEquals(IssueType.ADDED, diff2.getType()); assertEquals(0, diff2.getComments().size()); assertFalse(diff2.containsComment(12345)); assertFalse(diff2.containsComment(54321)); StashDiff diff3 = report.getDiffs().get(2); assertEquals((long)40, diff3.getSource()); assertEquals((long)50, diff3.getDestination()); assertEquals("stash-plugin/Test.java", diff3.getPath()); assertEquals(IssueType.CONTEXT, diff3.getType()); assertEquals(1, diff3.getComments().size()); assertFalse(diff3.containsComment(12345)); assertTrue(diff3.containsComment(54321)); StashComment comment2 = diff3.getComments().get(0); assertEquals(54321, comment2.getId()); assertEquals("Test comment 2", comment2.getMessage()); assertEquals(1, comment2.getVersion()); StashUser author2 = comment2.getAuthor(); assertEquals(54321, author2.getId()); assertEquals("SonarQube2", author2.getName()); assertEquals("sonarqube2", author2.getSlug()); assertEquals("sq2@email.com", author2.getEmail()); StashDiff diff4 = report.getDiffs().get(3); assertEquals((long)60, diff4.getSource()); assertEquals((long)70, diff4.getDestination()); assertEquals("stash-plugin/Test.java", diff4.getPath()); assertEquals(IssueType.ADDED, diff4.getType()); assertEquals(0, diff4.getComments().size()); assertFalse(diff4.containsComment(12345)); assertFalse(diff4.containsComment(54321)); } @Test public void testExtractDiffsWithNoComments() throws Exception { StashDiffReport report = StashCollector.extractDiffs(parse(DiffReportSample.baseReportWithNoComments)); assertEquals(4, report.getDiffs().size()); StashDiff diff1 = report.getDiffs().get(0); assertEquals((long)10, diff1.getSource()); assertEquals((long)20, diff1.getDestination()); assertEquals("stash-plugin/Test.java", diff1.getPath()); assertEquals(IssueType.CONTEXT, diff1.getType()); assertEquals(0, diff1.getComments().size()); StashDiff diff2 = report.getDiffs().get(1); assertEquals((long)30, diff2.getSource()); assertEquals((long)40, diff2.getDestination()); assertEquals("stash-plugin/Test.java", diff2.getPath()); assertEquals(IssueType.ADDED, diff2.getType()); assertEquals(0, diff2.getComments().size()); StashDiff diff3 = report.getDiffs().get(2); assertEquals((long)40, diff3.getSource()); assertEquals((long)50, diff3.getDestination()); assertEquals("stash-plugin/Test.java", diff3.getPath()); assertEquals(IssueType.CONTEXT, diff3.getType()); assertEquals(0, diff3.getComments().size()); StashDiff diff4 = report.getDiffs().get(3); assertEquals((long)60, diff4.getSource()); assertEquals((long)70, diff4.getDestination()); assertEquals("stash-plugin/Test.java", diff4.getPath()); assertEquals(IssueType.ADDED, diff4.getType()); assertEquals(0, diff4.getComments().size()); } @Test public void testExtractDiffsWithFileComments() throws Exception { StashDiffReport report = StashCollector.extractDiffs(parse(DiffReportSample.baseReportWithFileComments)); assertEquals(5, report.getDiffs().size()); StashDiff diff1 = report.getDiffs().get(0); assertEquals(1, diff1.getComments().size()); assertTrue(diff1.containsComment(12345)); assertFalse(diff1.containsComment(54321)); StashComment comment1 = diff1.getComments().get(0); assertEquals(12345, comment1.getId()); assertEquals("Test comment", comment1.getMessage()); assertEquals(1, comment1.getVersion()); StashUser author1 = comment1.getAuthor(); assertEquals(12345, author1.getId()); assertEquals("SonarQube", author1.getName()); assertEquals("sonarqube", author1.getSlug()); assertEquals("sq@email.com", author1.getEmail()); StashDiff diff2 = report.getDiffs().get(1); assertEquals(0, diff2.getComments().size()); assertFalse(diff2.containsComment(12345)); assertFalse(diff2.containsComment(54321)); StashDiff diff3 = report.getDiffs().get(2); assertEquals(1, diff3.getComments().size()); assertFalse(diff3.containsComment(12345)); assertTrue(diff3.containsComment(54321)); StashComment comment2 = diff3.getComments().get(0); assertEquals(54321, comment2.getId()); assertEquals("Test comment 2", comment2.getMessage()); assertEquals(1, comment2.getVersion()); StashUser author2 = comment2.getAuthor(); assertEquals(54321, author2.getId()); assertEquals("SonarQube2", author2.getName()); assertEquals("sonarqube2", author2.getSlug()); assertEquals("sq2@email.com", author2.getEmail()); StashDiff diff4 = report.getDiffs().get(3); assertEquals(0, diff4.getComments().size()); assertFalse(diff4.containsComment(12345)); assertFalse(diff4.containsComment(54321)); StashDiff diff5 = report.getDiffs().get(4); assertEquals(0, diff5.getSource()); assertEquals(0, diff5.getDestination()); assertEquals("stash-plugin/Test.java", diff5.getPath()); assertEquals(IssueType.CONTEXT, diff5.getType()); assertEquals(2, diff5.getComments().size()); assertFalse(diff5.containsComment(12345)); assertFalse(diff5.containsComment(54321)); assertTrue(diff5.containsComment(123456)); assertTrue(diff5.containsComment(654321)); StashComment comment3 = diff5.getComments().get(0); assertEquals(123456, comment3.getId()); assertEquals(1, comment3.getVersion()); assertEquals("Test File comment", comment3.getMessage()); StashUser author3 = comment3.getAuthor(); assertEquals(12345, author3.getId()); assertEquals("SonarQube", author3.getName()); assertEquals("sonarqube", author3.getSlug()); assertEquals("sq@email.com", author3.getEmail()); StashComment comment4 = diff5.getComments().get(1); assertEquals(654321, comment4.getId()); assertEquals("Test File comment 2", comment4.getMessage()); assertEquals(1, comment4.getVersion()); StashUser author4 = comment4.getAuthor(); assertEquals(54321, author4.getId()); assertEquals("SonarQube2", author4.getName()); assertEquals("sonarqube2", author4.getSlug()); assertEquals("sq2@email.com", author4.getEmail()); } @Test public void testExtractDiffsWithEmptyFileComments() throws Exception { StashDiffReport report = StashCollector.extractDiffs(parse(DiffReportSample.baseReportWithEmptyFileComments)); assertEquals(5, report.getDiffs().size()); StashDiff diff1 = report.getDiffs().get(0); assertEquals(1, diff1.getComments().size()); assertTrue(diff1.containsComment(12345)); assertFalse(diff1.containsComment(54321)); StashComment comment1 = diff1.getComments().get(0); assertEquals(12345, comment1.getId()); assertEquals("Test comment", comment1.getMessage()); assertEquals(1, comment1.getVersion()); StashUser author1 = comment1.getAuthor(); assertEquals(12345, author1.getId()); assertEquals("SonarQube", author1.getName()); assertEquals("sonarqube", author1.getSlug()); assertEquals("sq@email.com", author1.getEmail()); StashDiff diff2 = report.getDiffs().get(1); assertEquals(0, diff2.getComments().size()); assertFalse(diff2.containsComment(12345)); assertFalse(diff2.containsComment(54321)); StashDiff diff3 = report.getDiffs().get(2); assertEquals(1, diff3.getComments().size()); assertFalse(diff3.containsComment(12345)); assertTrue(diff3.containsComment(54321)); StashDiff diff4 = report.getDiffs().get(3); assertEquals(0, diff4.getComments().size()); assertFalse(diff4.containsComment(12345)); assertFalse(diff4.containsComment(54321)); StashDiff diff5 = report.getDiffs().get(4); assertEquals(0, diff5.getSource()); assertEquals(0, diff5.getDestination()); assertEquals("stash-plugin/Test.java", diff5.getPath()); assertEquals(IssueType.CONTEXT, diff5.getType()); assertEquals(0, diff5.getComments().size()); assertFalse(diff5.containsComment(12345)); assertFalse(diff5.containsComment(54321)); } @Test public void testExtractDiffsWithEmptyReport() throws Exception { String jsonBody = "{ \"diffs\": []}"; StashDiffReport report = StashCollector.extractDiffs(parse(jsonBody)); assertTrue(report.getDiffs().isEmpty()); report = StashCollector.extractDiffs(parse(DiffReportSample.emptyReport)); assertTrue(report.getDiffs().isEmpty()); } @Test public void testExtractDiffsWithMultipleFile() throws Exception { StashDiffReport report = StashCollector.extractDiffs(parse(DiffReportSample.multipleFileReport)); assertEquals(2, report.getDiffs().size()); StashDiff diff1 = report.getDiffs().get(0); assertEquals((long)10, diff1.getSource()); assertEquals((long)20, diff1.getDestination()); assertEquals("stash-plugin/Test.java", diff1.getPath()); assertEquals(IssueType.CONTEXT, diff1.getType()); assertTrue(diff1.containsComment(12345)); assertFalse(diff1.containsComment(54321)); StashDiff diff2 = report.getDiffs().get(1); assertEquals((long)20, diff2.getSource()); assertEquals((long)30, diff2.getDestination()); assertEquals("stash-plugin/Test1.java", diff2.getPath()); assertEquals(IssueType.ADDED, diff2.getType()); assertFalse(diff2.containsComment(12345)); assertFalse(diff2.containsComment(54321)); } @Test public void testExtractDiffsWithDeletedFile() throws Exception { StashDiffReport report = StashCollector.extractDiffs(parse(DiffReportSample.deletedFileReport)); assertEquals(2, report.getDiffs().size()); StashDiff diff1 = report.getDiffs().get(0); assertEquals((long)10, diff1.getSource()); assertEquals((long)20, diff1.getDestination()); assertEquals("stash-plugin/Test2.java", diff1.getPath()); assertEquals(IssueType.CONTEXT, diff1.getType()); StashDiff diff2 = report.getDiffs().get(1); assertEquals((long)30, diff2.getSource()); assertEquals((long)40, diff2.getDestination()); assertEquals("stash-plugin/Test2.java", diff2.getPath()); assertEquals(IssueType.ADDED, diff2.getType()); } @Test public void testExtractPullRequest() throws Exception { String project = "project"; String repository = "repository"; int pullRequestId = 123; long pullRequestVersion = 1; long reviewerId = 1; String reviewerName = "SonarQube"; String reviewerSlug = "sonarqube"; String reviewerEmail = "sq@email.com"; String jsonBody = "{\"id\": " + pullRequestId + ", \"version\": " + pullRequestVersion + ", \"title\": \"PR-Test\"," + "\"description\": \"PR-test\", \"reviewers\": [" + "{\"user\": { \"name\":\"" + reviewerName + "\", \"emailAddress\": \"" + reviewerEmail + "\"," + "\"id\": " + reviewerId + ", \"slug\": \"" + reviewerSlug + "\"}, \"role\": \"REVIEWER\", \"approved\": false}]}"; StashPullRequest pullRequest = StashCollector.extractPullRequest(pr, parse(jsonBody)); assertEquals(project, pullRequest.getProject()); assertEquals(repository, pullRequest.getRepository()); assertEquals(pullRequestId, pullRequest.getId()); assertEquals(pullRequestVersion, pullRequest.getVersion()); StashUser reviewer = new StashUser(reviewerId, reviewerName, reviewerSlug, reviewerEmail); assertEquals(1, pullRequest.getReviewers().size()); assertTrue(pullRequest.containsReviewer(reviewer)); } @Test public void testExtractPullRequestWithSeveralReviewer() throws Exception { String project = "project"; String repository = "repository"; int pullRequestId = 123; long pullRequestVersion = 1; long reviewerId1 = 1; String reviewerName1 = "SonarQube1"; String reviewerSlug1 = "sonarqube1"; String reviewerEmail1 = "sq1@email.com"; long reviewerId2 = 1; String reviewerName2 = "SonarQube2"; String reviewerSlug2 = "sonarqube2"; String reviewerEmail2 = "sq2@email.com"; String jsonBody = "{\"id\": " + pullRequestId + ", \"version\": " + pullRequestVersion + ", \"title\": \"PR-Test\"," + "\"description\": \"PR-test\", \"reviewers\": [" + "{\"user\": { \"name\":\"" + reviewerName1 + "\", \"emailAddress\": \"" + reviewerEmail1 + "\"," + "\"id\": " + reviewerId1 + ", \"slug\": \"" + reviewerSlug1 + "\"}, \"role\": \"REVIEWER\", \"approved\": false}," + "{\"user\": { \"name\":\"" + reviewerName2 + "\", \"emailAddress\": \"" + reviewerEmail2 + "\"," + "\"id\": " + reviewerId2 + ", \"slug\": \"" + reviewerSlug2 + "\"}, \"role\": \"REVIEWER\", \"approved\": false}]}"; StashPullRequest pullRequest = StashCollector.extractPullRequest(pr, parse(jsonBody)); assertEquals(project, pullRequest.getProject()); assertEquals(repository, pullRequest.getRepository()); assertEquals(pullRequestId, pullRequest.getId()); assertEquals(pullRequestVersion, pullRequest.getVersion()); StashUser reviewer1 = new StashUser(reviewerId1, reviewerName1, reviewerSlug1, reviewerEmail1); StashUser reviewer2 = new StashUser(reviewerId2, reviewerName2, reviewerSlug2, reviewerEmail2); assertEquals(2, pullRequest.getReviewers().size()); assertTrue(pullRequest.containsReviewer(reviewer1)); assertTrue(pullRequest.containsReviewer(reviewer2)); } @Test public void testExtractPullRequestWithNoReviewer() throws Exception { String project = "project"; String repository = "repository"; int pullRequestId = 123; long pullRequestVersion = 1; String jsonBody = "{\"id\": " + pullRequestId + ", \"version\": " + pullRequestVersion + ", \"title\": \"PR-Test\"," + "\"description\": \"PR-test\", \"reviewers\": []}"; StashPullRequest pullRequest = StashCollector.extractPullRequest(pr, parse(jsonBody)); assertEquals(project, pullRequest.getProject()); assertEquals(repository, pullRequest.getRepository()); assertEquals(pullRequestId, pullRequest.getId()); assertEquals(pullRequestVersion, pullRequest.getVersion()); assertEquals(0, pullRequest.getReviewers().size()); } @Test public void testExtractUser() throws Exception { long userId = 1; String userName = "SonarQube"; String userSlug = "sonarqube"; String userEmail = "sq@email.com"; String jsonBody = "{ \"name\":\"" + userName + "\", \"email\": \"" + userEmail + "\"," + "\"id\": " + userId + ", \"slug\": \"" + userSlug + "\"}"; StashUser user = StashCollector.extractUser(parse(jsonBody)); assertEquals(userId, user.getId()); assertEquals(userName, user.getName()); assertEquals(userSlug, user.getSlug()); assertEquals(userEmail, user.getEmail()); } @Test public void testExtractTask() throws Exception { long id = 1111; String text = "Text"; String state = "State"; boolean deletable = true; String jsonTask = "{ \"id\":" + id + ", \"text\":\"" + text + "\", \"state\":\"" + state + "\"," + "\"permittedOperations\": { \"deletable\":" + deletable + "}}"; StashTask task = StashCollector.extractTask(parse(jsonTask)); assertEquals(id, (long)task.getId()); assertEquals(text, task.getText()); assertEquals(state, task.getState()); assertEquals(deletable, task.isDeletable()); } @Test public void testExtractTaskWithoutPermittedOperation() throws Exception { long id = 1111; String text = "Text"; String state = "State"; String jsonTask = "{ \"id\":" + id + ", \"text\":\"" + text + "\", \"state\": \"" + state + "\"}"; StashTask task = StashCollector.extractTask(parse(jsonTask)); assertEquals(id, (long)task.getId()); assertEquals(text, task.getText()); assertEquals(state, task.getState()); assertTrue(task.isDeletable()); } private static JsonObject parse(String s) throws Exception { return (JsonObject) Jsoner.deserialize(s); } } ================================================ FILE: src/test/resources/fixtures/issue194_stash_diff.json ================================================ { "fromHash": "820a8e5219b4ebdf44060f8db5a19893abf5360c", "toHash": "7424a87f33d9665ed7a9b6800a2ad7926d10715b", "contextLines": 10, "whitespace": "SHOW", "diffs": [ { "source": { "components": [ "aModule-api-app", "pom.xml" ], "parent": "aModule-api-app", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-app/pom.xml" }, "destination": { "components": [ "aModule-api-app", "pom.xml" ], "parent": "aModule-api-app", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-app/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 16, "destinationLine": 1, "destinationSpan": 16, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-app", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " ", "truncated": false }, { "source": 14, "destination": 14, "line": " com.gdn.package", "truncated": false }, { "source": 15, "destination": 15, "line": " aModule-api-web", "truncated": false }, { "source": 16, "destination": 16, "line": " ${project.version}", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-app", "src", "main", "resources", "logback.xml" ], "parent": "aModule-api-app/src/main/resources", "name": "logback.xml", "extension": "xml", "toString": "aModule-api-app/src/main/resources/logback.xml" }, "destination": { "components": [ "aModule-api-app", "src", "main", "resources", "logback.xml" ], "parent": "aModule-api-app/src/main/resources", "name": "logback.xml", "extension": "xml", "toString": "aModule-api-app/src/main/resources/logback.xml" }, "hunks": [ { "sourceLine": 41, "sourceSpan": 24, "destinationLine": 41, "destinationSpan": 24, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 41, "destination": 41, "line": " ", "truncated": false }, { "source": 42, "destination": 42, "line": "", "truncated": false }, { "source": 43, "destination": 43, "line": " ", "truncated": false }, { "source": 44, "destination": 44, "line": " ", "truncated": false }, { "source": 45, "destination": 45, "line": " ", "truncated": false }, { "source": 46, "destination": 46, "line": " ", "truncated": false }, { "source": 47, "destination": 47, "line": " ", "truncated": false }, { "source": 48, "destination": 48, "line": " ", "truncated": false }, { "source": 49, "destination": 49, "line": "", "truncated": false }, { "source": 50, "destination": 50, "line": " ", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 51, "destination": 51, "line": " ", "truncated": false }, { "source": 52, "destination": 51, "line": " ", "truncated": false }, { "source": 53, "destination": 51, "line": " ", "truncated": false }, { "source": 54, "destination": 51, "line": " ", "truncated": false }, { "source": 55, "destination": 51, "line": " ", "truncated": false }, { "source": 56, "destination": 51, "line": " ", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 57, "destination": 51, "line": " ", "truncated": false }, { "source": 57, "destination": 52, "line": " ", "truncated": false }, { "source": 57, "destination": 53, "line": " ${CONSOLE_LOG_PATTERN:-%clr([%date{\"yyyy-MM-dd'T'HH:mm:ss,SSSXXX\", GMT+07:00}]){faint}%clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint}%clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint}%m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}", "truncated": false }, { "source": 57, "destination": 54, "line": " utf8", "truncated": false }, { "source": 57, "destination": 55, "line": " ", "truncated": false }, { "source": 57, "destination": 56, "line": " ", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 57, "destination": 57, "line": "", "truncated": false }, { "source": 58, "destination": 58, "line": " ", "truncated": false }, { "source": 59, "destination": 59, "line": " ", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 60, "destination": 60, "line": " ", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 61, "destination": 60, "line": " ", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 61, "destination": 61, "line": " ", "truncated": false }, { "source": 62, "destination": 62, "line": " ", "truncated": false }, { "source": 63, "destination": 63, "line": "", "truncated": false }, { "source": 64, "destination": 64, "line": "", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-client-impl", "pom.xml" ], "parent": "aModule-api-client-impl", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-client-impl/pom.xml" }, "destination": { "components": [ "aModule-api-client-impl", "pom.xml" ], "parent": "aModule-api-client-impl", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-client-impl/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 16, "destinationLine": 1, "destinationSpan": 16, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-client-impl", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " ", "truncated": false }, { "source": 14, "destination": 14, "line": " com.gdn.package", "truncated": false }, { "source": 15, "destination": 15, "line": " aModule-api-properties", "truncated": false }, { "source": 16, "destination": 16, "line": " ${project.version}", "truncated": false } ], "truncated": false } ], "truncated": false }, { "sourceLine": 41, "sourceSpan": 11, "destinationLine": 41, "destinationSpan": 11, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 41, "destination": 41, "line": " wiremock-standalone", "truncated": false }, { "source": 42, "destination": 42, "line": " test", "truncated": false }, { "source": 43, "destination": 43, "line": " ", "truncated": false }, { "source": 44, "destination": 44, "line": " ", "truncated": false }, { "source": 45, "destination": 45, "line": " io.rest-assured", "truncated": false }, { "source": 46, "destination": 46, "line": " rest-assured", "truncated": false }, { "source": 47, "destination": 47, "line": " test", "truncated": false }, { "source": 48, "destination": 48, "line": " ", "truncated": false }, { "source": 49, "destination": 49, "line": " ", "truncated": false }, { "source": 50, "destination": 50, "line": "", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 51, "destination": 51, "line": "", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 52, "destination": 51, "line": "", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-client-model", "pom.xml" ], "parent": "aModule-api-client-model", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-client-model/pom.xml" }, "destination": { "components": [ "aModule-api-client-model", "pom.xml" ], "parent": "aModule-api-client-model", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-client-model/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 16, "destinationLine": 1, "destinationSpan": 16, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-client-model", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " ", "truncated": false }, { "source": 14, "destination": 14, "line": " com.gdn.package", "truncated": false }, { "source": 15, "destination": 15, "line": " aModule-api-entity", "truncated": false }, { "source": 16, "destination": 16, "line": " ${project.version}", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-client", "pom.xml" ], "parent": "aModule-api-client", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-client/pom.xml" }, "destination": { "components": [ "aModule-api-client", "pom.xml" ], "parent": "aModule-api-client", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-client/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 16, "destinationLine": 1, "destinationSpan": 16, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-client", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " ", "truncated": false }, { "source": 14, "destination": 14, "line": " com.gdn.package", "truncated": false }, { "source": 15, "destination": 15, "line": " aModule-api-properties", "truncated": false }, { "source": 16, "destination": 16, "line": " ${project.version}", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-client", "src", "main", "java", "com", "gdn", "package", "aModule", "client", "aModuleClient.java" ], "parent": "aModule-api-client/src/main/java/com/gdn/package/aModule/client", "name": "aModuleClient.java", "extension": "java", "toString": "aModule-api-client/src/main/java/com/gdn/package/aModule/client/aModuleClient.java" }, "destination": { "components": [ "aModule-api-client", "src", "main", "java", "com", "gdn", "package", "aModule", "client", "aModuleClient.java" ], "parent": "aModule-api-client/src/main/java/com/gdn/package/aModule/client", "name": "aModuleClient.java", "extension": "java", "toString": "aModule-api-client/src/main/java/com/gdn/package/aModule/client/aModuleClient.java" }, "hunks": [ { "context": "public class aModuleClient implements NettyConnector aModuleAddresses = addresses.stream().map(Server::new).collect(Collectors.toList());", "truncated": false }, { "source": 115, "destination": 124, "line": " this.loadBalancer.addServers(aModuleAddresses);", "truncated": false }, { "source": 116, "destination": 125, "line": " this.loadBalancerContext = new LoadBalancerContext(loadBalancer, loadBalancerConfig);", "truncated": false }, { "source": 117, "destination": 126, "line": " }", "truncated": false }, { "source": 118, "destination": 127, "line": "", "truncated": false }, { "source": 119, "destination": 128, "line": " @Override", "truncated": false }, { "source": 120, "destination": 129, "line": " @SuppressWarnings(\"unchecked\")", "truncated": false }, { "source": 121, "destination": 130, "line": " public Mono newHandler(BiFunction> biFunction) {", "truncated": false }, { "source": 122, "destination": 131, "line": " return (Mono) bridgeClient.newHandler((BiFunction>) biFunction);", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-command-impl", "pom.xml" ], "parent": "aModule-api-command-impl", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-command-impl/pom.xml" }, "destination": { "components": [ "aModule-api-command-impl", "pom.xml" ], "parent": "aModule-api-command-impl", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-command-impl/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 16, "destinationLine": 1, "destinationSpan": 16, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-command-impl", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " ", "truncated": false }, { "source": 14, "destination": 14, "line": " com.gdn.package", "truncated": false }, { "source": 15, "destination": 15, "line": " aModule-api-command", "truncated": false }, { "source": 16, "destination": 16, "line": " ${project.version}", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-command-model", "pom.xml" ], "parent": "aModule-api-command-model", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-command-model/pom.xml" }, "destination": { "components": [ "aModule-api-command-model", "pom.xml" ], "parent": "aModule-api-command-model", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-command-model/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 16, "destinationLine": 1, "destinationSpan": 16, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-command-model", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " ", "truncated": false }, { "source": 14, "destination": 14, "line": " com.gdn.package", "truncated": false }, { "source": 15, "destination": 15, "line": " aModule-api-validation", "truncated": false }, { "source": 16, "destination": 16, "line": " ${project.version}", "truncated": false } ], "truncated": false } ], "truncated": false }, { "sourceLine": 20, "sourceSpan": 11, "destinationLine": 20, "destinationSpan": 11, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 20, "destination": 20, "line": " aModule-api-web-model", "truncated": false }, { "source": 21, "destination": 21, "line": " ${project.version}", "truncated": false }, { "source": 22, "destination": 22, "line": " ", "truncated": false }, { "source": 23, "destination": 23, "line": " ", "truncated": false }, { "source": 24, "destination": 24, "line": " com.gdn.package", "truncated": false }, { "source": 25, "destination": 25, "line": " aModule-api-client-model", "truncated": false }, { "source": 26, "destination": 26, "line": " ${project.version}", "truncated": false }, { "source": 27, "destination": 27, "line": " ", "truncated": false }, { "source": 28, "destination": 28, "line": " ", "truncated": false }, { "source": 29, "destination": 29, "line": "", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 30, "destination": 30, "line": "", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 31, "destination": 30, "line": "", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-command", "pom.xml" ], "parent": "aModule-api-command", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-command/pom.xml" }, "destination": { "components": [ "aModule-api-command", "pom.xml" ], "parent": "aModule-api-command", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-command/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 16, "destinationLine": 1, "destinationSpan": 16, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-command", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " ", "truncated": false }, { "source": 14, "destination": 14, "line": " com.gdn.package", "truncated": false }, { "source": 15, "destination": 15, "line": " aModule-api-command-model", "truncated": false }, { "source": 16, "destination": 16, "line": " ${project.version}", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-entity", "pom.xml" ], "parent": "aModule-api-entity", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-entity/pom.xml" }, "destination": { "components": [ "aModule-api-entity", "pom.xml" ], "parent": "aModule-api-entity", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-entity/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 12, "destinationLine": 1, "destinationSpan": 12, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-entity", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false }, { "source": 12, "destination": 12, "line": "", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-properties", "pom.xml" ], "parent": "aModule-api-properties", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-properties/pom.xml" }, "destination": { "components": [ "aModule-api-properties", "pom.xml" ], "parent": "aModule-api-properties", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-properties/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 20, "destinationLine": 1, "destinationSpan": 20, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-properties", "truncated": false }, { "source": 11, "destination": 11, "line": " ", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " com.gdn.package", "truncated": false }, { "source": 14, "destination": 14, "line": " aModule-api-entity", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 15, "destination": 15, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 16, "destination": 15, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 16, "destination": 16, "line": " compile", "truncated": false }, { "source": 17, "destination": 17, "line": " ", "truncated": false }, { "source": 18, "destination": 18, "line": " ", "truncated": false }, { "source": 19, "destination": 19, "line": "", "truncated": false }, { "source": 20, "destination": 20, "line": "", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-repository", "pom.xml" ], "parent": "aModule-api-repository", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-repository/pom.xml" }, "destination": { "components": [ "aModule-api-repository", "pom.xml" ], "parent": "aModule-api-repository", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-repository/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 25, "destinationLine": 1, "destinationSpan": 25, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-repository", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " ", "truncated": false }, { "source": 14, "destination": 14, "line": " com.gdn.package", "truncated": false }, { "source": 15, "destination": 15, "line": " aModule-api-entity", "truncated": false }, { "source": 16, "destination": 16, "line": " ${project.version}", "truncated": false }, { "source": 17, "destination": 17, "line": " ", "truncated": false }, { "source": 18, "destination": 18, "line": " ", "truncated": false }, { "source": 19, "destination": 19, "line": " de.flapdoodle.embed", "truncated": false }, { "source": 20, "destination": 20, "line": " de.flapdoodle.embed.mongo", "truncated": false }, { "source": 21, "destination": 21, "line": " test", "truncated": false }, { "source": 22, "destination": 22, "line": " ", "truncated": false }, { "source": 23, "destination": 23, "line": " ", "truncated": false }, { "source": 24, "destination": 24, "line": "", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 25, "destination": 25, "line": "", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 26, "destination": 25, "line": "", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-streaming-model", "pom.xml" ], "parent": "aModule-api-streaming-model", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-streaming-model/pom.xml" }, "destination": { "components": [ "aModule-api-streaming-model", "pom.xml" ], "parent": "aModule-api-streaming-model", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-streaming-model/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 12, "destinationLine": 1, "destinationSpan": 12, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-streaming-model", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 12, "destination": 12, "line": "", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 13, "destination": 12, "line": "", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-streaming", "pom.xml" ], "parent": "aModule-api-streaming", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-streaming/pom.xml" }, "destination": { "components": [ "aModule-api-streaming", "pom.xml" ], "parent": "aModule-api-streaming", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-streaming/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 16, "destinationLine": 1, "destinationSpan": 16, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-streaming", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " ", "truncated": false }, { "source": 14, "destination": 14, "line": " com.gdn.package", "truncated": false }, { "source": 15, "destination": 15, "line": " aModule-api-streaming-model", "truncated": false }, { "source": 16, "destination": 16, "line": " ${project.version}", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-validation", "pom.xml" ], "parent": "aModule-api-validation", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-validation/pom.xml" }, "destination": { "components": [ "aModule-api-validation", "pom.xml" ], "parent": "aModule-api-validation", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-validation/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 16, "destinationLine": 1, "destinationSpan": 16, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-validation", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " ", "truncated": false }, { "source": 14, "destination": 14, "line": " com.gdn.package", "truncated": false }, { "source": 15, "destination": 15, "line": " aModule-api-repository", "truncated": false }, { "source": 16, "destination": 16, "line": " ${project.version}", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-web-model", "pom.xml" ], "parent": "aModule-api-web-model", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-web-model/pom.xml" }, "destination": { "components": [ "aModule-api-web-model", "pom.xml" ], "parent": "aModule-api-web-model", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-web-model/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 25, "destinationLine": 1, "destinationSpan": 25, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-web-model", "truncated": false }, { "source": 11, "destination": 11, "line": " ", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " com.gdn.package", "truncated": false }, { "source": 14, "destination": 14, "line": " aModule-api-entity", "truncated": false }, { "source": 15, "destination": 15, "line": " ${project.version}", "truncated": false }, { "source": 16, "destination": 16, "line": " compile", "truncated": false }, { "source": 17, "destination": 17, "line": " ", "truncated": false }, { "source": 18, "destination": 18, "line": " ", "truncated": false }, { "source": 19, "destination": 19, "line": " com.gdn.package", "truncated": false }, { "source": 20, "destination": 20, "line": " aModule-api-validation", "truncated": false }, { "source": 21, "destination": 21, "line": " ${project.version}", "truncated": false }, { "source": 22, "destination": 22, "line": " ", "truncated": false }, { "source": 23, "destination": 23, "line": " ", "truncated": false }, { "source": 24, "destination": 24, "line": "", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 25, "destination": 25, "line": "", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 26, "destination": 25, "line": "", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "aModule-api-web", "pom.xml" ], "parent": "aModule-api-web", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-web/pom.xml" }, "destination": { "components": [ "aModule-api-web", "pom.xml" ], "parent": "aModule-api-web", "name": "pom.xml", "extension": "xml", "toString": "aModule-api-web/pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 16, "destinationLine": 1, "destinationSpan": 16, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " ", "truncated": false }, { "source": 4, "destination": 4, "line": " com.gdn.package", "truncated": false }, { "source": 5, "destination": 5, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 6, "destination": 6, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 7, "destination": 6, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 7, "destination": 7, "line": " ", "truncated": false }, { "source": 8, "destination": 8, "line": " 4.0.0", "truncated": false }, { "source": 9, "destination": 9, "line": "", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-web", "truncated": false }, { "source": 11, "destination": 11, "line": "", "truncated": false }, { "source": 12, "destination": 12, "line": " ", "truncated": false }, { "source": 13, "destination": 13, "line": " ", "truncated": false }, { "source": 14, "destination": 14, "line": " ", "truncated": false }, { "source": 15, "destination": 15, "line": " org.springframework.cloud", "truncated": false }, { "source": 16, "destination": 16, "line": " spring-cloud-contract-maven-plugin", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] }, { "source": { "components": [ "pom.xml" ], "parent": "", "name": "pom.xml", "extension": "xml", "toString": "pom.xml" }, "destination": { "components": [ "pom.xml" ], "parent": "", "name": "pom.xml", "extension": "xml", "toString": "pom.xml" }, "hunks": [ { "sourceLine": 1, "sourceSpan": 17, "destinationLine": 1, "destinationSpan": 17, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 1, "destination": 1, "line": "", "truncated": false }, { "source": 2, "destination": 2, "line": "", "truncated": false }, { "source": 3, "destination": 3, "line": " 4.0.0", "truncated": false }, { "source": 4, "destination": 4, "line": "", "truncated": false }, { "source": 5, "destination": 5, "line": " com.gdn.package", "truncated": false }, { "source": 6, "destination": 6, "line": " aModule", "truncated": false } ], "truncated": false }, { "type": "REMOVED", "lines": [ { "source": 7, "destination": 7, "line": " 0.0.1-4-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 8, "destination": 7, "line": " 0.0.1-5-SNAPSHOT", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 8, "destination": 8, "line": " ", "truncated": false }, { "source": 9, "destination": 9, "line": " aModule-api-entity", "truncated": false }, { "source": 10, "destination": 10, "line": " aModule-api-repository", "truncated": false }, { "source": 11, "destination": 11, "line": " aModule-api-app", "truncated": false }, { "source": 12, "destination": 12, "line": " aModule-api-command", "truncated": false }, { "source": 13, "destination": 13, "line": " aModule-api-command-impl", "truncated": false }, { "source": 14, "destination": 14, "line": " aModule-api-web", "truncated": false }, { "source": 15, "destination": 15, "line": " aModule-api-streaming", "truncated": false }, { "source": 16, "destination": 16, "line": " aModule-api-web-model", "truncated": false }, { "source": 17, "destination": 17, "line": " aModule-api-command-model", "truncated": false } ], "truncated": false } ], "truncated": false }, { "sourceLine": 59, "sourceSpan": 20, "destinationLine": 59, "destinationSpan": 27, "segments": [ { "type": "CONTEXT", "lines": [ { "source": 59, "destination": 59, "line": " ", "truncated": false }, { "source": 60, "destination": 60, "line": " ", "truncated": false }, { "source": 61, "destination": 61, "line": " src/main/java/com/gdn/package/aModule/entity/**/*,", "truncated": false }, { "source": 62, "destination": 62, "line": " src/main/java/com/gdn/package/aModule/properties/**/*,", "truncated": false }, { "source": 63, "destination": 63, "line": " src/main/java/com/gdn/package/aModule/client/model/**/*,", "truncated": false }, { "source": 64, "destination": 64, "line": " src/main/java/com/gdn/package/aModule/configurations/**/*,", "truncated": false }, { "source": 65, "destination": 65, "line": " src/main/java/com/gdn/package/aModule/command/model/**/*", "truncated": false }, { "source": 66, "destination": 66, "line": " src/main/java/com/gdn/package/aModule/streaming/model/**/*,", "truncated": false }, { "source": 67, "destination": 67, "line": " src/main/java/com/gdn/package/aModule/web/model/**/*", "truncated": false }, { "source": 68, "destination": 68, "line": " ", "truncated": false } ], "truncated": false }, { "type": "ADDED", "lines": [ { "source": 69, "destination": 69, "line": " jdbc:h2:tcp://10.177.92.24:9092/sonar", "truncated": false }, { "source": 69, "destination": 70, "line": " sonar", "truncated": false }, { "source": 69, "destination": 71, "line": " sonar", "truncated": false }, { "source": 69, "destination": 72, "line": " org.h2.Driver", "truncated": false }, { "source": 69, "destination": 73, "line": " http://10.177.92.24:9000", "truncated": false }, { "source": 69, "destination": 74, "line": " admin", "truncated": false }, { "source": 69, "destination": 75, "line": " admin", "truncated": false } ], "truncated": false }, { "type": "CONTEXT", "lines": [ { "source": 69, "destination": 76, "line": " ", "truncated": false }, { "source": 70, "destination": 77, "line": "", "truncated": false }, { "source": 71, "destination": 78, "line": " ", "truncated": false }, { "source": 72, "destination": 79, "line": " ", "truncated": false }, { "source": 73, "destination": 80, "line": " ", "truncated": false }, { "source": 74, "destination": 81, "line": " org.springframework.cloud", "truncated": false }, { "source": 75, "destination": 82, "line": " spring-cloud-dependencies", "truncated": false }, { "source": 76, "destination": 83, "line": " ${spring-cloud.version}", "truncated": false }, { "source": 77, "destination": 84, "line": " pom", "truncated": false }, { "source": 78, "destination": 85, "line": " import", "truncated": false } ], "truncated": false } ], "truncated": false } ], "truncated": false, "lineComments": [ { "properties": { "repositoryId": 1 }, "id": 71, "version": 0, "text": "*MINOR* - Remove this unused \"B\" local variable. [[squid:S1481](http://10.177.92.24:9000/coding_rules#rule_key=squid:S1481)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686764, "updatedDate": 1542790686764, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 72, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686785, "updatedDate": 1542790686785, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 70, "version": 0, "text": "*MAJOR* - Change this condition so that it does not always evaluate to \"false\" [[squid:S2583](http://10.177.92.24:9000/coding_rules#rule_key=squid:S2583)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686749, "updatedDate": 1542790686749, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 68, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686696, "updatedDate": 1542790686696, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 73, "version": 0, "text": "*MINOR* - Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'. [[squid:S00117](http://10.177.92.24:9000/coding_rules#rule_key=squid:S00117)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686806, "updatedDate": 1542790686806, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } }, { "properties": { "repositoryId": 1 }, "id": 69, "version": 0, "text": "*MAJOR* - Replace this use of System.out or System.err by a logger. [[squid:S106](http://10.177.92.24:9000/coding_rules#rule_key=squid:S106)]", "author": { "name": "some.user", "emailAddress": "some.user@example.org", "id": 1, "displayName": "Some Random User", "active": true, "slug": "some.user", "type": "NORMAL" }, "createdDate": 1542790686731, "updatedDate": 1542790686731, "comments": [], "tasks": [], "permittedOperations": { "editable": true, "deletable": true } } ] } ], "truncated": false } ================================================ FILE: src/test/resources/foo/module1/src/main/java/Foo.java ================================================ public class Foo { public static void main(String[] args) { System.out.println("Foo"); } } ================================================ FILE: src/test/resources/foo/module2/src/main/java/Bar.java ================================================ public class Bar { public static void main(String[] args) { System.out.println("Bar"); } } ================================================ FILE: src/test/resources/foo/sonar-project.properties ================================================ sonar.modules=module1,module2 module1.sonar.sources=src/main/java module2.sonar.sources=src/main/java