]> source.dussan.org Git - sonarqube.git/blob
59b3abc0dc3a746fd0d7810a2e8b6d27b39ac5d4
[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.server.issue.index;
21
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.OptionalInt;
26 import java.util.stream.Collectors;
27 import org.jetbrains.annotations.NotNull;
28 import org.junit.jupiter.api.Test;
29 import org.sonar.api.issue.Issue;
30 import org.sonar.api.rule.Severity;
31 import org.sonar.api.rules.RuleType;
32 import org.sonar.db.component.ComponentDto;
33 import org.sonar.server.view.index.ViewDoc;
34
35 import static java.lang.Integer.parseInt;
36 import static java.util.Arrays.asList;
37 import static java.util.Collections.singletonList;
38 import static java.util.Comparator.comparing;
39 import static java.util.stream.Collectors.toList;
40 import static org.assertj.core.api.Assertions.assertThat;
41 import static org.assertj.core.api.Assertions.tuple;
42 import static org.sonar.api.server.rule.RulesDefinition.OwaspAsvsVersion;
43 import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2017;
44 import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
45 import static org.sonar.api.server.rule.RulesDefinition.PciDssVersion;
46 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
47 import static org.sonar.server.issue.IssueDocTesting.newDocForProject;
48 import static org.sonar.server.security.SecurityStandards.UNKNOWN_STANDARD;
49
50 class IssueIndexSecurityReportsTest extends IssueIndexTestCommon {
51
52   @Test
53   void getOwaspTop10Report_dont_count_vulnerabilities_from_other_projects() {
54     ComponentDto project = newPrivateProjectDto();
55     ComponentDto another = newPrivateProjectDto();
56
57     IssueDoc openVulDoc = newDocForProject("openvul1", project).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR);
58     openVulDoc.setOwaspTop10For2021(singletonList("a2")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR);
59
60     IssueDoc otherProjectDoc = newDocForProject("anotherProject", another).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL);
61     otherProjectDoc.setOwaspTop10For2021(singletonList("a2")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL);
62
63     indexIssues(openVulDoc, otherProjectDoc);
64
65     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2017);
66     assertThat(owaspTop10Report)
67       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
68         SecurityStandardCategoryStatistics::getVulnerabilityRating)
69       .contains(
70         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
71
72     List<SecurityStandardCategoryStatistics> owaspTop10For2021Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2021);
73     assertThat(owaspTop10For2021Report)
74       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
75         SecurityStandardCategoryStatistics::getVulnerabilityRating)
76       .contains(
77         tuple("a2", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
78
79   }
80
81   @Test
82   void getOwaspTop10Report_dont_count_closed_vulnerabilities() {
83     ComponentDto project = newPrivateProjectDto();
84     indexIssues(
85       newDocForProject("openvul1", project).setOwaspTop10(List.of("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR),
86       newDocForProject("openvul12021", project).setOwaspTop10For2021(List.of("a2")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR),
87       newDocForProject("notopenvul", project).setOwaspTop10(List.of("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED)
88         .setSeverity(Severity.BLOCKER),
89       newDocForProject("notopenvul2021", project).setOwaspTop10For2021(List.of("a2")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED)
90         .setSeverity(Severity.BLOCKER));
91
92     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2017);
93     assertThat(owaspTop10Report)
94       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
95         SecurityStandardCategoryStatistics::getVulnerabilityRating)
96       .contains(
97         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
98
99     List<SecurityStandardCategoryStatistics> owaspTop10For2021Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2021);
100     assertThat(owaspTop10For2021Report)
101       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
102         SecurityStandardCategoryStatistics::getVulnerabilityRating)
103       .contains(
104         tuple("a2", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
105   }
106
107   @Test
108   void getOwaspTop10Report_dont_count_old_vulnerabilities() {
109     ComponentDto project = newPrivateProjectDto();
110     indexIssues(
111       // Previous vulnerabilities in projects that are not reanalyzed will have no owasp nor cwe attributes (not even 'unknown')
112       newDocForProject("openvulNotReindexed", project).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR));
113
114     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2017);
115     assertThat(owaspTop10Report)
116       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
117         SecurityStandardCategoryStatistics::getVulnerabilityRating)
118       .containsOnly(
119         tuple(0L, OptionalInt.empty()));
120
121     List<SecurityStandardCategoryStatistics> owaspTop10For2021Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2021);
122     assertThat(owaspTop10For2021Report)
123       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
124         SecurityStandardCategoryStatistics::getVulnerabilityRating)
125       .containsOnly(
126         tuple(0L, OptionalInt.empty()));
127   }
128
129   @Test
130   void getOwaspTop10Report_dont_count_hotspots_from_other_projects() {
131     ComponentDto project = newPrivateProjectDto();
132     ComponentDto another = newPrivateProjectDto();
133     indexIssues(
134       newDocForProject("openhotspot1", project).setOwaspTop10(List.of("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
135       newDocForProject("openhotspot2021", project).setOwaspTop10For2021(List.of("a2")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
136       newDocForProject("anotherProject", another).setOwaspTop10(List.of("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
137       newDocForProject("anotherProject2021", another).setOwaspTop10For2021(List.of("a2")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
138
139     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2017);
140     assertThat(owaspTop10Report)
141       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
142       .contains(
143         tuple("a1", 1L /* openhotspot1 */));
144
145     List<SecurityStandardCategoryStatistics> owaspTop10For2021Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2021);
146     assertThat(owaspTop10For2021Report)
147       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
148       .contains(
149         tuple("a2", 1L /* openhotspot1 */));
150   }
151
152   @Test
153   void getOwaspTop10Report_dont_count_closed_hotspots() {
154     ComponentDto project = newPrivateProjectDto();
155     indexIssues(
156       newDocForProject("openhotspot1", project).setOwaspTop10(List.of("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
157       newDocForProject("openhotspot2021", project).setOwaspTop10For2021(List.of("a2")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
158       newDocForProject("closedHotspot", project).setOwaspTop10(List.of("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_CLOSED)
159         .setResolution(Issue.RESOLUTION_FIXED),
160       newDocForProject("closedHotspot2021", project).setOwaspTop10For2021(List.of("a2")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_CLOSED)
161         .setResolution(Issue.RESOLUTION_FIXED));
162
163     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2017);
164     assertThat(owaspTop10Report)
165       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
166       .contains(
167         tuple("a1", 1L /* openhotspot1 */));
168
169     List<SecurityStandardCategoryStatistics> owaspTop10For2021Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2021);
170     assertThat(owaspTop10For2021Report)
171       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
172       .contains(
173         tuple("a2", 1L /* openhotspot1 */));
174   }
175
176   @Test
177   void getOwaspTop10Report_aggregation_no_cwe() {
178     List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(false);
179
180     assertThat(owaspTop10Report)
181       .isNotEmpty()
182       .allMatch(category -> category.getChildren().isEmpty());
183   }
184
185   @Test
186   void getPciDss32Report_aggregation() {
187     List<SecurityStandardCategoryStatistics> pciDss32Report = indexIssuesAndAssertPciDss32Report();
188
189     assertThat(pciDss32Report)
190       .isNotEmpty();
191
192     assertThat(pciDss32Report.get(0).getChildren()).hasSize(2);
193     assertThat(pciDss32Report.get(1).getChildren()).isEmpty();
194     assertThat(pciDss32Report.get(2).getChildren()).hasSize(4);
195     assertThat(pciDss32Report.get(3).getChildren()).isEmpty();
196     assertThat(pciDss32Report.get(4).getChildren()).isEmpty();
197     assertThat(pciDss32Report.get(5).getChildren()).hasSize(2);
198     assertThat(pciDss32Report.get(6).getChildren()).isEmpty();
199     assertThat(pciDss32Report.get(7).getChildren()).hasSize(1);
200     assertThat(pciDss32Report.get(8).getChildren()).isEmpty();
201     assertThat(pciDss32Report.get(9).getChildren()).hasSize(1);
202     assertThat(pciDss32Report.get(10).getChildren()).isEmpty();
203     assertThat(pciDss32Report.get(11).getChildren()).isEmpty();
204   }
205
206   @Test
207   void getOwaspAsvs40Report_aggregation() {
208     List<SecurityStandardCategoryStatistics> owaspAsvsReport = indexIssuesAndAssertOwaspAsvsReport();
209
210     assertThat(owaspAsvsReport)
211       .isNotEmpty();
212
213     assertThat(owaspAsvsReport.get(0).getChildren()).isEmpty();
214     assertThat(owaspAsvsReport.get(1).getChildren()).hasSize(2);
215     assertThat(owaspAsvsReport.get(2).getChildren()).hasSize(4);
216     assertThat(owaspAsvsReport.get(3).getChildren()).isEmpty();
217     assertThat(owaspAsvsReport.get(4).getChildren()).isEmpty();
218     assertThat(owaspAsvsReport.get(5).getChildren()).hasSize(2);
219     assertThat(owaspAsvsReport.get(6).getChildren()).hasSize(1);
220     assertThat(owaspAsvsReport.get(7).getChildren()).hasSize(1);
221     assertThat(owaspAsvsReport.get(8).getChildren()).isEmpty();
222     assertThat(owaspAsvsReport.get(9).getChildren()).hasSize(1);
223     assertThat(owaspAsvsReport.get(10).getChildren()).isEmpty();
224     assertThat(owaspAsvsReport.get(11).getChildren()).isEmpty();
225     assertThat(owaspAsvsReport.get(12).getChildren()).isEmpty();
226     assertThat(owaspAsvsReport.get(13).getChildren()).isEmpty();
227   }
228
229   @Test
230   void getOwaspAsvs40ReportGroupedByLevel_aggregation() {
231     List<SecurityStandardCategoryStatistics> owaspAsvsReportGroupedByLevel = indexIssuesAndAssertOwaspAsvsReportGroupedByLevel();
232
233     assertThat(owaspAsvsReportGroupedByLevel)
234       .isNotEmpty();
235
236     assertThat(owaspAsvsReportGroupedByLevel.get(0).getChildren()).hasSize(3);
237     assertThat(owaspAsvsReportGroupedByLevel.get(1).getChildren()).hasSize(7);
238     assertThat(owaspAsvsReportGroupedByLevel.get(2).getChildren()).hasSize(11);
239   }
240
241   @Test
242   void getOwaspTop10Report_aggregation_with_cwe() {
243     List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(true);
244
245     Map<String, List<SecurityStandardCategoryStatistics>> cweByOwasp = owaspTop10Report.stream()
246       .collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getChildren));
247
248     assertThat(cweByOwasp.get("a1")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
249         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
250         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
251       .containsExactlyInAnyOrder(
252         tuple("123", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
253         tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
254         tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
255     assertThat(cweByOwasp.get("a3")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
256         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
257         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
258       .containsExactlyInAnyOrder(
259         tuple("123", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
260         tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
261         tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
262   }
263
264   @Test
265   void getOwaspTop10For2021Report_aggregation_with_cwe() {
266     List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwasp2021Report(true);
267
268     Map<String, List<SecurityStandardCategoryStatistics>> cweByOwasp = owaspTop10Report.stream()
269       .collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getChildren));
270
271     assertThat(cweByOwasp.get("a1")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
272         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
273         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
274       .containsExactlyInAnyOrder(
275         tuple("123", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
276         tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
277         tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
278     assertThat(cweByOwasp.get("a3")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
279         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
280         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
281       .containsExactlyInAnyOrder(
282         tuple("123", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
283         tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
284         tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
285   }
286
287   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspReport(boolean includeCwe) {
288     ComponentDto project = newPrivateProjectDto();
289     indexIssues(
290       newDocForProject("openvul1", project).setOwaspTop10(asList("a1", "a3")).setCwe(asList("123", "456")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
291         .setSeverity(Severity.MAJOR),
292       newDocForProject("openvul2", project).setOwaspTop10(asList("a3", "a6")).setCwe(List.of("123")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
293         .setSeverity(Severity.MINOR),
294       newDocForProject("notowaspvul", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
295       newDocForProject("toreviewhotspot1", project).setOwaspTop10(asList("a1", "a3")).setCwe(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT)
296         .setStatus(Issue.STATUS_TO_REVIEW),
297       newDocForProject("toreviewhotspot2", project).setOwaspTop10(asList("a3", "a6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
298       newDocForProject("reviewedHotspot", project).setOwaspTop10(asList("a3", "a8")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
299         .setResolution(Issue.RESOLUTION_FIXED),
300       newDocForProject("notowasphotspot", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
301
302     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, includeCwe, Y2017);
303     assertThat(owaspTop10Report)
304       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
305         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
306         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
307       .containsExactlyInAnyOrder(
308         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
309         tuple("a2", 0L, OptionalInt.empty(), 0L, 0L, 1),
310         tuple("a3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
311         tuple("a4", 0L, OptionalInt.empty(), 0L, 0L, 1),
312         tuple("a5", 0L, OptionalInt.empty(), 0L, 0L, 1),
313         tuple("a6", 1L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
314         tuple("a7", 0L, OptionalInt.empty(), 0L, 0L, 1),
315         tuple("a8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */, 1),
316         tuple("a9", 0L, OptionalInt.empty(), 0L, 0L, 1),
317         tuple("a10", 0L, OptionalInt.empty(), 0L, 0L, 1));
318     return owaspTop10Report;
319   }
320
321   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertPciDss32Report() {
322     ComponentDto project = newPrivateProjectDto();
323     indexIssues(
324       newDocForProject("openvul1", project).setPciDss32(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
325         .setSeverity(Severity.MAJOR),
326       newDocForProject("openvul2", project).setPciDss32(asList("3.3.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
327         .setSeverity(Severity.MINOR),
328       newDocForProject("openvul3", project).setPciDss32(asList("10.1.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
329         .setSeverity(Severity.MINOR),
330       newDocForProject("notpcidssvul", project).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
331       newDocForProject("toreviewhotspot1", project).setPciDss32(asList("1.3.0", "3.3.2")).setType(RuleType.SECURITY_HOTSPOT)
332         .setStatus(Issue.STATUS_TO_REVIEW),
333       newDocForProject("toreviewhotspot2", project).setPciDss32(asList("3.5.6", "6.4.5")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
334       newDocForProject("reviewedHotspot", project).setPciDss32(asList("3.1.1", "8.6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
335         .setResolution(Issue.RESOLUTION_FIXED),
336       newDocForProject("notpcidsshotspot", project).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
337
338     List<SecurityStandardCategoryStatistics> pciDssReport = underTest.getPciDssReport(project.uuid(), false, PciDssVersion.V3_2).stream()
339       .sorted(comparing(s -> parseInt(s.getCategory())))
340       .collect(toList());
341     assertThat(pciDssReport)
342       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
343         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
344         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
345       .containsExactlyInAnyOrder(
346         tuple("1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
347         tuple("2", 0L, OptionalInt.empty(), 0L, 0L, 1),
348         tuple("3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
349         tuple("4", 0L, OptionalInt.empty(), 0L, 0L, 1),
350         tuple("5", 0L, OptionalInt.empty(), 0L, 0L, 1),
351         tuple("6", 2L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
352         tuple("7", 0L, OptionalInt.empty(), 0L, 0L, 1),
353         tuple("8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */, 1),
354         tuple("9", 0L, OptionalInt.empty(), 0L, 0L, 1),
355         tuple("10", 1L, OptionalInt.of(2), 0L, 0L, 1),
356         tuple("11", 0L, OptionalInt.empty(), 0L, 0L, 1),
357         tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1));
358
359     return pciDssReport;
360   }
361
362   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspAsvsReport() {
363     ComponentDto project = getProjectWithOwaspAsvsIssuesIndexed();
364
365     List<SecurityStandardCategoryStatistics> owaspAsvsReport = underTest.getOwaspAsvsReport(project.uuid(), false, OwaspAsvsVersion.V4_0, 3).stream()
366       .sorted(comparing(s -> parseInt(s.getCategory())))
367       .collect(toList());
368     assertThat(owaspAsvsReport)
369       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
370         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
371         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
372       .containsExactlyInAnyOrder(
373         tuple("1", 0L, OptionalInt.empty(), 0L, 0L, 1),
374         tuple("2", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
375         tuple("3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
376         tuple("4", 0L, OptionalInt.empty(), 0L, 0L, 1),
377         tuple("5", 0L, OptionalInt.empty(), 0L, 0L, 1),
378         tuple("6", 2L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 0L, 0L, 1),
379         tuple("7", 0L /* openvul2 */, OptionalInt.empty() /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
380         tuple("8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */, 1),
381         tuple("9", 0L, OptionalInt.empty(), 0L, 0L, 1),
382         tuple("10", 1L, OptionalInt.of(2), 0L, 0L, 1),
383         tuple("11", 0L, OptionalInt.empty(), 0L, 0L, 1),
384         tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1),
385         tuple("13", 0L, OptionalInt.empty(), 0L, 0L, 1),
386         tuple("14", 0L, OptionalInt.empty(), 0L, 0L, 1));
387
388     return owaspAsvsReport;
389   }
390
391   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspAsvsReportGroupedByLevel() {
392     ComponentDto project = getProjectWithOwaspAsvsIssuesIndexed();
393
394     List<SecurityStandardCategoryStatistics> owaspAsvsReportGroupedByLevel = new ArrayList<>();
395     owaspAsvsReportGroupedByLevel.addAll(underTest.getOwaspAsvsReportGroupedByLevel(project.uuid(), false, OwaspAsvsVersion.V4_0, 1).stream()
396       .sorted(comparing(s -> parseInt(s.getCategory())))
397       .toList());
398     owaspAsvsReportGroupedByLevel.addAll(underTest.getOwaspAsvsReportGroupedByLevel(project.uuid(), false, OwaspAsvsVersion.V4_0, 2).stream()
399       .sorted(comparing(s -> parseInt(s.getCategory())))
400       .toList());
401     owaspAsvsReportGroupedByLevel.addAll(underTest.getOwaspAsvsReportGroupedByLevel(project.uuid(), false, OwaspAsvsVersion.V4_0, 3).stream()
402       .sorted(comparing(s -> parseInt(s.getCategory())))
403       .toList());
404
405     assertThat(owaspAsvsReportGroupedByLevel)
406       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
407         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
408         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
409       .containsExactlyInAnyOrder(
410         tuple("l1", 1L /* openvul2 */, OptionalInt.of(2), 1L /* toreviewhotspot2 */, 0L, 5),
411         tuple("l2", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L /* toreviewhotspot1, toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
412         tuple("l3", 3L /* openvul1,openvul2,openvul3 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4));
413
414     return owaspAsvsReportGroupedByLevel;
415   }
416
417   @NotNull
418   private ComponentDto getProjectWithOwaspAsvsIssuesIndexed() {
419     ComponentDto project = newPrivateProjectDto();
420     indexIssues(
421       newDocForProject("openvul1", project).setOwaspAsvs40(asList("2.4.1", "3.2.4")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
422         .setSeverity(Severity.MAJOR),
423       newDocForProject("openvul2", project).setOwaspAsvs40(asList("3.4.5", "6.2.1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
424         .setSeverity(Severity.MINOR),
425       newDocForProject("openvul3", project).setOwaspAsvs40(asList("10.2.4", "6.2.8")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
426         .setSeverity(Severity.MINOR),
427       newDocForProject("notowaspasvsvul", project).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
428       newDocForProject("toreviewhotspot1", project).setOwaspAsvs40(asList("2.2.5", "3.2.4")).setType(RuleType.SECURITY_HOTSPOT)
429         .setStatus(Issue.STATUS_TO_REVIEW),
430       newDocForProject("toreviewhotspot2", project).setOwaspAsvs40(asList("3.6.1", "7.1.1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
431       newDocForProject("reviewedHotspot", project).setOwaspAsvs40(asList("3.3.3", "8.3.7")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
432         .setResolution(Issue.RESOLUTION_FIXED),
433       newDocForProject("notowaspasvshotspot", project).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
434     return project;
435   }
436
437   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwasp2021Report(boolean includeCwe) {
438     ComponentDto project = newPrivateProjectDto();
439     indexIssues(
440       newDocForProject("openvul1", project).setOwaspTop10For2021(asList("a1", "a3")).setCwe(asList("123", "456")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
441         .setSeverity(Severity.MAJOR),
442       newDocForProject("openvul2", project).setOwaspTop10For2021(asList("a3", "a6")).setCwe(List.of("123")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
443         .setSeverity(Severity.MINOR),
444       newDocForProject("notowaspvul", project).setOwaspTop10For2021(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
445       newDocForProject("toreviewhotspot1", project).setOwaspTop10For2021(asList("a1", "a3")).setCwe(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT)
446         .setStatus(Issue.STATUS_TO_REVIEW),
447       newDocForProject("toreviewhotspot2", project).setOwaspTop10For2021(asList("a3", "a6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
448       newDocForProject("reviewedHotspot", project).setOwaspTop10For2021(asList("a3", "a8")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
449         .setResolution(Issue.RESOLUTION_FIXED),
450       newDocForProject("notowasphotspot", project).setOwaspTop10For2021(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
451
452     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, includeCwe, Y2021);
453     assertThat(owaspTop10Report)
454       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
455         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
456         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
457       .containsExactlyInAnyOrder(
458         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
459         tuple("a2", 0L, OptionalInt.empty(), 0L, 0L, 1),
460         tuple("a3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
461         tuple("a4", 0L, OptionalInt.empty(), 0L, 0L, 1),
462         tuple("a5", 0L, OptionalInt.empty(), 0L, 0L, 1),
463         tuple("a6", 1L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
464         tuple("a7", 0L, OptionalInt.empty(), 0L, 0L, 1),
465         tuple("a8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */, 1),
466         tuple("a9", 0L, OptionalInt.empty(), 0L, 0L, 1),
467         tuple("a10", 0L, OptionalInt.empty(), 0L, 0L, 1));
468     return owaspTop10Report;
469   }
470
471   @Test
472   void getPciDssReport_aggregation_on_portfolio() {
473     ComponentDto portfolio1 = db.components().insertPrivateApplication().getMainBranchComponent();
474     ComponentDto portfolio2 = db.components().insertPrivateApplication().getMainBranchComponent();
475     ComponentDto project1 = db.components().insertPrivateProject().getMainBranchComponent();
476     ComponentDto project2 = db.components().insertPrivateProject().getMainBranchComponent();
477
478     indexIssues(
479       newDocForProject("openvul1", project1).setPciDss32(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
480         .setSeverity(Severity.MAJOR),
481       newDocForProject("openvul2", project2).setPciDss32(asList("3.3.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
482         .setSeverity(Severity.MINOR),
483       newDocForProject("openvul3", project1).setPciDss32(asList("10.1.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
484         .setSeverity(Severity.MINOR),
485       newDocForProject("notpcidssvul", project1).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
486       newDocForProject("toreviewhotspot1", project2).setPciDss32(asList("1.3.0", "3.3.2")).setType(RuleType.SECURITY_HOTSPOT)
487         .setStatus(Issue.STATUS_TO_REVIEW),
488       newDocForProject("toreviewhotspot2", project1).setPciDss32(asList("3.5.6", "6.4.5")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
489       newDocForProject("reviewedHotspot", project2).setPciDss32(asList("3.1.1", "8.6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
490         .setResolution(Issue.RESOLUTION_FIXED),
491       newDocForProject("notpcidsshotspot", project1).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
492
493     indexView(portfolio1.uuid(), singletonList(project1.uuid()));
494     indexView(portfolio2.uuid(), singletonList(project2.uuid()));
495
496     List<SecurityStandardCategoryStatistics> pciDssReport = underTest.getPciDssReport(portfolio1.uuid(), true, PciDssVersion.V3_2).stream()
497       .sorted(comparing(s -> parseInt(s.getCategory())))
498       .collect(toList());
499     assertThat(pciDssReport)
500       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
501         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
502         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
503       .containsExactlyInAnyOrder(
504         tuple("1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
505         tuple("2", 0L, OptionalInt.empty(), 0L, 0L, 1),
506         tuple("3", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L/* toreviewhotspot2 */, 0L, 5),
507         tuple("4", 0L, OptionalInt.empty(), 0L, 0L, 1),
508         tuple("5", 0L, OptionalInt.empty(), 0L, 0L, 1),
509         tuple("6", 1L /* openvul3 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
510         tuple("7", 0L, OptionalInt.empty(), 0L, 0L, 1),
511         tuple("8", 0L, OptionalInt.empty(), 0L, 0L /* reviewedHotspot */, 1),
512         tuple("9", 0L, OptionalInt.empty(), 0L, 0L, 1),
513         tuple("10", 1L /* openvul3 */, OptionalInt.of(2), 0L, 0L, 1),
514         tuple("11", 0L, OptionalInt.empty(), 0L, 0L, 1),
515         tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1));
516   }
517
518   @Test
519   void getOwaspAsvsReport_aggregation_on_portfolio() {
520     ComponentDto portfolio1 = db.components().insertPrivateApplication().getMainBranchComponent();
521     ComponentDto portfolio2 = db.components().insertPrivateApplication().getMainBranchComponent();
522     ComponentDto project1 = db.components().insertPrivateProject().getMainBranchComponent();
523     ComponentDto project2 = db.components().insertPrivateProject().getMainBranchComponent();
524
525     indexIssues(
526       newDocForProject("openvul1", project1).setOwaspAsvs40(asList("2.1.1", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
527         .setSeverity(Severity.MAJOR),
528       newDocForProject("openvul2", project2).setOwaspAsvs40(asList("3.3.2", "6.2.1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
529         .setSeverity(Severity.MINOR),
530       newDocForProject("openvul3", project1).setOwaspAsvs40(asList("10.3.2", "6.2.1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
531         .setSeverity(Severity.MINOR),
532       newDocForProject("notowaspasvsvul", project1).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
533       newDocForProject("toreviewhotspot1", project2).setOwaspAsvs40(asList("2.1.3", "3.3.2")).setType(RuleType.SECURITY_HOTSPOT)
534         .setStatus(Issue.STATUS_TO_REVIEW),
535       newDocForProject("toreviewhotspot2", project1).setOwaspAsvs40(asList("3.4.4", "6.2.1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
536       newDocForProject("reviewedHotspot", project2).setOwaspAsvs40(asList("3.1.1", "8.3.1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
537         .setResolution(Issue.RESOLUTION_FIXED),
538       newDocForProject("notowaspasvshotspot", project1).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
539
540     indexView(portfolio1.uuid(), singletonList(project1.uuid()));
541     indexView(portfolio2.uuid(), singletonList(project2.uuid()));
542
543     List<SecurityStandardCategoryStatistics> owaspAsvsReport = underTest.getOwaspAsvsReport(portfolio1.uuid(), true, OwaspAsvsVersion.V4_0, 1).stream()
544       .sorted(comparing(s -> parseInt(s.getCategory())))
545       .collect(toList());
546     assertThat(owaspAsvsReport)
547       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
548         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
549         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
550       .containsExactlyInAnyOrder(
551         tuple("1", 0L, OptionalInt.empty(), 0L, 0L, 1),
552         tuple("2", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
553         tuple("3", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L/* toreviewhotspot2 */, 0L, 5),
554         tuple("4", 0L, OptionalInt.empty(), 0L, 0L, 1),
555         tuple("5", 0L, OptionalInt.empty(), 0L, 0L, 1),
556         tuple("6", 1L /* openvul3 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
557         tuple("7", 0L, OptionalInt.empty(), 0L, 0L, 1),
558         tuple("8", 0L, OptionalInt.empty(), 0L, 0L /* reviewedHotspot */, 1),
559         tuple("9", 0L, OptionalInt.empty(), 0L, 0L, 1),
560         tuple("10", 1L /* openvul3 */, OptionalInt.of(2), 0L, 0L, 1),
561         tuple("11", 0L, OptionalInt.empty(), 0L, 0L, 1),
562         tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1),
563         tuple("13", 0L, OptionalInt.empty(), 0L, 0L, 1),
564         tuple("14", 0L, OptionalInt.empty(), 0L, 0L, 1));
565   }
566
567   @Test
568   void getCWETop25Report_aggregation() {
569     ComponentDto project = newPrivateProjectDto();
570     indexIssues(
571       newDocForProject("openvul", project).setCwe(List.of("119")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
572         .setSeverity(Severity.MAJOR),
573       newDocForProject("notopenvul", project).setCwe(List.of("119")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
574         .setResolution(Issue.RESOLUTION_FIXED)
575         .setSeverity(Severity.BLOCKER),
576       newDocForProject("toreviewhotspot", project).setCwe(List.of("89")).setType(RuleType.SECURITY_HOTSPOT)
577         .setStatus(Issue.STATUS_TO_REVIEW),
578       newDocForProject("only2020", project).setCwe(List.of("862")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
579         .setSeverity(Severity.MINOR),
580       newDocForProject("unknown", project).setCwe(List.of("999")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
581         .setSeverity(Severity.MINOR));
582
583     List<SecurityStandardCategoryStatistics> cweTop25Reports = underTest.getCweTop25Reports(project.uuid(), false);
584
585     List<String> listOfYears = cweTop25Reports.stream()
586       .map(SecurityStandardCategoryStatistics::getCategory)
587       .collect(toList());
588
589     assertThat(listOfYears).contains("2021", "2022", "2023");
590
591     SecurityStandardCategoryStatistics cwe2021 = cweTop25Reports.stream()
592       .filter(s -> s.getCategory().equals("2021"))
593       .findAny().get();
594     assertThat(cwe2021.getChildren()).hasSize(25);
595     assertThat(findRuleInCweByYear(cwe2021, "119")).isNotNull()
596       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
597         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
598         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
599       .containsExactlyInAnyOrder(1L, 0L, 0L);
600     assertThat(findRuleInCweByYear(cwe2021, "89")).isNotNull()
601       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
602         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
603         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
604       .containsExactlyInAnyOrder(0L, 1L, 0L);
605     assertThat(findRuleInCweByYear(cwe2021, "295")).isNull();
606     assertThat(findRuleInCweByYear(cwe2021, "999")).isNull();
607
608     SecurityStandardCategoryStatistics cwe2022 = cweTop25Reports.stream()
609       .filter(s -> s.getCategory().equals("2022"))
610       .findAny().get();
611     assertThat(cwe2022.getChildren()).hasSize(25);
612     assertThat(findRuleInCweByYear(cwe2022, "119")).isNotNull()
613       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
614         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
615         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
616       .containsExactlyInAnyOrder(1L, 0L, 0L);
617     assertThat(findRuleInCweByYear(cwe2022, "89")).isNotNull()
618       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
619         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
620         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
621       .containsExactlyInAnyOrder(0L, 1L, 0L);
622     assertThat(findRuleInCweByYear(cwe2022, "950")).isNull();
623     assertThat(findRuleInCweByYear(cwe2022, "999")).isNull();
624
625     SecurityStandardCategoryStatistics cwe2023 = cweTop25Reports.stream()
626       .filter(s -> s.getCategory().equals("2023"))
627       .findAny().get();
628     assertThat(cwe2023.getChildren()).hasSize(25);
629     assertThat(findRuleInCweByYear(cwe2023, "119")).isNotNull()
630       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
631         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
632         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
633       .containsExactlyInAnyOrder(1L, 0L, 0L);
634     assertThat(findRuleInCweByYear(cwe2023, "89")).isNotNull()
635       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
636         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
637         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
638       .containsExactlyInAnyOrder(0L, 1L, 0L);
639     assertThat(findRuleInCweByYear(cwe2023, "862")).isNotNull()
640       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
641         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
642         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
643       .containsExactlyInAnyOrder(1L, 0L, 0L);
644     assertThat(findRuleInCweByYear(cwe2023, "999")).isNull();
645   }
646
647   @Test
648   void getCWETop25Report_aggregation_on_portfolio() {
649     ComponentDto application = db.components().insertPrivateApplication().getMainBranchComponent();
650     ComponentDto project1 = db.components().insertPrivateProject().getMainBranchComponent();
651     ComponentDto project2 = db.components().insertPrivateProject().getMainBranchComponent();
652
653     indexIssues(
654       newDocForProject("openvul1", project1).setCwe(List.of("119")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
655         .setSeverity(Severity.MAJOR),
656       newDocForProject("openvul2", project2).setCwe(List.of("119")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
657         .setSeverity(Severity.MINOR),
658       newDocForProject("toreviewhotspot", project1).setCwe(List.of("89")).setType(RuleType.SECURITY_HOTSPOT)
659         .setStatus(Issue.STATUS_TO_REVIEW),
660       newDocForProject("only2020", project2).setCwe(List.of("862")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
661         .setSeverity(Severity.MINOR),
662       newDocForProject("unknown", project2).setCwe(List.of("999")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
663         .setSeverity(Severity.MINOR));
664
665     indexView(application.uuid(), asList(project1.uuid(), project2.uuid()));
666
667     List<SecurityStandardCategoryStatistics> cweTop25Reports = underTest.getCweTop25Reports(application.uuid(), true);
668
669     List<String> listOfYears = cweTop25Reports.stream()
670       .map(SecurityStandardCategoryStatistics::getCategory)
671       .collect(toList());
672
673     assertThat(listOfYears).contains("2021", "2022", "2023");
674
675     SecurityStandardCategoryStatistics cwe2021 = cweTop25Reports.stream()
676       .filter(s -> s.getCategory().equals("2021"))
677       .findAny().get();
678     assertThat(cwe2021.getChildren()).hasSize(25);
679     assertThat(findRuleInCweByYear(cwe2021, "119")).isNotNull()
680       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
681         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
682         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
683       .containsExactlyInAnyOrder(2L, 0L, 0L);
684     assertThat(findRuleInCweByYear(cwe2021, "89")).isNotNull()
685       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
686         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
687         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
688       .containsExactlyInAnyOrder(0L, 1L, 0L);
689     assertThat(findRuleInCweByYear(cwe2021, "295")).isNull();
690     assertThat(findRuleInCweByYear(cwe2021, "999")).isNull();
691
692
693     SecurityStandardCategoryStatistics cwe2022 = cweTop25Reports.stream()
694       .filter(s -> s.getCategory().equals("2022"))
695       .findAny().get();
696     assertThat(cwe2022.getChildren()).hasSize(25);
697     assertThat(findRuleInCweByYear(cwe2022, "119")).isNotNull()
698       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
699         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
700         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
701       .containsExactlyInAnyOrder(2L, 0L, 0L);
702     assertThat(findRuleInCweByYear(cwe2022, "89")).isNotNull()
703       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
704         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
705         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
706       .containsExactlyInAnyOrder(0L, 1L, 0L);
707     assertThat(findRuleInCweByYear(cwe2022, "295")).isNull();
708     assertThat(findRuleInCweByYear(cwe2022, "999")).isNull();
709
710     SecurityStandardCategoryStatistics cwe2023 = cweTop25Reports.stream()
711       .filter(s -> s.getCategory().equals("2023"))
712       .findAny().get();
713     assertThat(cwe2023.getChildren()).hasSize(25);
714     assertThat(findRuleInCweByYear(cwe2023, "119")).isNotNull()
715       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
716         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
717         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
718       .containsExactlyInAnyOrder(2L, 0L, 0L);
719     assertThat(findRuleInCweByYear(cwe2023, "89")).isNotNull()
720       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
721         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
722         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
723       .containsExactlyInAnyOrder(0L, 1L, 0L);
724     assertThat(findRuleInCweByYear(cwe2023, "862")).isNotNull()
725       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
726         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
727         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
728       .containsExactlyInAnyOrder(1L, 0L, 0L);
729     assertThat(findRuleInCweByYear(cwe2023, "999")).isNull();
730   }
731
732   private SecurityStandardCategoryStatistics findRuleInCweByYear(SecurityStandardCategoryStatistics statistics, String cweId) {
733     return statistics.getChildren().stream().filter(stat -> stat.getCategory().equals(cweId)).findAny().orElse(null);
734   }
735
736   private void indexView(String viewUuid, List<String> projectBranchUuids) {
737     viewIndexer.index(new ViewDoc().setUuid(viewUuid).setProjectBranchUuids(projectBranchUuids));
738   }
739
740 }