]> source.dussan.org Git - sonarqube.git/blob
e13d1590a9f5a29cfeb5628ed1cc01f9b8fc4ff2
[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.ce.task.projectanalysis.qualitymodel;
21
22 import java.util.Arrays;
23 import javax.annotation.Nullable;
24 import org.assertj.core.api.Assertions;
25 import org.assertj.core.data.Offset;
26 import org.junit.jupiter.api.BeforeEach;
27 import org.junit.jupiter.api.Test;
28 import org.junit.jupiter.api.extension.RegisterExtension;
29 import org.sonar.api.rules.RuleType;
30 import org.sonar.ce.task.projectanalysis.component.Component;
31 import org.sonar.ce.task.projectanalysis.component.FileAttributes;
32 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
33 import org.sonar.ce.task.projectanalysis.component.VisitorsCrawler;
34 import org.sonar.ce.task.projectanalysis.issue.ComponentIssuesRepositoryRule;
35 import org.sonar.ce.task.projectanalysis.issue.FillComponentIssuesVisitorRule;
36 import org.sonar.ce.task.projectanalysis.issue.NewIssueClassifier;
37 import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
38 import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
39 import org.sonar.core.issue.DefaultIssue;
40 import org.sonar.core.util.UuidFactoryFast;
41 import org.sonar.core.util.Uuids;
42 import org.sonar.server.measure.Rating;
43
44 import static org.assertj.core.api.Assertions.assertThat;
45 import static org.mockito.ArgumentMatchers.any;
46 import static org.mockito.ArgumentMatchers.eq;
47 import static org.mockito.Mockito.mock;
48 import static org.mockito.Mockito.when;
49 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
50 import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
51 import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
52 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED;
53 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY;
54 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS;
55 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY;
56 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS;
57 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY;
58 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REVIEW_RATING;
59 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY;
60 import static org.sonar.api.rule.Severity.MAJOR;
61 import static org.sonar.api.rule.Severity.MINOR;
62 import static org.sonar.ce.task.projectanalysis.component.Component.Type.DIRECTORY;
63 import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
64 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
65 import static org.sonar.ce.task.projectanalysis.measure.MeasureAssert.assertThat;
66 import static org.sonar.server.measure.Rating.A;
67 import static org.sonar.server.measure.Rating.B;
68 import static org.sonar.server.measure.Rating.C;
69 import static org.sonar.server.measure.Rating.D;
70 import static org.sonar.server.measure.Rating.E;
71 import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING;
72 import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY;
73
74 class NewSecurityReviewMeasuresVisitorTest {
75   private static final Offset<Double> VALUE_COMPARISON_OFFSET = Offset.offset(0.01);
76   private static final String LANGUAGE_KEY_1 = "lKey1";
77
78   private static final int PROJECT_REF = 1;
79   private static final int ROOT_DIR_REF = 12;
80   private static final int DIRECTORY_REF = 123;
81   private static final int FILE_1_REF = 1231;
82   private static final int FILE_2_REF = 1232;
83
84   private static final Component ROOT_PROJECT = builder(Component.Type.PROJECT, PROJECT_REF).setKey("project")
85     .addChildren(
86       builder(DIRECTORY, ROOT_DIR_REF).setKey("dir")
87         .addChildren(
88           builder(DIRECTORY, DIRECTORY_REF).setKey("directory")
89             .addChildren(
90               builder(FILE, FILE_1_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_KEY_1, 1)).setKey("file1").build(),
91               builder(FILE, FILE_2_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_KEY_1, 1)).setKey("file2").build())
92             .build())
93         .build())
94     .build();
95
96   @RegisterExtension
97   private final TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
98   @RegisterExtension
99   private final MetricRepositoryRule metricRepository = new MetricRepositoryRule()
100     .add(NEW_SECURITY_REVIEW_RATING)
101     .add(NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING)
102     .add(NEW_SECURITY_HOTSPOTS_REVIEWED)
103     .add(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS)
104     .add(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS);
105   @RegisterExtension
106   private final MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
107   private final ComponentIssuesRepositoryRule componentIssuesRepositoryRule = new ComponentIssuesRepositoryRule(treeRootHolder);
108   @RegisterExtension
109   private final FillComponentIssuesVisitorRule fillComponentIssuesVisitorRule =
110     new FillComponentIssuesVisitorRule(componentIssuesRepositoryRule, treeRootHolder);
111   private final NewIssueClassifier newIssueClassifier = mock(NewIssueClassifier.class);
112   private final VisitorsCrawler underTest = new VisitorsCrawler(Arrays.asList(fillComponentIssuesVisitorRule,
113     new NewSecurityReviewMeasuresVisitor(componentIssuesRepositoryRule, measureRepository, metricRepository, newIssueClassifier)));
114
115   @BeforeEach
116   void setup() {
117     when(newIssueClassifier.isEnabled()).thenReturn(true);
118   }
119
120   @Test
121   void compute_measures_when_100_percent_hotspots_reviewed() {
122     treeRootHolder.setRoot(ROOT_PROJECT);
123     fillComponentIssuesVisitorRule.setIssues(FILE_1_REF,
124       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
125       // Should not be taken into account
126       oldHotspot(STATUS_TO_REVIEW, null),
127       oldHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
128       newIssue());
129     fillComponentIssuesVisitorRule.setIssues(FILE_2_REF,
130       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
131       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED));
132     fillComponentIssuesVisitorRule.setIssues(ROOT_DIR_REF,
133       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED));
134
135     underTest.visit(ROOT_PROJECT);
136
137     verifyRatingAndReviewedMeasures(FILE_1_REF, A, A, 100.0);
138     verifyRatingAndReviewedMeasures(FILE_2_REF, A, A, 100.0);
139     verifyRatingAndReviewedMeasures(DIRECTORY_REF, A, A, 100.0);
140     verifyRatingAndReviewedMeasures(ROOT_DIR_REF, A, A, 100.0);
141     verifyRatingAndReviewedMeasures(PROJECT_REF, A, A, 100.0);
142   }
143
144   @Test
145   void compute_measures_when_more_than_80_percent_hotspots_reviewed() {
146     treeRootHolder.setRoot(ROOT_PROJECT);
147     fillComponentIssuesVisitorRule.setIssues(FILE_1_REF,
148       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
149       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
150       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
151       // Should not be taken into account
152       newIssue());
153     fillComponentIssuesVisitorRule.setIssues(FILE_2_REF,
154       newHotspot(STATUS_TO_REVIEW, null),
155       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
156       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
157       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
158       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
159       // Should not be taken into account
160       oldHotspot(STATUS_TO_REVIEW, null),
161       oldHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
162       newIssue());
163
164     underTest.visit(ROOT_PROJECT);
165
166     verifyRatingAndReviewedMeasures(FILE_1_REF, A, A, 100.0);
167     verifyRatingAndReviewedMeasures(FILE_2_REF, A, B, 80.0);
168     verifyRatingAndReviewedMeasures(DIRECTORY_REF, A, B, 87.5);
169     verifyRatingAndReviewedMeasures(ROOT_DIR_REF, A, B, 87.5);
170     verifyRatingAndReviewedMeasures(PROJECT_REF, A, B, 87.5);
171   }
172
173   @Test
174   void compute_measures_when_more_than_70_percent_hotspots_reviewed() {
175     treeRootHolder.setRoot(ROOT_PROJECT);
176     fillComponentIssuesVisitorRule.setIssues(FILE_1_REF,
177       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
178       // Should not be taken into account
179       newIssue());
180     fillComponentIssuesVisitorRule.setIssues(FILE_2_REF,
181       newHotspot(STATUS_TO_REVIEW, null),
182       newHotspot(STATUS_TO_REVIEW, null),
183       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
184       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
185       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
186       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
187       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
188       // Should not be taken into account
189       oldHotspot(STATUS_TO_REVIEW, null),
190       oldHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
191       newIssue());
192
193     underTest.visit(ROOT_PROJECT);
194
195     verifyRatingAndReviewedMeasures(FILE_1_REF, A, A, 100.0);
196     verifyRatingAndReviewedMeasures(FILE_2_REF, B, B, 71.42);
197     verifyRatingAndReviewedMeasures(DIRECTORY_REF, B, B, 75.0);
198     verifyRatingAndReviewedMeasures(ROOT_DIR_REF, B, B, 75.0);
199     verifyRatingAndReviewedMeasures(PROJECT_REF, B, B, 75.0);
200   }
201
202   @Test
203   void compute_measures_when_more_than_50_percent_hotspots_reviewed() {
204     treeRootHolder.setRoot(ROOT_PROJECT);
205     fillComponentIssuesVisitorRule.setIssues(FILE_1_REF,
206       newHotspot(STATUS_TO_REVIEW, null),
207       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
208       // Should not be taken into account
209       newIssue());
210     fillComponentIssuesVisitorRule.setIssues(FILE_2_REF,
211       newHotspot(STATUS_TO_REVIEW, null),
212       newHotspot(STATUS_TO_REVIEW, null),
213       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
214       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
215       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
216       // Should not be taken into account
217       oldHotspot(STATUS_TO_REVIEW, null),
218       oldHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
219       newIssue());
220
221     underTest.visit(ROOT_PROJECT);
222
223     verifyRatingAndReviewedMeasures(FILE_1_REF, C, C, 50.0);
224     verifyRatingAndReviewedMeasures(FILE_2_REF, C, C, 60.0);
225     verifyRatingAndReviewedMeasures(DIRECTORY_REF, C, C, 57.14);
226     verifyRatingAndReviewedMeasures(ROOT_DIR_REF, C, C, 57.14);
227     verifyRatingAndReviewedMeasures(PROJECT_REF, C, C, 57.14);
228   }
229
230   @Test
231   void compute_measures_when_more_30_than_percent_hotspots_reviewed() {
232     treeRootHolder.setRoot(ROOT_PROJECT);
233     fillComponentIssuesVisitorRule.setIssues(FILE_1_REF,
234       newHotspot(STATUS_TO_REVIEW, null),
235       newHotspot(STATUS_TO_REVIEW, null),
236       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
237       // Should not be taken into account
238       newIssue());
239     fillComponentIssuesVisitorRule.setIssues(FILE_2_REF,
240       newHotspot(STATUS_TO_REVIEW, null),
241       newHotspot(STATUS_TO_REVIEW, null),
242       newHotspot(STATUS_TO_REVIEW, null),
243       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
244       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
245       // Should not be taken into account
246       oldHotspot(STATUS_TO_REVIEW, null),
247       oldHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
248       newIssue());
249
250     underTest.visit(ROOT_PROJECT);
251
252     verifyRatingAndReviewedMeasures(FILE_1_REF, D, D, 33.33);
253     verifyRatingAndReviewedMeasures(FILE_2_REF, D, D, 40.0);
254     verifyRatingAndReviewedMeasures(DIRECTORY_REF, D, D, 37.5);
255     verifyRatingAndReviewedMeasures(ROOT_DIR_REF, D, D, 37.5);
256     verifyRatingAndReviewedMeasures(PROJECT_REF, D, D, 37.5);
257   }
258
259   @Test
260   void compute_measures_when_less_than_30_percent_hotspots_reviewed() {
261     treeRootHolder.setRoot(ROOT_PROJECT);
262     fillComponentIssuesVisitorRule.setIssues(FILE_1_REF,
263       newHotspot(STATUS_TO_REVIEW, null),
264       newHotspot(STATUS_TO_REVIEW, null),
265       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
266       // Should not be taken into account
267       newIssue());
268     fillComponentIssuesVisitorRule.setIssues(FILE_2_REF,
269       newHotspot(STATUS_TO_REVIEW, null),
270       newHotspot(STATUS_TO_REVIEW, null),
271       newHotspot(STATUS_TO_REVIEW, null),
272       // Should not be taken into account
273       oldHotspot(STATUS_TO_REVIEW, null),
274       oldHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
275       newIssue());
276
277     underTest.visit(ROOT_PROJECT);
278
279     verifyRatingAndReviewedMeasures(FILE_1_REF, D, D, 33.33);
280     verifyRatingAndReviewedMeasures(FILE_2_REF, E, D, 0.0);
281     verifyRatingAndReviewedMeasures(DIRECTORY_REF, E, D, 16.66);
282     verifyRatingAndReviewedMeasures(ROOT_DIR_REF, E, D, 16.66);
283     verifyRatingAndReviewedMeasures(PROJECT_REF, E, D, 16.66);
284   }
285
286   @Test
287   void compute_A_rating_and_no_percent_when_no_new_hotspot_on_new_code() {
288     treeRootHolder.setRoot(ROOT_PROJECT);
289     fillComponentIssuesVisitorRule.setIssues(FILE_1_REF,
290       oldHotspot(STATUS_TO_REVIEW, null),
291       oldHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
292       newIssue());
293
294     underTest.visit(ROOT_PROJECT);
295
296     verifyRatingAndReviewedMeasures(PROJECT_REF, A, A, null);
297   }
298
299   @Test
300   void compute_status_related_measures() {
301     treeRootHolder.setRoot(ROOT_PROJECT);
302     fillComponentIssuesVisitorRule.setIssues(FILE_1_REF,
303       newHotspot(STATUS_TO_REVIEW, null),
304       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
305       // Should not be taken into account
306       newIssue());
307     fillComponentIssuesVisitorRule.setIssues(FILE_2_REF,
308       newHotspot(STATUS_TO_REVIEW, null),
309       newHotspot(STATUS_TO_REVIEW, null),
310       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
311       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
312       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED),
313       newIssue());
314
315     underTest.visit(ROOT_PROJECT);
316
317     verifyHotspotStatusMeasures(FILE_1_REF, null, null);
318     verifyHotspotStatusMeasures(FILE_2_REF, null, null);
319     verifyHotspotStatusMeasures(DIRECTORY_REF, null, null);
320     verifyHotspotStatusMeasures(ROOT_DIR_REF, null, null);
321     verifyHotspotStatusMeasures(PROJECT_REF, 4, 3);
322   }
323
324   @Test
325   void compute_0_status_related_measures_when_no_hotspot() {
326     treeRootHolder.setRoot(ROOT_PROJECT);
327
328     underTest.visit(ROOT_PROJECT);
329
330     verifyHotspotStatusMeasures(PROJECT_REF, 0, 0);
331   }
332
333   @Test
334   void no_measure_if_there_is_no_period() {
335     when(newIssueClassifier.isEnabled()).thenReturn(false);
336     treeRootHolder.setRoot(ROOT_PROJECT);
337     fillComponentIssuesVisitorRule.setIssues(FILE_1_REF,
338       newHotspot(STATUS_TO_REVIEW, null),
339       newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED));
340
341     underTest.visit(ROOT_PROJECT);
342
343     assertThat(measureRepository.getAddedRawMeasures(PROJECT_REF).values()).isEmpty();
344   }
345
346   private void verifyRatingAndReviewedMeasures(int componentRef, Rating expectedReviewRating,
347     Rating expectedSoftwareQualitySecurityReviewRating, @Nullable Double expectedHotspotsReviewed) {
348     assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_REVIEW_RATING_KEY)).hasValue(expectedReviewRating.getIndex());
349     assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY)).hasValue(expectedSoftwareQualitySecurityReviewRating.getIndex());
350     if (expectedHotspotsReviewed != null) {
351       assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY)).hasValue(expectedHotspotsReviewed,
352         VALUE_COMPARISON_OFFSET);
353     } else {
354       assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY)).isAbsent();
355     }
356   }
357
358   private void verifyHotspotStatusMeasures(int componentRef, @Nullable Integer hotspotsReviewed, @Nullable Integer hotspotsToReview) {
359     if (hotspotsReviewed == null) {
360       Assertions.assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)).isEmpty();
361     } else {
362       assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)).hasValue(hotspotsReviewed);
363     }
364     if (hotspotsReviewed == null) {
365       Assertions.assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)).isEmpty();
366     } else {
367       assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)).hasValue(hotspotsToReview);
368     }
369   }
370
371   private DefaultIssue newHotspot(String status, @Nullable String resolution) {
372     return createHotspot(status, resolution, true);
373   }
374
375   private DefaultIssue oldHotspot(String status, @Nullable String resolution) {
376     return createHotspot(status, resolution, false);
377   }
378
379   private DefaultIssue createHotspot(String status, @Nullable String resolution, boolean isNew) {
380     DefaultIssue issue = new DefaultIssue()
381       .setKey(UuidFactoryFast.getInstance().create())
382       .setSeverity(MINOR)
383       .setStatus(status)
384       .setResolution(resolution)
385       .setType(RuleType.SECURITY_HOTSPOT);
386     when(newIssueClassifier.isNew(any(), eq(issue))).thenReturn(isNew);
387     return issue;
388   }
389
390   private DefaultIssue newIssue() {
391     DefaultIssue issue = new DefaultIssue()
392       .setKey(Uuids.create())
393       .setSeverity(MAJOR)
394       .setType(RuleType.BUG);
395     when(newIssueClassifier.isNew(any(), eq(issue))).thenReturn(false);
396     return issue;
397
398   }
399
400 }