]> source.dussan.org Git - sonarqube.git/blob
956211c3f8e70d3699afc6cb7da419ff6fd99a62
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.scanner.externalissue;
21
22 import com.google.gson.Gson;
23 import java.io.IOException;
24 import java.io.Reader;
25 import java.nio.charset.StandardCharsets;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import org.junit.Before;
30 import org.junit.Rule;
31 import org.junit.Test;
32 import org.slf4j.event.Level;
33 import org.sonar.api.testfixtures.log.LogAndArguments;
34 import org.sonar.api.testfixtures.log.LogTester;
35 import org.sonar.core.documentation.DocumentationLinkGenerator;
36
37 import static org.assertj.core.api.Assertions.assertThat;
38 import static org.assertj.core.api.Assertions.assertThatCode;
39 import static org.assertj.core.api.Assertions.assertThatThrownBy;
40 import static org.mockito.Mockito.mock;
41 import static org.mockito.Mockito.when;
42
43 public class ExternalIssueReportValidatorTest {
44
45   private static final String DEPRECATED_REPORTS_LOCATION = "src/test/resources/org/sonar/scanner/externalissue/";
46   private static final String REPORTS_LOCATION = "src/test/resources/org/sonar/scanner/externalissue/cct/";
47   private static final String URL = "/analyzing-source-code/importing-external-issues/generic-issue-import-format/";
48   private static final String TEST_URL = "test-url";
49
50   private final Gson gson = new Gson();
51   private final Path reportPath = Paths.get("report-path");
52   private final DocumentationLinkGenerator documentationLinkGenerator = mock(DocumentationLinkGenerator.class);
53   private final ExternalIssueReportValidator validator = new ExternalIssueReportValidator(documentationLinkGenerator);
54
55   @Rule
56   public LogTester logTester = new LogTester();
57
58   @Before
59   public void setUp() {
60     when(documentationLinkGenerator.getDocumentationLink(URL)).thenReturn(TEST_URL);
61   }
62
63   @Test
64   public void validate_whenInvalidReport_shouldThrowException() throws IOException {
65     ExternalIssueReport report = readInvalidReport(DEPRECATED_REPORTS_LOCATION);
66     assertThatThrownBy(() -> validator.validate(report, reportPath))
67       .isInstanceOf(IllegalStateException.class)
68       .hasMessage("Failed to parse report 'report-path': invalid report detected.");
69   }
70
71   @Test
72   public void validate_whenCorrect_shouldNotThrowException() throws IOException {
73     ExternalIssueReport report = read(REPORTS_LOCATION);
74     assertThatCode(() -> validator.validate(report, reportPath)).doesNotThrowAnyException();
75   }
76
77   @Test
78   public void validate_whenDuplicateRuleIdFound_shouldThrowException() throws IOException {
79     ExternalIssueReport report = read(REPORTS_LOCATION);
80     report.rules[0].id = "rule2";
81
82     assertThatThrownBy(() -> validator.validate(report, reportPath))
83       .isInstanceOf(IllegalStateException.class)
84       .hasMessage("Failed to parse report 'report-path': found duplicate rule ID 'rule2'.");
85   }
86
87   @Test
88   public void validate_whenMissingMandatoryCleanCodeAttributeField_shouldThrowException() throws IOException {
89     ExternalIssueReport report = read(REPORTS_LOCATION);
90     report.rules[0].cleanCodeAttribute = null;
91
92     assertThatThrownBy(() -> validator.validate(report, reportPath))
93       .isInstanceOf(IllegalStateException.class)
94       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'cleanCodeAttribute'.");
95   }
96
97   @Test
98   public void validate_whenMissingEngineIdField_shouldThrowException() throws IOException {
99     ExternalIssueReport report = read(REPORTS_LOCATION);
100     report.rules[0].engineId = null;
101
102     assertThatThrownBy(() -> validator.validate(report, reportPath))
103       .isInstanceOf(IllegalStateException.class)
104       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'engineId'.");
105   }
106
107   @Test
108   public void validate_whenMissingFilepathFieldForPrimaryLocation_shouldThrowException() throws IOException {
109     ExternalIssueReport report = read(REPORTS_LOCATION);
110     report.issues[0].primaryLocation.filePath = null;
111
112     assertThatThrownBy(() -> validator.validate(report, reportPath))
113       .isInstanceOf(IllegalStateException.class)
114       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'filePath' in the primary location of the issue.");
115   }
116
117   @Test
118   public void validate_whenMissingImpactsField_shouldThrowException() throws IOException {
119     ExternalIssueReport report = read(REPORTS_LOCATION);
120     report.rules[0].impacts = null;
121
122     assertThatThrownBy(() -> validator.validate(report, reportPath))
123       .isInstanceOf(IllegalStateException.class)
124       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'impacts'.");
125   }
126
127   @Test
128   public void validate_whenMissingMessageFieldForPrimaryLocation_shouldThrowException() throws IOException {
129     ExternalIssueReport report = read(REPORTS_LOCATION);
130     report.issues[0].primaryLocation.message = null;
131
132     assertThatThrownBy(() -> validator.validate(report, reportPath))
133       .isInstanceOf(IllegalStateException.class)
134       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'message' in the primary location of the issue.");
135   }
136
137   @Test
138   public void validate_whenMissingStartLineFieldForPrimaryLocation_shouldThrowException() throws IOException {
139     ExternalIssueReport report = read(REPORTS_LOCATION);
140     report.issues[0].primaryLocation.textRange.startLine = null;
141
142     assertThatThrownBy(() -> validator.validate(report, reportPath))
143       .isInstanceOf(IllegalStateException.class)
144       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'startLine of the text range' in the primary location of the issue.");
145   }
146
147   @Test
148   public void validate_whenReportMissingFilePathForSecondaryLocation_shouldThrowException() throws IOException {
149     ExternalIssueReport report = read(REPORTS_LOCATION);
150     report.issues[3].secondaryLocations[0].filePath = null;
151
152     assertThatThrownBy(() -> validator.validate(report, reportPath))
153       .isInstanceOf(IllegalStateException.class)
154       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'filePath' in a secondary location of the issue.");
155   }
156
157   @Test
158   public void validate_whenReportMissingTextRangeForSecondaryLocation_shouldThrowException() throws IOException {
159     ExternalIssueReport report = read(REPORTS_LOCATION);
160     report.issues[3].secondaryLocations[0].textRange = null;
161
162     assertThatThrownBy(() -> validator.validate(report, reportPath))
163       .isInstanceOf(IllegalStateException.class)
164       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'textRange' in a secondary location of the issue.");
165   }
166
167   @Test
168   public void validate_whenReportMissingTextRangeStartLineForSecondaryLocation_shouldThrowException() throws IOException {
169     ExternalIssueReport report = read(REPORTS_LOCATION);
170     report.issues[3].secondaryLocations[0].textRange.startLine = null;
171
172     assertThatThrownBy(() -> validator.validate(report, reportPath))
173       .isInstanceOf(IllegalStateException.class)
174       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'startLine of the text range' in a secondary location of the issue.");
175   }
176
177   @Test
178   public void validate_whenMissingNameField_shouldThrowException() throws IOException {
179     ExternalIssueReport report = read(REPORTS_LOCATION);
180     report.rules[0].name = null;
181
182     assertThatThrownBy(() -> validator.validate(report, reportPath))
183       .isInstanceOf(IllegalStateException.class)
184       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'name'.");
185   }
186
187   @Test
188   public void validate_whenMissingPrimaryLocationField_shouldThrowException() throws IOException {
189     ExternalIssueReport report = read(REPORTS_LOCATION);
190     report.issues[0].primaryLocation = null;
191
192     assertThatThrownBy(() -> validator.validate(report, reportPath))
193       .isInstanceOf(IllegalStateException.class)
194       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'primaryLocation'.");
195   }
196
197   @Test
198   public void validate_whenMissingOrEmptyRuleIdField_shouldThrowException() throws IOException {
199     String errorMessage = "Failed to parse report 'report-path': missing mandatory field 'id'.";
200
201     ExternalIssueReport report = read(REPORTS_LOCATION);
202     report.rules[0].id = null;
203     assertThatThrownBy(() -> validator.validate(report, reportPath))
204       .isInstanceOf(IllegalStateException.class)
205       .hasMessage(errorMessage);
206
207     report.rules[0].id = "";
208     assertThatThrownBy(() -> validator.validate(report, reportPath))
209       .isInstanceOf(IllegalStateException.class)
210       .hasMessage(errorMessage);
211   }
212
213   @Test
214   public void validate_whenIssueContainsRuleIdNotPresentInReport_shouldThrowException() throws IOException {
215     ExternalIssueReport report = read(REPORTS_LOCATION);
216     report.issues[0].ruleId = "rule-id-not-present";
217
218     assertThatThrownBy(() -> validator.validate(report, reportPath))
219       .isInstanceOf(IllegalStateException.class)
220       .hasMessage("Failed to parse report 'report-path': rule with 'rule-id-not-present' not present.");
221   }
222
223   @Test
224   public void validate_whenIssueRuleIdNotPresentInReport_shouldThrowException() throws IOException {
225     ExternalIssueReport report = read(REPORTS_LOCATION);
226     report.issues[0].ruleId = null;
227
228     assertThatThrownBy(() -> validator.validate(report, reportPath))
229       .isInstanceOf(IllegalStateException.class)
230       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'ruleId'.");
231   }
232
233   @Test
234   public void validate_whenContainsDeprecatedSeverityEntry_shouldThrowException() throws IOException {
235     ExternalIssueReport report = read(REPORTS_LOCATION);
236     report.issues[0].severity = "MAJOR";
237
238     assertThatThrownBy(() -> validator.validate(report, reportPath))
239       .isInstanceOf(IllegalStateException.class)
240       .hasMessage("Deprecated 'severity' field found in the following report: 'report-path'.");
241   }
242
243   @Test
244   public void validate_whenContainsDeprecatedTypeEntry_shouldThrowException() throws IOException {
245     ExternalIssueReport report = read(REPORTS_LOCATION);
246     report.issues[0].type = "BUG";
247
248     assertThatThrownBy(() -> validator.validate(report, reportPath))
249       .isInstanceOf(IllegalStateException.class)
250       .hasMessage("Deprecated 'type' field found in the following report: 'report-path'.");
251   }
252
253   @Test
254   public void validate_whenContainsEmptyImpacts_shouldThrowException() throws IOException {
255     ExternalIssueReport report = read(REPORTS_LOCATION);
256     report.rules[0].impacts = new ExternalIssueReport.Impact[0];
257
258     assertThatThrownBy(() -> validator.validate(report, reportPath))
259       .isInstanceOf(IllegalStateException.class)
260       .hasMessage("Failed to parse report 'report-path': mandatory array 'impacts' not populated.");
261   }
262
263   @Test
264   public void validate_whenDeprecatedReportFormat_shouldValidateWithWarningLog() throws IOException {
265     ExternalIssueReport report = read(DEPRECATED_REPORTS_LOCATION);
266     assertThatCode(() -> validator.validate(report, reportPath)).doesNotThrowAnyException();
267     assertWarningLog();
268   }
269
270   @Test
271   public void validate_whenDeprecatedReportMissingEngineId_shouldThrowException() throws IOException {
272     ExternalIssueReport report = read(DEPRECATED_REPORTS_LOCATION);
273     report.issues[0].engineId = null;
274
275     assertThatThrownBy(() -> validator.validate(report, reportPath))
276       .isInstanceOf(IllegalStateException.class)
277       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'engineId'.");
278     assertWarningLog();
279   }
280
281   @Test
282   public void validate_whenDeprecatedReportMissingFilepathForPrimaryLocation_shouldThrowException() throws IOException {
283     ExternalIssueReport report = read(DEPRECATED_REPORTS_LOCATION);
284     report.issues[0].primaryLocation.filePath = null;
285
286     assertThatThrownBy(() -> validator.validate(report, reportPath))
287       .isInstanceOf(IllegalStateException.class)
288       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'filePath' in the primary location of the issue.");
289     assertWarningLog();
290   }
291
292   @Test
293   public void validate_whenDeprecatedReportMissingMessageForPrimaryLocation_shouldThrowException() throws IOException {
294     ExternalIssueReport report = read(DEPRECATED_REPORTS_LOCATION);
295     report.issues[0].primaryLocation.message = null;
296
297     assertThatThrownBy(() -> validator.validate(report, reportPath))
298       .isInstanceOf(IllegalStateException.class)
299       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'message' in the primary location of the issue.");
300     assertWarningLog();
301   }
302
303   @Test
304   public void validate_whenDeprecatedReportMissingPrimaryLocation_shouldThrowException() throws IOException {
305     ExternalIssueReport report = read(DEPRECATED_REPORTS_LOCATION);
306     report.issues[0].primaryLocation = null;
307
308     assertThatThrownBy(() -> validator.validate(report, reportPath))
309       .isInstanceOf(IllegalStateException.class)
310       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'primaryLocation'.");
311     assertWarningLog();
312   }
313
314   @Test
315   public void validate_whenDeprecatedReportMissingStartLineForPrimaryLocation_shouldThrowException() throws IOException {
316     ExternalIssueReport report = read(DEPRECATED_REPORTS_LOCATION);
317     report.issues[0].primaryLocation.textRange.startLine = null;
318
319     assertThatThrownBy(() -> validator.validate(report, reportPath))
320       .isInstanceOf(IllegalStateException.class)
321       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'startLine of the text range' in the primary location of the issue.");
322     assertWarningLog();
323   }
324
325   @Test
326   public void validate_whenDeprecatedReportMissingFilePathForSecondaryLocation_shouldThrowException() throws IOException {
327     ExternalIssueReport report = read(DEPRECATED_REPORTS_LOCATION);
328     report.issues[3].secondaryLocations[0].filePath = null;
329
330     assertThatThrownBy(() -> validator.validate(report, reportPath))
331       .isInstanceOf(IllegalStateException.class)
332       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'filePath' in a secondary location of the issue.");
333     assertWarningLog();
334   }
335
336   @Test
337   public void validate_whenDeprecatedReportMissingTextRangeForSecondaryLocation_shouldThrowException() throws IOException {
338     ExternalIssueReport report = read(DEPRECATED_REPORTS_LOCATION);
339     report.issues[3].secondaryLocations[0].textRange = null;
340
341     assertThatThrownBy(() -> validator.validate(report, reportPath))
342       .isInstanceOf(IllegalStateException.class)
343       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'textRange' in a secondary location of the issue.");
344     assertWarningLog();
345   }
346
347   @Test
348   public void validate_whenDeprecatedReportMissingTextRangeStartLineForSecondaryLocation_shouldThrowException() throws IOException {
349     ExternalIssueReport report = read(DEPRECATED_REPORTS_LOCATION);
350     report.issues[3].secondaryLocations[0].textRange.startLine = null;
351
352     assertThatThrownBy(() -> validator.validate(report, reportPath))
353       .isInstanceOf(IllegalStateException.class)
354       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'startLine of the text range' in a secondary location of the issue.");
355     assertWarningLog();
356   }
357
358   @Test
359   public void validate_whenDeprecatedReportMissingRuleId_shouldThrowException() throws IOException {
360     ExternalIssueReport report = read(DEPRECATED_REPORTS_LOCATION);
361     report.issues[0].ruleId = null;
362
363     assertThatThrownBy(() -> validator.validate(report, reportPath))
364       .isInstanceOf(IllegalStateException.class)
365       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'ruleId'.");
366     assertWarningLog();
367   }
368
369   @Test
370   public void validate_whenDeprecatedReportMissingSeverity_shouldThrowException() throws IOException {
371     ExternalIssueReport report = read(DEPRECATED_REPORTS_LOCATION);
372     report.issues[0].severity = null;
373
374     assertThatThrownBy(() -> validator.validate(report, reportPath))
375       .isInstanceOf(IllegalStateException.class)
376       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'severity'.");
377     assertWarningLog();
378   }
379
380   @Test
381   public void validate_whenDeprecatedReportMissingType_shouldThrowException() throws IOException {
382     ExternalIssueReport report = read(DEPRECATED_REPORTS_LOCATION);
383     report.issues[0].type = null;
384
385     assertThatThrownBy(() -> validator.validate(report, reportPath))
386       .isInstanceOf(IllegalStateException.class)
387       .hasMessage("Failed to parse report 'report-path': missing mandatory field 'type'.");
388     assertWarningLog();
389   }
390
391   private void assertWarningLog() {
392     assertThat(logTester.getLogs(Level.WARN))
393       .extracting(LogAndArguments::getFormattedMsg)
394       .contains("External issues were imported with a deprecated format which will be removed soon. " +
395         "Please switch to the newest format to fully benefit from Clean Code: " + TEST_URL);
396   }
397
398   private ExternalIssueReport read(String location) throws IOException {
399     Reader reader = Files.newBufferedReader(Paths.get(location + "report.json"), StandardCharsets.UTF_8);
400     return gson.fromJson(reader, ExternalIssueReport.class);
401   }
402
403   private ExternalIssueReport readInvalidReport(String location) throws IOException {
404     Reader reader = Files.newBufferedReader(Paths.get(location + "invalid_report.json"), StandardCharsets.UTF_8);
405     return gson.fromJson(reader, ExternalIssueReport.class);
406   }
407
408 }