|
4 | 4 | _ "embed" |
5 | 5 | "net/http" |
6 | 6 | "net/http/httptest" |
| 7 | + "sync/atomic" |
7 | 8 | "testing" |
8 | 9 |
|
9 | 10 | cdx "github.com/CycloneDX/cyclonedx-go" |
@@ -56,6 +57,61 @@ func TestEnrichSBOM_CycloneDXWithVulnerabilities(t *testing.T) { |
56 | 57 | assert.Equal(t, (*vuln.Ratings)[1].Method, cdx.ScoringMethodCVSSv3) |
57 | 58 | } |
58 | 59 |
|
| 60 | +func TestEnrichSBOM_CycloneDXDeduplicatesRequests(t *testing.T) { |
| 61 | + var numRequests int32 |
| 62 | + mux := http.NewServeMux() |
| 63 | + mux.HandleFunc( |
| 64 | + "GET /rest/self", |
| 65 | + func(w http.ResponseWriter, r *http.Request) { |
| 66 | + respond(w, selfBody) |
| 67 | + }) |
| 68 | + mux.HandleFunc( |
| 69 | + "GET /rest/orgs/{org_id}/packages/{purl}/issues", |
| 70 | + func(w http.ResponseWriter, r *http.Request) { |
| 71 | + atomic.AddInt32(&numRequests, 1) |
| 72 | + respond(w, numpyIssues) |
| 73 | + }) |
| 74 | + |
| 75 | + srv := httptest.NewServer(mux) |
| 76 | + t.Cleanup(srv.Close) |
| 77 | + |
| 78 | + cfg := DefaultConfig() |
| 79 | + cfg.APIToken = "asdf" |
| 80 | + cfg.SnykAPIURL = srv.URL |
| 81 | + |
| 82 | + logger := zerolog.Nop() |
| 83 | + svc := NewService(cfg, &logger) |
| 84 | + |
| 85 | + bom := &cdx.BOM{ |
| 86 | + Components: &[]cdx.Component{ |
| 87 | + { |
| 88 | + BOMRef: "pkg:pypi/numpy@1.16.0", |
| 89 | + Name: "numpy", |
| 90 | + Version: "1.16.0", |
| 91 | + PackageURL: "pkg:pypi/numpy@1.16.0", |
| 92 | + }, |
| 93 | + { |
| 94 | + BOMRef: "pkg:pypi/numpy@1.16.0#dup", |
| 95 | + Name: "numpy", |
| 96 | + Version: "1.16.0", |
| 97 | + PackageURL: "pkg:pypi/numpy@1.16.0", |
| 98 | + }, |
| 99 | + }, |
| 100 | + } |
| 101 | + doc := &sbom.SBOMDocument{BOM: bom} |
| 102 | + |
| 103 | + svc.EnrichSBOM(doc) |
| 104 | + |
| 105 | + assert.Equal(t, int32(1), atomic.LoadInt32(&numRequests)) |
| 106 | + require.NotNil(t, bom.Vulnerabilities) |
| 107 | + vulnByRef := map[string]int{} |
| 108 | + for _, vuln := range *bom.Vulnerabilities { |
| 109 | + vulnByRef[vuln.BOMRef]++ |
| 110 | + } |
| 111 | + assert.Greater(t, vulnByRef["pkg:pypi/numpy@1.16.0"], 0) |
| 112 | + assert.Greater(t, vulnByRef["pkg:pypi/numpy@1.16.0#dup"], 0) |
| 113 | +} |
| 114 | + |
59 | 115 | func TestEnrichSBOM_CycloneDXExternalRefs(t *testing.T) { |
60 | 116 | svc := setupTestEnv(t) |
61 | 117 |
|
@@ -199,6 +255,79 @@ func TestEnrichSBOM_SPDXWithVulnerabilities(t *testing.T) { |
199 | 255 | assert.Equal(t, "Arbitrary Code Execution", vulnRef.ExternalRefComment) |
200 | 256 | } |
201 | 257 |
|
| 258 | +func TestEnrichSBOM_SPDXDeduplicatesRequests(t *testing.T) { |
| 259 | + var numRequests int32 |
| 260 | + mux := http.NewServeMux() |
| 261 | + mux.HandleFunc( |
| 262 | + "GET /rest/self", |
| 263 | + func(w http.ResponseWriter, r *http.Request) { |
| 264 | + respond(w, selfBody) |
| 265 | + }) |
| 266 | + mux.HandleFunc( |
| 267 | + "GET /rest/orgs/{org_id}/packages/{purl}/issues", |
| 268 | + func(w http.ResponseWriter, r *http.Request) { |
| 269 | + atomic.AddInt32(&numRequests, 1) |
| 270 | + respond(w, numpyIssues) |
| 271 | + }) |
| 272 | + |
| 273 | + srv := httptest.NewServer(mux) |
| 274 | + t.Cleanup(srv.Close) |
| 275 | + |
| 276 | + cfg := DefaultConfig() |
| 277 | + cfg.APIToken = "asdf" |
| 278 | + cfg.SnykAPIURL = srv.URL |
| 279 | + |
| 280 | + logger := zerolog.Nop() |
| 281 | + svc := NewService(cfg, &logger) |
| 282 | + |
| 283 | + bom := &spdx_2_3.Document{ |
| 284 | + Packages: []*spdx_2_3.Package{ |
| 285 | + { |
| 286 | + PackageSPDXIdentifier: "pkg:pypi/numpy@1.16.0", |
| 287 | + PackageName: "numpy", |
| 288 | + PackageVersion: "1.16.0", |
| 289 | + PackageExternalReferences: []*spdx_2_3.PackageExternalReference{ |
| 290 | + { |
| 291 | + Category: spdx.CategoryPackageManager, |
| 292 | + RefType: "purl", |
| 293 | + Locator: "pkg:pypi/numpy@1.16.0", |
| 294 | + }, |
| 295 | + }, |
| 296 | + }, |
| 297 | + { |
| 298 | + PackageSPDXIdentifier: "pkg:pypi/numpy@1.16.0-dup", |
| 299 | + PackageName: "numpy", |
| 300 | + PackageVersion: "1.16.0", |
| 301 | + PackageExternalReferences: []*spdx_2_3.PackageExternalReference{ |
| 302 | + { |
| 303 | + Category: spdx.CategoryPackageManager, |
| 304 | + RefType: "purl", |
| 305 | + Locator: "pkg:pypi/numpy@1.16.0", |
| 306 | + }, |
| 307 | + }, |
| 308 | + }, |
| 309 | + }, |
| 310 | + } |
| 311 | + doc := &sbom.SBOMDocument{BOM: bom} |
| 312 | + |
| 313 | + svc.EnrichSBOM(doc) |
| 314 | + |
| 315 | + assert.Equal(t, int32(1), atomic.LoadInt32(&numRequests)) |
| 316 | + expectedLocator := "https://security.snyk.io/vuln/SNYK-PYTHON-NUMPY-73513" |
| 317 | + for _, pkg := range bom.Packages { |
| 318 | + hasVulnRef := false |
| 319 | + for _, ref := range pkg.PackageExternalReferences { |
| 320 | + if ref.Category == spdx.CategorySecurity && |
| 321 | + ref.RefType == "advisory" && |
| 322 | + ref.Locator == expectedLocator { |
| 323 | + hasVulnRef = true |
| 324 | + break |
| 325 | + } |
| 326 | + } |
| 327 | + assert.Truef(t, hasVulnRef, "expected vulnerability reference for %s", pkg.PackageSPDXIdentifier) |
| 328 | + } |
| 329 | +} |
| 330 | + |
202 | 331 | func TestEnrichSBOM_SPDXExternalRefs(t *testing.T) { |
203 | 332 | svc := setupTestEnv(t) |
204 | 333 |
|
|
0 commit comments