diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..10727e1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,55 @@ +--- +name: Build + +on: # yamllint disable-line rule:truthy + push: + branches: + - "master" + - "main" + - "develop" + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - '*.md' + - '*.adoc' + - '*.txt' + - '.all-contributorsrc' + pull_request: + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - '*.md' + - '*.adoc' + - '*.txt' + - '.all-contributorsrc' + +jobs: + build: + permissions: + checks: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + cache: 'gradle' + - name: Cache SonarQube packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: ./gradlew build sonar --no-daemon + - name: Build native + run: ./gradlew build --no-daemon -Dquarkus.package.type=native diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5830b08 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,42 @@ +--- +name: release-build + +on: # yamllint disable-line rule:truthy + release: + types: [created] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Install graalvm + uses: DeLaGuardo/setup-graalvm@3 + with: + graalvm-version: '20.0.0.java11' + - name: Install native-image + run: gu install native-image + - name: Build native executable + run: "./gradlew -Pversion=\"${{ github.event.release.tag_name }}\" build -Dquarkus.package.type=native" + - name: Upload native executable + id: upload-native-executable + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ./build/qualityannotate-${{ github.event.release.tag_name }}-runner + asset_name: qualityannotate-${{ github.event.release.tag_name }}-linux + asset_content_type: application/octet-stream + - name: Upload config + id: upload-config + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ./build/resources/main/application.yml + asset_name: application.yml + asset_content_type: text/yaml diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..15a72c1 --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,7 @@ +--- +extends: default + +rules: + line-length: + max: 120 + level: warning diff --git a/build.gradle b/build.gradle index d0ee1a0..4ae262e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,9 @@ plugins { id 'java' + id "jacoco" id 'io.quarkus' id("io.freefair.lombok") version "8.4" + id "org.sonarqube" version "4.3.1.3277" } repositories { @@ -24,18 +26,39 @@ group 'org.acme' version '1.0.0-SNAPSHOT' java { - sourceCompatibility = JavaVersion.VERSION_16 - targetCompatibility = JavaVersion.VERSION_16 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + compileJava { + options.encoding = 'UTF-8' + options.compilerArgs << '-parameters' + } + + compileTestJava { + options.encoding = 'UTF-8' + } } test { systemProperty "java.util.logging.manager", "org.jboss.logmanager.LogManager" } -compileJava { - options.encoding = 'UTF-8' - options.compilerArgs << '-parameters' + +project.tasks["sonar"].dependsOn "build" + +sonar { + properties { + property "sonar.projectKey", "quyt_qualityannotate" + property "sonar.organization", "quyt" + property "sonar.host.url", "https://sonarcloud.io" + + property "sonar.projectName", "Quality Annotate" + property "sonar.sourceEncoding", "UTF-8" + } } -compileTestJava { - options.encoding = 'UTF-8' +jacocoTestReport { + reports { + xml.required = true + html.outputLocation = layout.buildDirectory.dir('jacocoHtml') + } } +test.finalizedBy jacocoTestReport diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..62d4c05 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..db9a6b8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..61a3430 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,10 @@ +sonar.host.url=https://sonarcloud.io +sonar.projectName=Quality Annotate +sonar.projectKey=qualityannotate +sonar.sourceEncoding=UTF-8 + + +sonar.sources=src/main/java/ +sonar.java.libraries=target/*.jar +sonar.java.binaries=target/classes/org/acme/rest/json +sonar.java.test.binaries=target/test-classes/org/acme/rest/json diff --git a/src/main/java/org/qualityannotate/api/qualitytool/Issue.java b/src/main/java/org/qualityannotate/api/qualitytool/Issue.java index ae99464..32badfd 100644 --- a/src/main/java/org/qualityannotate/api/qualitytool/Issue.java +++ b/src/main/java/org/qualityannotate/api/qualitytool/Issue.java @@ -1,4 +1,4 @@ package org.qualityannotate.api.qualitytool; -public record Issue(String fileName, String lineNumber, String comment, String severity) { +public record Issue(String fileName, Integer lineNumber, String comment, String severity) { } diff --git a/src/main/java/org/qualityannotate/quality/sonarqube/SonarqubeConfig.java b/src/main/java/org/qualityannotate/quality/sonarqube/SonarqubeConfig.java index 663c4fa..ecdf84b 100644 --- a/src/main/java/org/qualityannotate/quality/sonarqube/SonarqubeConfig.java +++ b/src/main/java/org/qualityannotate/quality/sonarqube/SonarqubeConfig.java @@ -3,6 +3,8 @@ import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithName; +import java.util.List; + @ConfigMapping(prefix = "sonarqube") public interface SonarqubeConfig { @WithName("url") @@ -16,4 +18,7 @@ public interface SonarqubeConfig { @WithName("pull_request") String pullRequest(); + + @WithName("global_metric_types") + List globalMetricTypes(); } diff --git a/src/main/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityTool.java b/src/main/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityTool.java index 509d78e..15a5efd 100644 --- a/src/main/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityTool.java +++ b/src/main/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityTool.java @@ -4,10 +4,7 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import org.qualityannotate.core.rest.BasicAuthRequestFilter; -import org.qualityannotate.quality.sonarqube.client.ComponentMeasures; -import org.qualityannotate.quality.sonarqube.client.Measure; -import org.qualityannotate.quality.sonarqube.client.Metric; -import org.qualityannotate.quality.sonarqube.client.SonarqubeApiClient; +import org.qualityannotate.quality.sonarqube.client.*; import org.qualityannotate.api.qualitytool.GlobalMetrics; import org.qualityannotate.api.qualitytool.Issue; import org.qualityannotate.api.qualitytool.QualityTool; @@ -31,16 +28,34 @@ public SonarqubeQualityTool(SonarqubeConfig config) { @Override public List getIssues() { - return Collections.emptyList(); + IssueSearch issuesSearch = client.getIssuesSearch(config.project(), config.pullRequest(), null, null, null, null, null, null, null, null, null); + // TODO issueSearch.facets can be used for globalMetrics - but maybe doesn't matter + List issues = new ArrayList<>(); + for (SqIssue sqIssue : issuesSearch.issues()) { + issues.add(new Issue(sqIssue.getPath(config.project()), sqIssue.getLineNumber(), sqIssue.getMessage(), sqIssue.getSeverity())); + } + return issues; } @Override public GlobalMetrics getGlobalMetrics() { - ComponentMeasures componentMeasures = client.getComponentMeasures(config.project(), config.pullRequest(), "new_coverage,new_sqale_debt_ratio,new_uncovered_conditions"); + ComponentMeasures componentMeasures = client.getComponentMeasures(config.project(), config.pullRequest(), + String.join(",", config.globalMetricTypes())); Map metrics = new HashMap<>(); for (Metric metric : componentMeasures.getMetrics()) { - Optional measure = componentMeasures.getComponent().getMeasure(metric.getKey()); - measure.ifPresent(value -> metrics.put(metric.getName(), value.getPeriod() == null ? value.getValue() : value.getPeriod().value())); + Optional measureOpt = componentMeasures.getComponent().getMeasure(metric.getKey()); + if (measureOpt.isPresent()) { + Measure measure = measureOpt.get(); + String value = measure.getPeriod() == null ? measure.getValue() : measure.getPeriod().value(); + if (metric.getKey().endsWith("coverage")) { + try { + value = String.format("%.2f%%", Double.parseDouble(value)); + } catch (NumberFormatException e) { + // ignore + } + } + metrics.put(metric.getName(), value); + } } return new GlobalMetrics(metrics); } diff --git a/src/main/java/org/qualityannotate/quality/sonarqube/client/Component.java b/src/main/java/org/qualityannotate/quality/sonarqube/client/Component.java index 5c28311..429d7f5 100644 --- a/src/main/java/org/qualityannotate/quality/sonarqube/client/Component.java +++ b/src/main/java/org/qualityannotate/quality/sonarqube/client/Component.java @@ -1,13 +1,11 @@ package org.qualityannotate.quality.sonarqube.client; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Value; import java.util.List; import java.util.Optional; @Value -@JsonIgnoreProperties(ignoreUnknown = true) public class Component { /** * "key": "MY_PROJECT:ElementImpl.java", diff --git a/src/main/java/org/qualityannotate/quality/sonarqube/client/ComponentMeasures.java b/src/main/java/org/qualityannotate/quality/sonarqube/client/ComponentMeasures.java index bb7009e..9f43609 100644 --- a/src/main/java/org/qualityannotate/quality/sonarqube/client/ComponentMeasures.java +++ b/src/main/java/org/qualityannotate/quality/sonarqube/client/ComponentMeasures.java @@ -1,13 +1,11 @@ package org.qualityannotate.quality.sonarqube.client; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; @Data -@JsonIgnoreProperties(ignoreUnknown = true) public class ComponentMeasures { @JsonProperty("component") public Component component; diff --git a/src/main/java/org/qualityannotate/quality/sonarqube/client/Facet.java b/src/main/java/org/qualityannotate/quality/sonarqube/client/Facet.java new file mode 100644 index 0000000..2f599b0 --- /dev/null +++ b/src/main/java/org/qualityannotate/quality/sonarqube/client/Facet.java @@ -0,0 +1,17 @@ +package org.qualityannotate.quality.sonarqube.client; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; + +public record Facet(List values) { + + /** + * @param val human readable name of the Facet + * @param count some metric count + */ + @JsonIgnoreProperties(ignoreUnknown = true) + public record FacetValue(String val, String count) { + + } +} diff --git a/src/main/java/org/qualityannotate/quality/sonarqube/client/IssueSearch.java b/src/main/java/org/qualityannotate/quality/sonarqube/client/IssueSearch.java new file mode 100644 index 0000000..75902f2 --- /dev/null +++ b/src/main/java/org/qualityannotate/quality/sonarqube/client/IssueSearch.java @@ -0,0 +1,6 @@ +package org.qualityannotate.quality.sonarqube.client; + +import java.util.List; + +public record IssueSearch(Paging paging, List issues, List components, List facets) { +} diff --git a/src/main/java/org/qualityannotate/quality/sonarqube/client/Measure.java b/src/main/java/org/qualityannotate/quality/sonarqube/client/Measure.java index 5c551d1..549213a 100644 --- a/src/main/java/org/qualityannotate/quality/sonarqube/client/Measure.java +++ b/src/main/java/org/qualityannotate/quality/sonarqube/client/Measure.java @@ -1,10 +1,8 @@ package org.qualityannotate.quality.sonarqube.client; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Value; @Value -@JsonIgnoreProperties(ignoreUnknown = true) public class Measure { /** * "metric": "complexity", diff --git a/src/main/java/org/qualityannotate/quality/sonarqube/client/Paging.java b/src/main/java/org/qualityannotate/quality/sonarqube/client/Paging.java new file mode 100644 index 0000000..8f46183 --- /dev/null +++ b/src/main/java/org/qualityannotate/quality/sonarqube/client/Paging.java @@ -0,0 +1,7 @@ +package org.qualityannotate.quality.sonarqube.client; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record Paging(int pageIndex, int pageSize, int total) { +} diff --git a/src/main/java/org/qualityannotate/quality/sonarqube/client/SonarqubeApiClient.java b/src/main/java/org/qualityannotate/quality/sonarqube/client/SonarqubeApiClient.java index 0de0755..0d15ac4 100644 --- a/src/main/java/org/qualityannotate/quality/sonarqube/client/SonarqubeApiClient.java +++ b/src/main/java/org/qualityannotate/quality/sonarqube/client/SonarqubeApiClient.java @@ -1,6 +1,8 @@ package org.qualityannotate.quality.sonarqube.client; +import jakarta.annotation.Nullable; +import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.QueryParam; @@ -10,15 +12,48 @@ @Path("/api") public interface SonarqubeApiClient { /** - * docs + * docs * * @param project Component key. E.g. my_project * @param prId Pull request id. Not available in the community edition. E.g. 1234 * @param metricKeys Comma-separated list of additional fields that can be returned in the response. * E.g."new_coverage,new_sqale_debt_ratio,new_uncovered_conditions" - * @return */ @GET() @Path("/measures/component") ComponentMeasures getComponentMeasures(@QueryParam("component") String project, @QueryParam("pullRequest") String prId, @QueryParam("metricsKeys") String metricKeys); + + + /** + * docs + * TODO onComponentOnly might be interesting to retrieve global metrics + * + * @param project Component key. E.g. my_project + * @param prId Pull request id. Not available in the community edition. E.g. 1234 + * @param branch BBranch key. Not available in the community edition. E.g. feature/test + * @param additionalFields Comma-separated list of the optional fields to be returned in response. + * @param facets Comma-separated list of the facets to be computed. No facet is computed by default. + * @param pageNumber Page number. E.g. 1 + * @param pageSize Page size. Highest number is 500 + * @param resolved To match resolved or unresolved issues + * @param sort Sort the issues + * @param severities Comma-separated list of severities + * @param statuses Comma-separated list of statuses + */ + @GET() + @Path("/issues/search") + IssueSearch getIssuesSearch(@QueryParam("componentKeys") String project, + @Nullable @QueryParam("pullRequest") String prId, + @Nullable @QueryParam("branch") String branch, + @Nullable @QueryParam("additionalFields") @DefaultValue("_all") String additionalFields, + @Nullable @QueryParam("facets") @DefaultValue("severities,types") String facets, + @Nullable @QueryParam("p") @DefaultValue("1") String pageNumber, + // 500 is the biggest size + @Nullable @QueryParam("ps") @DefaultValue("500") String pageSize, + @Nullable @QueryParam("resolved") @DefaultValue("false") String resolved, + @Nullable @QueryParam("s") @DefaultValue("FILE_LINE") String sort, + // INFO got removed here + @Nullable @QueryParam("severities") @DefaultValue("MINOR,MAJOR,CRITICAL,BLOCKER") String severities, + @Nullable @QueryParam("statuses") @DefaultValue("OPEN,REOPENED,CONFIRMED") String statuses + ); } diff --git a/src/main/java/org/qualityannotate/quality/sonarqube/client/SqIssue.java b/src/main/java/org/qualityannotate/quality/sonarqube/client/SqIssue.java new file mode 100644 index 0000000..381de18 --- /dev/null +++ b/src/main/java/org/qualityannotate/quality/sonarqube/client/SqIssue.java @@ -0,0 +1,171 @@ +package org.qualityannotate.quality.sonarqube.client; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Value; + +/** + * Single issue inside the project. + *

+ * Following elements are not converted: + *

+ *      {
+ *       "messageFormattings": [
+ *         {
+ *           "start": 0,
+ *           "end": 4,
+ *           "type": "CODE"
+ *         }
+ *       ],
+ *       "creationDate": "2013-05-13T17:55:39+0200",
+ *       "updateDate": "2013-05-13T17:55:39+0200",
+ *       "tags": [
+ *         "bug"
+ *       ],
+ *       "comments": [
+ *         {
+ *           "key": "7d7c56f5-7b5a-41b9-87f8-36fa70caa5ba",
+ *           "login": "john.smith",
+ *           "htmlText": "Must be "public"!",
+ *           "markdown": "Must be \"public\"!",
+ *           "updatable": false,
+ *           "createdAt": "2013-05-13T18:08:34+0200"
+ *         }
+ *       ],
+ *       "attr": {
+ *         "jira-issue-key": "SONAR-1234"
+ *       },
+ *       "transitions": [
+ *         "reopen"
+ *       ],
+ *       "actions": [
+ *         "comment"
+ *       ],
+ *       "flows": [
+ *         {
+ *           "locations": [
+ *             {
+ *               "textRange": {
+ *                 "startLine": 16,
+ *                 "endLine": 16,
+ *                 "startOffset": 0,
+ *                 "endOffset": 30
+ *               },
+ *               "msg": "Expected position: 5",
+ *               "msgFormattings": [
+ *                 {
+ *                   "start": 0,
+ *                   "end": 4,
+ *                   "type": "CODE"
+ *                 }
+ *               ]
+ *             }
+ *           ]
+ *         },
+ *         {
+ *           "locations": [
+ *             {
+ *               "textRange": {
+ *                 "startLine": 15,
+ *                 "endLine": 15,
+ *                 "startOffset": 0,
+ *                 "endOffset": 37
+ *               },
+ *               "msg": "Expected position: 6",
+ *               "msgFormattings": []
+ *             }
+ *           ]
+ *         }
+ *       ],
+ *       "quickFixAvailable": false,
+ *       "ruleDescriptionContextKey": "spring"
+ *     }
+ * 
+ */ +@Value +@JsonIgnoreProperties(ignoreUnknown = true) +public class SqIssue { + /** + * unique identifier. E.g. 01fc972e-2a3c-433e-bcae-0bd7f88f5123 + */ + String key; + + /** + * E.g. com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest + */ + String component; + + /** + * E.g. com.github.kevinsawicki:http-request + */ + String project; + + /** + * E.g. java:S1144 + */ + String rule; + + /** + * E.g. RESOLVED + */ + String status; + + /** + * E.g. WONTFIX + */ + String resolution; + + /** + * E.g. MAJOR + */ + String severity; + + /** + * E.g. Remove this unused private "getKee" method. + */ + String message; + + /** + * Optional Line-number E.g. 81 + */ + Integer lineNumber; + + /** + * unique hash for this issue. E.g. a227e508d6646b55a086ee11d63b21e9 + */ + String hash; + /** + * E.g. Developer 1 + */ + String author; + /** + * E.g. 2h1min + */ + String effort; + /** + * E.g. CODE_SMELL + */ + String type; + + /** + * E.g. startLine: 2, endline: 2, startOffset: 0, endOffset: 204 + */ + TextRange textRange; + + boolean quickFixAvailable; + + + /** + * Computing the file-path requires to know the project-identifier. + * It may not work if sonarqube was just using compiled files for analysis. + * + * @return File-path. E.g. src/main/Test.java + */ + public String getPath(String project) { + return component.replace(project, "").substring(1); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + + public record TextRange(int startLine, int endLine, int startOffset, int endOffset) { + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f0a8e32..25c2005 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,5 +1,20 @@ +# If your project uses sonarqube (https://www.sonarsource.com/products/sonarqube/) configure it here +# the properties url, token, project, pull_request are most important to configure sonarqube: + # base-url for your sonarqube api. E.g. https://mysonar.example.com/ url: "test" + # How to create a token: https://docs.sonarsource.com/sonarqube/9.8/user-guide/user-account/generating-and-using-tokens/ token: "test" + # Name of your project project: "test" + # Pullrequest id pull_request: "test" + + # retrieve these metric types for the global metrics. An empty list will probably return all + # https://docs.sonarsource.com/sonarqube/9.9/user-guide/metric-definitions/ + global_metric_types: + - "new_coverage" + - "new_sqale_debt_ratio" + - "new_uncovered_conditions" + - "new_violations" + diff --git a/src/test/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityToolTest.java b/src/test/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityToolTest.java index 182a5ac..f58e518 100644 --- a/src/test/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityToolTest.java +++ b/src/test/java/org/qualityannotate/quality/sonarqube/SonarqubeQualityToolTest.java @@ -18,10 +18,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.qualityannotate.api.qualitytool.GlobalMetrics; +import org.qualityannotate.api.qualitytool.Issue; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; +import java.util.List; import java.util.Map; import static com.github.tomakehurst.wiremock.client.WireMock.*; @@ -59,6 +61,11 @@ public String project() { public String pullRequest() { return "Test_Pr"; } + + @Override + public List globalMetricTypes() { + return List.of("test-metrics-1", "test-metrics-2"); + } } @Inject @@ -84,11 +91,23 @@ void testGetGlobalMetrics() { .withBasicAuth("", config.token()) .withQueryParam("component", equalTo(config.project())) .withQueryParam("pullRequest", equalTo(config.pullRequest())) - .withQueryParam("metricsKeys", not(equalTo("test"))) + .withQueryParam("metricsKeys", equalTo("test-metrics-1,test-metrics-2")) .willReturn(okJson(content))); assertEquals(new GlobalMetrics(Map.of("New issues", "25", "Lines of code", "114", "Complexity", "12")), cut.getGlobalMetrics()); } + @Test + void testGetIssues() { + String content = getContent("issues/search/sonarqube_issues.json"); + WM.stubFor(get(urlPathTemplate("/api/issues/search")) + .withBasicAuth("", config.token()) + .withQueryParam("componentKeys", equalTo(config.project())) + .willReturn(okJson(content))); + assertEquals(List.of(new Issue("om.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest", + null, "Remove this unused private \"getKee\" method.", "MAJOR")), + cut.getIssues()); + } + private String getContent(String name) { try { return Files.read(Paths.get("src/test/resources/sonarqube/api/" + name).toFile(), StandardCharsets.UTF_8); diff --git a/src/test/resources/sonarqube/api/issues/search/sonarqube_issues.json b/src/test/resources/sonarqube/api/issues/search/sonarqube_issues.json new file mode 100644 index 0000000..6f9e62c --- /dev/null +++ b/src/test/resources/sonarqube/api/issues/search/sonarqube_issues.json @@ -0,0 +1,133 @@ +{ + "paging": { + "pageIndex": 1, + "pageSize": 100, + "total": 1 + }, + "issues": [ + { + "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", + "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest", + "project": "com.github.kevinsawicki:http-request", + "rule": "java:S1144", + "status": "RESOLVED", + "resolution": "WONTFIX", + "severity": "MAJOR", + "message": "Remove this unused private \"getKee\" method.", + "messageFormattings": [ + { + "start": 0, + "end": 4, + "type": "CODE" + } + ], + "line": 81, + "hash": "a227e508d6646b55a086ee11d63b21e9", + "author": "Developer 1", + "effort": "2h1min", + "creationDate": "2013-05-13T17:55:39+0200", + "updateDate": "2013-05-13T17:55:39+0200", + "tags": [ + "bug" + ], + "type": "CODE_SMELL", + "comments": [ + { + "key": "7d7c56f5-7b5a-41b9-87f8-36fa70caa5ba", + "login": "john.smith", + "htmlText": "Must be "public"!", + "markdown": "Must be \"public\"!", + "updatable": false, + "createdAt": "2013-05-13T18:08:34+0200" + } + ], + "attr": { + "jira-issue-key": "SONAR-1234" + }, + "transitions": [ + "reopen" + ], + "actions": [ + "comment" + ], + "textRange": { + "startLine": 2, + "endLine": 2, + "startOffset": 0, + "endOffset": 204 + }, + "flows": [ + { + "locations": [ + { + "textRange": { + "startLine": 16, + "endLine": 16, + "startOffset": 0, + "endOffset": 30 + }, + "msg": "Expected position: 5", + "msgFormattings": [ + { + "start": 0, + "end": 4, + "type": "CODE" + } + ] + } + ] + }, + { + "locations": [ + { + "textRange": { + "startLine": 15, + "endLine": 15, + "startOffset": 0, + "endOffset": 37 + }, + "msg": "Expected position: 6", + "msgFormattings": [] + } + ] + } + ], + "quickFixAvailable": false, + "ruleDescriptionContextKey": "spring" + } + ], + "components": [ + { + "key": "com.github.kevinsawicki:http-request:src/main/java/com/github/kevinsawicki/http/HttpRequest.java", + "enabled": true, + "qualifier": "FIL", + "name": "HttpRequest.java", + "longName": "src/main/java/com/github/kevinsawicki/http/HttpRequest.java", + "path": "src/main/java/com/github/kevinsawicki/http/HttpRequest.java" + }, + { + "key": "com.github.kevinsawicki:http-request", + "enabled": true, + "qualifier": "TRK", + "name": "http-request", + "longName": "http-request" + } + ], + "rules": [ + { + "key": "java:S1144", + "name": "Unused \"private\" methods should be removed", + "status": "READY", + "lang": "java", + "langName": "Java" + } + ], + "users": [ + { + "login": "admin", + "name": "Administrator", + "active": true, + "avatar": "ab0ec6adc38ad44a15105f207394946f" + } + ] +} \ No newline at end of file