@@ -19,7 +19,9 @@ import (
1919 "testing"
2020
2121 "github.com/l3montree-dev/devguard/internal/core/vuln"
22+ "github.com/l3montree-dev/devguard/internal/core/vulndb/scan"
2223 "github.com/l3montree-dev/devguard/internal/database/models"
24+ "github.com/l3montree-dev/devguard/internal/utils"
2325 "github.com/package-url/packageurl-go"
2426 "github.com/stretchr/testify/assert"
2527)
@@ -79,3 +81,288 @@ func TestDependencyVulnToTableRow(t *testing.T) {
7981 })
8082
8183}
84+
85+ func TestPrintScaResults (t * testing.T ) {
86+ assetName := "test-asset"
87+ webUI := "https://app.devguard.org"
88+
89+ t .Run ("should return nil when no vulnerabilities found" , func (t * testing.T ) {
90+ scanResponse := scan.ScanResponse {
91+ DependencyVulns : []vuln.DependencyVulnDTO {},
92+ AmountOpened : 0 ,
93+ AmountClosed : 0 ,
94+ }
95+
96+ err := printScaResults (scanResponse , "critical" , "critical" , assetName , webUI )
97+ assert .Nil (t , err )
98+ })
99+
100+ t .Run ("should not fail when all vulnerabilities are closed/accepted - even with high risk/CVSS" , func (t * testing.T ) {
101+ scanResponse := scan.ScanResponse {
102+ DependencyVulns : []vuln.DependencyVulnDTO {
103+ {
104+ CVEID : utils .Ptr ("CVE-2023-12345" ),
105+ ComponentPurl : utils .Ptr ("pkg:golang/github.com/example/lib@v1.0.0" ),
106+ State : "closed" , // CLOSED vulnerability should not cause failure
107+ RawRiskAssessment : utils .Ptr (9.5 ), // High risk but closed
108+ AssetVersionName : "main" ,
109+ CVE : & models.CVE {
110+ CVE : "CVE-2023-12345" ,
111+ CVSS : 9.0 , // High CVSS but closed
112+ },
113+ },
114+ {
115+ CVEID : utils .Ptr ("CVE-2023-67890" ),
116+ ComponentPurl : utils .Ptr ("pkg:golang/github.com/example/lib2@v1.0.0" ),
117+ State : "accepted" , // ACCEPTED vulnerability should not cause failure
118+ RawRiskAssessment : utils .Ptr (10.0 ), // High risk but accepted
119+ AssetVersionName : "main" ,
120+ CVE : & models.CVE {
121+ CVE : "CVE-2023-67890" ,
122+ CVSS : 9.8 , // High CVSS but accepted
123+ },
124+ },
125+ },
126+ AmountOpened : 0 ,
127+ AmountClosed : 2 ,
128+ }
129+
130+ // Should pass even with low thresholds because vulnerabilities are closed/accepted
131+ err := printScaResults (scanResponse , "low" , "low" , assetName , webUI )
132+ assert .Nil (t , err )
133+ })
134+
135+ // Test failOnRisk conditions - consolidated table-driven test
136+ t .Run ("failOnRisk thresholds" , func (t * testing.T ) {
137+ testCases := []struct {
138+ name string
139+ risk float64
140+ threshold string
141+ shouldFail bool
142+ expectedError string
143+ }{
144+ {"low threshold pass" , 0.05 , "low" , false , "" },
145+ {"low threshold fail" , 0.2 , "low" , true , "max risk exceeds threshold 0.20" },
146+ {"medium threshold pass" , 3.9 , "medium" , false , "" },
147+ {"medium threshold fail" , 4.5 , "medium" , true , "max risk exceeds threshold 4.50" },
148+ {"high threshold pass" , 6.9 , "high" , false , "" },
149+ {"high threshold fail" , 7.2 , "high" , true , "max risk exceeds threshold 7.20" },
150+ {"critical threshold pass" , 8.9 , "critical" , false , "" },
151+ {"critical threshold fail" , 9.5 , "critical" , true , "max risk exceeds threshold 9.50" },
152+ }
153+
154+ for _ , tc := range testCases {
155+ t .Run (tc .name , func (t * testing.T ) {
156+ scanResponse := scan.ScanResponse {
157+ DependencyVulns : []vuln.DependencyVulnDTO {
158+ {
159+ CVEID : utils .Ptr ("CVE-2023-12345" ),
160+ ComponentPurl : utils .Ptr ("pkg:golang/github.com/example/lib@v1.0.0" ),
161+ State : "open" ,
162+ RawRiskAssessment : utils .Ptr (tc .risk ),
163+ AssetVersionName : "main" ,
164+ CVE : & models.CVE {
165+ CVE : "CVE-2023-12345" ,
166+ CVSS : 5.0 ,
167+ },
168+ },
169+ },
170+ AmountOpened : 1 ,
171+ AmountClosed : 0 ,
172+ }
173+
174+ err := printScaResults (scanResponse , tc .threshold , "critical" , assetName , webUI )
175+ if tc .shouldFail {
176+ assert .NotNil (t , err )
177+ assert .Contains (t , err .Error (), tc .expectedError )
178+ } else {
179+ assert .Nil (t , err )
180+ }
181+ })
182+ }
183+ })
184+
185+ // Test failOnCVSS conditions - consolidated table-driven test
186+ t .Run ("failOnCVSS thresholds" , func (t * testing.T ) {
187+ testCases := []struct {
188+ name string
189+ cvss float32
190+ threshold string
191+ shouldFail bool
192+ expectedError string
193+ }{
194+ {"low threshold pass" , 0.05 , "low" , false , "" },
195+ {"low threshold fail" , 0.2 , "low" , true , "max CVSS exceeds threshold 0.20" },
196+ {"medium threshold pass" , 3.9 , "medium" , false , "" },
197+ {"medium threshold fail" , 4.5 , "medium" , true , "max CVSS exceeds threshold 4.50" },
198+ {"high threshold pass" , 6.9 , "high" , false , "" },
199+ {"high threshold fail" , 7.2 , "high" , true , "max CVSS exceeds threshold 7.20" },
200+ {"critical threshold pass" , 8.9 , "critical" , false , "" },
201+ {"critical threshold fail" , 9.5 , "critical" , true , "max CVSS exceeds threshold 9.50" },
202+ }
203+
204+ for _ , tc := range testCases {
205+ t .Run (tc .name , func (t * testing.T ) {
206+ scanResponse := scan.ScanResponse {
207+ DependencyVulns : []vuln.DependencyVulnDTO {
208+ {
209+ CVEID : utils .Ptr ("CVE-2023-12345" ),
210+ ComponentPurl : utils .Ptr ("pkg:golang/github.com/example/lib@v1.0.0" ),
211+ State : "open" ,
212+ RawRiskAssessment : utils .Ptr (1.0 ),
213+ AssetVersionName : "main" ,
214+ CVE : & models.CVE {
215+ CVE : "CVE-2023-12345" ,
216+ CVSS : tc .cvss ,
217+ },
218+ },
219+ },
220+ AmountOpened : 1 ,
221+ AmountClosed : 0 ,
222+ }
223+
224+ err := printScaResults (scanResponse , "critical" , tc .threshold , assetName , webUI )
225+ if tc .shouldFail {
226+ assert .NotNil (t , err )
227+ assert .Contains (t , err .Error (), tc .expectedError )
228+ } else {
229+ assert .Nil (t , err )
230+ }
231+ })
232+ }
233+ })
234+
235+ // Test edge cases
236+ t .Run ("should handle vulnerabilities without CVE (no CVSS)" , func (t * testing.T ) {
237+ scanResponse := scan.ScanResponse {
238+ DependencyVulns : []vuln.DependencyVulnDTO {
239+ {
240+ CVEID : nil , // No CVE ID
241+ ComponentPurl : utils .Ptr ("pkg:golang/github.com/example/lib@v1.0.0" ),
242+ State : "open" ,
243+ RawRiskAssessment : utils .Ptr (5.0 ),
244+ AssetVersionName : "main" ,
245+ CVE : nil , // No CVE data - should default CVSS to 0.0
246+ },
247+ },
248+ AmountOpened : 1 ,
249+ AmountClosed : 0 ,
250+ }
251+
252+ // Should fail on risk but pass on CVSS (defaults to 0.0)
253+ err := printScaResults (scanResponse , "medium" , "critical" , assetName , webUI )
254+ assert .NotNil (t , err )
255+ assert .Contains (t , err .Error (), "max risk exceeds threshold 5.00" )
256+ })
257+
258+ t .Run ("should only consider OPEN vulnerabilities - mixed states scenario" , func (t * testing.T ) {
259+ scanResponse := scan.ScanResponse {
260+ DependencyVulns : []vuln.DependencyVulnDTO {
261+ {
262+ CVEID : utils .Ptr ("CVE-2023-12345" ),
263+ ComponentPurl : utils .Ptr ("pkg:golang/github.com/example/lib1@v1.0.0" ),
264+ State : "open" , // OPEN - should be considered
265+ RawRiskAssessment : utils .Ptr (3.0 ),
266+ AssetVersionName : "main" ,
267+ CVE : & models.CVE {
268+ CVE : "CVE-2023-12345" ,
269+ CVSS : 5.0 ,
270+ },
271+ },
272+ {
273+ CVEID : utils .Ptr ("CVE-2023-67890" ),
274+ ComponentPurl : utils .Ptr ("pkg:golang/github.com/example/lib2@v2.0.0" ),
275+ State : "open" , // OPEN - should be considered (highest values)
276+ RawRiskAssessment : utils .Ptr (8.5 ), // Higher risk
277+ AssetVersionName : "main" ,
278+ CVE : & models.CVE {
279+ CVE : "CVE-2023-67890" ,
280+ CVSS : 7.8 , // Higher CVSS
281+ },
282+ },
283+ {
284+ CVEID : utils .Ptr ("CVE-2023-11111" ),
285+ ComponentPurl : utils .Ptr ("pkg:golang/github.com/example/lib3@v3.0.0" ),
286+ State : "closed" , // CLOSED - should be IGNORED even though it has highest values
287+ RawRiskAssessment : utils .Ptr (10.0 ), // Highest risk but closed
288+ AssetVersionName : "main" ,
289+ CVE : & models.CVE {
290+ CVE : "CVE-2023-11111" ,
291+ CVSS : 10.0 , // Highest CVSS but closed
292+ },
293+ },
294+ {
295+ CVEID : utils .Ptr ("CVE-2023-22222" ),
296+ ComponentPurl : utils .Ptr ("pkg:golang/github.com/example/lib4@v4.0.0" ),
297+ State : "accepted" , // ACCEPTED - should be IGNORED even though it has highest values
298+ RawRiskAssessment : utils .Ptr (9.8 ), // Very high risk but accepted
299+ AssetVersionName : "main" ,
300+ CVE : & models.CVE {
301+ CVE : "CVE-2023-22222" ,
302+ CVSS : 9.9 , // Very high CVSS but accepted
303+ },
304+ },
305+ },
306+ AmountOpened : 2 ,
307+ AmountClosed : 2 ,
308+ }
309+
310+ // Should fail on high risk threshold (8.5 >= 7) - only considering open vulns
311+ err := printScaResults (scanResponse , "high" , "critical" , assetName , webUI )
312+ assert .NotNil (t , err )
313+ assert .Contains (t , err .Error (), "max risk exceeds threshold 8.50" )
314+
315+ // Should fail on high CVSS threshold (7.8 >= 7) - only considering open vulns
316+ err = printScaResults (scanResponse , "critical" , "high" , assetName , webUI )
317+ assert .NotNil (t , err )
318+ assert .Contains (t , err .Error (), "max CVSS exceeds threshold 7.80" )
319+ })
320+
321+ t .Run ("should handle nil RawRiskAssessment gracefully" , func (t * testing.T ) {
322+ scanResponse := scan.ScanResponse {
323+ DependencyVulns : []vuln.DependencyVulnDTO {
324+ {
325+ CVEID : utils .Ptr ("CVE-2023-12345" ),
326+ ComponentPurl : utils .Ptr ("pkg:golang/github.com/example/lib@v1.0.0" ),
327+ State : "open" ,
328+ RawRiskAssessment : nil , // Should default to 0
329+ AssetVersionName : "main" ,
330+ CVE : & models.CVE {
331+ CVE : "CVE-2023-12345" ,
332+ CVSS : 5.0 ,
333+ },
334+ },
335+ },
336+ AmountOpened : 1 ,
337+ AmountClosed : 0 ,
338+ }
339+
340+ // Should pass all risk thresholds (defaults to 0)
341+ err := printScaResults (scanResponse , "low" , "critical" , assetName , webUI )
342+ assert .Nil (t , err )
343+ })
344+
345+ t .Run ("should handle unknown failOn values gracefully" , func (t * testing.T ) {
346+ scanResponse := scan.ScanResponse {
347+ DependencyVulns : []vuln.DependencyVulnDTO {
348+ {
349+ CVEID : utils .Ptr ("CVE-2023-12345" ),
350+ ComponentPurl : utils .Ptr ("pkg:golang/github.com/example/lib@v1.0.0" ),
351+ State : "open" ,
352+ RawRiskAssessment : utils .Ptr (10.0 ),
353+ AssetVersionName : "main" ,
354+ CVE : & models.CVE {
355+ CVE : "CVE-2023-12345" ,
356+ CVSS : 10.0 ,
357+ },
358+ },
359+ },
360+ AmountOpened : 1 ,
361+ AmountClosed : 0 ,
362+ }
363+
364+ // Unknown failOn values should not cause failures
365+ err := printScaResults (scanResponse , "unknown" , "invalid" , assetName , webUI )
366+ assert .Nil (t , err )
367+ })
368+ }
0 commit comments