3 * Copyright (C) 2009-2024 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.ce.task.projectanalysis.qualitymodel;
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;
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;
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";
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;
84 private static final Component ROOT_PROJECT = builder(Component.Type.PROJECT, PROJECT_REF).setKey("project")
86 builder(DIRECTORY, ROOT_DIR_REF).setKey("dir")
88 builder(DIRECTORY, DIRECTORY_REF).setKey("directory")
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())
97 private final TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
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);
106 private final MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
107 private final ComponentIssuesRepositoryRule componentIssuesRepositoryRule = new ComponentIssuesRepositoryRule(treeRootHolder);
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)));
117 when(newIssueClassifier.isEnabled()).thenReturn(true);
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),
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));
135 underTest.visit(ROOT_PROJECT);
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);
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
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),
164 underTest.visit(ROOT_PROJECT);
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);
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
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),
193 underTest.visit(ROOT_PROJECT);
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);
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
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),
221 underTest.visit(ROOT_PROJECT);
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);
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
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),
250 underTest.visit(ROOT_PROJECT);
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);
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
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),
277 underTest.visit(ROOT_PROJECT);
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);
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),
294 underTest.visit(ROOT_PROJECT);
296 verifyRatingAndReviewedMeasures(PROJECT_REF, A, A, null);
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
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),
315 underTest.visit(ROOT_PROJECT);
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);
325 void compute_0_status_related_measures_when_no_hotspot() {
326 treeRootHolder.setRoot(ROOT_PROJECT);
328 underTest.visit(ROOT_PROJECT);
330 verifyHotspotStatusMeasures(PROJECT_REF, 0, 0);
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));
341 underTest.visit(ROOT_PROJECT);
343 assertThat(measureRepository.getAddedRawMeasures(PROJECT_REF).values()).isEmpty();
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);
354 assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY)).isAbsent();
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();
362 assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)).hasValue(hotspotsReviewed);
364 if (hotspotsReviewed == null) {
365 Assertions.assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)).isEmpty();
367 assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)).hasValue(hotspotsToReview);
371 private DefaultIssue newHotspot(String status, @Nullable String resolution) {
372 return createHotspot(status, resolution, true);
375 private DefaultIssue oldHotspot(String status, @Nullable String resolution) {
376 return createHotspot(status, resolution, false);
379 private DefaultIssue createHotspot(String status, @Nullable String resolution, boolean isNew) {
380 DefaultIssue issue = new DefaultIssue()
381 .setKey(UuidFactoryFast.getInstance().create())
384 .setResolution(resolution)
385 .setType(RuleType.SECURITY_HOTSPOT);
386 when(newIssueClassifier.isNew(any(), eq(issue))).thenReturn(isNew);
390 private DefaultIssue newIssue() {
391 DefaultIssue issue = new DefaultIssue()
392 .setKey(Uuids.create())
394 .setType(RuleType.BUG);
395 when(newIssueClassifier.isNew(any(), eq(issue))).thenReturn(false);