github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/javascript/parse_yarn_lock_test.go (about) 1 package javascript 2 3 import ( 4 "io" 5 "net/http" 6 "net/http/httptest" 7 "os" 8 "testing" 9 10 "github.com/stretchr/testify/assert" 11 12 "github.com/anchore/syft/syft/artifact" 13 "github.com/anchore/syft/syft/file" 14 "github.com/anchore/syft/syft/pkg" 15 "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" 16 ) 17 18 func TestParseYarnBerry(t *testing.T) { 19 var expectedRelationships []artifact.Relationship 20 fixture := "test-fixtures/yarn-berry/yarn.lock" 21 locations := file.NewLocationSet(file.NewLocation(fixture)) 22 23 expectedPkgs := []pkg.Package{ 24 { 25 Name: "@babel/code-frame", 26 Version: "7.10.4", 27 Locations: locations, 28 PURL: "pkg:npm/%40babel/code-frame@7.10.4", 29 Language: pkg.JavaScript, 30 Type: pkg.NpmPkg, 31 Metadata: pkg.YarnLockEntry{}, 32 }, 33 { 34 Name: "@types/minimatch", 35 Version: "3.0.3", 36 Locations: locations, 37 PURL: "pkg:npm/%40types/minimatch@3.0.3", 38 Language: pkg.JavaScript, 39 Type: pkg.NpmPkg, 40 Metadata: pkg.YarnLockEntry{}, 41 }, 42 { 43 Name: "@types/qs", 44 Version: "6.9.4", 45 Locations: locations, 46 PURL: "pkg:npm/%40types/qs@6.9.4", 47 Language: pkg.JavaScript, 48 Type: pkg.NpmPkg, 49 Metadata: pkg.YarnLockEntry{}, 50 }, 51 { 52 Name: "ajv", 53 Version: "6.12.3", 54 Locations: locations, 55 PURL: "pkg:npm/ajv@6.12.3", 56 Language: pkg.JavaScript, 57 Type: pkg.NpmPkg, 58 Metadata: pkg.YarnLockEntry{}, 59 }, 60 { 61 Name: "asn1.js", 62 Version: "4.10.1", 63 Locations: locations, 64 PURL: "pkg:npm/asn1.js@4.10.1", 65 Language: pkg.JavaScript, 66 Type: pkg.NpmPkg, 67 Metadata: pkg.YarnLockEntry{}, 68 }, 69 { 70 Name: "atob", 71 Version: "2.1.2", 72 Locations: locations, 73 PURL: "pkg:npm/atob@2.1.2", 74 Language: pkg.JavaScript, 75 Type: pkg.NpmPkg, 76 Metadata: pkg.YarnLockEntry{}, 77 }, 78 { 79 Name: "aws-sdk", 80 Version: "2.706.0", 81 PURL: "pkg:npm/aws-sdk@2.706.0", 82 Locations: locations, 83 Language: pkg.JavaScript, 84 Type: pkg.NpmPkg, 85 Metadata: pkg.YarnLockEntry{}, 86 }, 87 { 88 Name: "c0n-fab_u.laTION", 89 Version: "7.7.7", 90 Locations: locations, 91 PURL: "pkg:npm/c0n-fab_u.laTION@7.7.7", 92 Language: pkg.JavaScript, 93 Type: pkg.NpmPkg, 94 Metadata: pkg.YarnLockEntry{}, 95 }, 96 { 97 Name: "jhipster-core", 98 Version: "7.3.4", 99 Locations: locations, 100 PURL: "pkg:npm/jhipster-core@7.3.4", 101 Language: pkg.JavaScript, 102 Type: pkg.NpmPkg, 103 Metadata: pkg.YarnLockEntry{}, 104 }, 105 } 106 107 adapter := newGenericYarnLockAdapter(CatalogerConfig{}) 108 pkgtest.TestFileParser(t, fixture, adapter.parseYarnLock, expectedPkgs, expectedRelationships) 109 } 110 111 func TestParseYarnLock(t *testing.T) { 112 var expectedRelationships []artifact.Relationship 113 fixture := "test-fixtures/yarn/yarn.lock" 114 locations := file.NewLocationSet(file.NewLocation(fixture)) 115 116 expectedPkgs := []pkg.Package{ 117 { 118 Name: "@babel/code-frame", 119 Version: "7.10.4", 120 Locations: locations, 121 PURL: "pkg:npm/%40babel/code-frame@7.10.4", 122 Language: pkg.JavaScript, 123 Type: pkg.NpmPkg, 124 Metadata: pkg.YarnLockEntry{ 125 Resolved: "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a", 126 Integrity: "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", 127 }, 128 }, 129 { 130 Name: "@types/minimatch", 131 Version: "3.0.3", 132 Locations: locations, 133 PURL: "pkg:npm/%40types/minimatch@3.0.3", 134 Language: pkg.JavaScript, 135 Type: pkg.NpmPkg, 136 Metadata: pkg.YarnLockEntry{ 137 Resolved: "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d", 138 Integrity: "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", 139 }, 140 }, 141 { 142 Name: "@types/qs", 143 Version: "6.9.4", 144 Locations: locations, 145 PURL: "pkg:npm/%40types/qs@6.9.4", 146 Language: pkg.JavaScript, 147 Type: pkg.NpmPkg, 148 Metadata: pkg.YarnLockEntry{ 149 Resolved: "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a", 150 Integrity: "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==", 151 }, 152 }, 153 { 154 Name: "ajv", 155 Version: "6.12.3", 156 Locations: locations, 157 PURL: "pkg:npm/ajv@6.12.3", 158 Language: pkg.JavaScript, 159 Type: pkg.NpmPkg, 160 Metadata: pkg.YarnLockEntry{ 161 Resolved: "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706", 162 Integrity: "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", 163 }, 164 }, 165 { 166 Name: "asn1.js", 167 Version: "4.10.1", 168 Locations: locations, 169 PURL: "pkg:npm/asn1.js@4.10.1", 170 Language: pkg.JavaScript, 171 Type: pkg.NpmPkg, 172 Metadata: pkg.YarnLockEntry{ 173 Resolved: "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0", 174 Integrity: "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", 175 }, 176 }, 177 { 178 Name: "atob", 179 Version: "2.1.2", 180 Locations: locations, 181 182 PURL: "pkg:npm/atob@2.1.2", 183 Language: pkg.JavaScript, 184 Type: pkg.NpmPkg, 185 Metadata: pkg.YarnLockEntry{ 186 Resolved: "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9", 187 Integrity: "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", 188 }, 189 }, 190 { 191 Name: "aws-sdk", 192 Version: "2.706.0", 193 Locations: locations, 194 PURL: "pkg:npm/aws-sdk@2.706.0", 195 Language: pkg.JavaScript, 196 Type: pkg.NpmPkg, 197 Metadata: pkg.YarnLockEntry{ 198 Resolved: "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.706.0.tgz#09f65e9a91ecac5a635daf934082abae30eca953", 199 Integrity: "sha512-7GT+yrB5Wb/zOReRdv/Pzkb2Qt+hz6B/8FGMVaoysX3NryHvQUdz7EQWi5yhg9CxOjKxdw5lFwYSs69YlSp1KA==", 200 }, 201 }, 202 { 203 Name: "jhipster-core", 204 Version: "7.3.4", 205 Locations: locations, 206 PURL: "pkg:npm/jhipster-core@7.3.4", 207 Language: pkg.JavaScript, 208 Type: pkg.NpmPkg, 209 Metadata: pkg.YarnLockEntry{ 210 Resolved: "https://registry.yarnpkg.com/jhipster-core/-/jhipster-core-7.3.4.tgz#c34b8c97c7f4e8b7518dae015517e2112c73cc80", 211 Integrity: "sha512-AUhT69kNkqppaJZVfan/xnKG4Gs9Ggj7YLtTZFVe+xg+THrbMb5Ng7PL07PDlDw4KAEA33GMCwuAf65E8EpC4g==", 212 }, 213 }, 214 { 215 Name: "something-i-made-up", 216 Version: "7.7.7", 217 Locations: locations, 218 PURL: "pkg:npm/something-i-made-up@7.7.7", 219 Language: pkg.JavaScript, 220 Type: pkg.NpmPkg, 221 Metadata: pkg.YarnLockEntry{ 222 Resolved: "https://registry.yarnpkg.com/something-i-made-up/-/c0n-fab_u.laTION-7.7.7.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0", 223 Integrity: "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", 224 }, 225 }, 226 } 227 228 adapter := newGenericYarnLockAdapter(CatalogerConfig{}) 229 pkgtest.TestFileParser(t, fixture, adapter.parseYarnLock, expectedPkgs, expectedRelationships) 230 } 231 232 type handlerPath struct { 233 path string 234 handler func(w http.ResponseWriter, r *http.Request) 235 } 236 237 func TestSearchYarnForLicenses(t *testing.T) { 238 fixture := "test-fixtures/yarn-remote/yarn.lock" 239 locations := file.NewLocationSet(file.NewLocation(fixture)) 240 mux, url, teardown := setup() 241 defer teardown() 242 tests := []struct { 243 name string 244 fixture string 245 config CatalogerConfig 246 requestHandlers []handlerPath 247 expectedPackages []pkg.Package 248 }{ 249 { 250 name: "search remote licenses returns the expected licenses when search is set to true", 251 config: CatalogerConfig{SearchRemoteLicenses: true}, 252 requestHandlers: []handlerPath{ 253 { 254 // https://registry.yarnpkg.com/@babel/code-frame/7.10.4 255 path: "/@babel/code-frame/7.10.4", 256 handler: generateMockNPMHandler("test-fixtures/yarn-remote/registry_response.json"), 257 }, 258 }, 259 expectedPackages: []pkg.Package{ 260 { 261 Name: "@babel/code-frame", 262 Version: "7.10.4", 263 Locations: locations, 264 PURL: "pkg:npm/%40babel/code-frame@7.10.4", 265 Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")), 266 Language: pkg.JavaScript, 267 Type: pkg.NpmPkg, 268 Metadata: pkg.YarnLockEntry{ 269 Resolved: "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a", 270 Integrity: "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", 271 }, 272 }, 273 }, 274 }, 275 } 276 277 for _, tc := range tests { 278 t.Run(tc.name, func(t *testing.T) { 279 // set up the mock server 280 for _, handler := range tc.requestHandlers { 281 mux.HandleFunc(handler.path, handler.handler) 282 } 283 tc.config.NPMBaseURL = url 284 adapter := newGenericYarnLockAdapter(tc.config) 285 pkgtest.TestFileParser(t, fixture, adapter.parseYarnLock, tc.expectedPackages, nil) 286 }) 287 } 288 } 289 290 func TestParseYarnFindPackageNames(t *testing.T) { 291 tests := []struct { 292 line string 293 expected string 294 }{ 295 { 296 line: `"@babel/code-frame@npm:7.10.4":`, 297 expected: "@babel/code-frame", 298 }, 299 { 300 line: `"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":`, 301 expected: "@babel/code-frame", 302 }, 303 { 304 line: "ajv@^6.10.2, ajv@^6.5.5:", 305 expected: "ajv", 306 }, 307 { 308 line: "aws-sdk@2.706.0:", 309 expected: "aws-sdk", 310 }, 311 { 312 line: "asn1.js@^4.0.0:", 313 expected: "asn1.js", 314 }, 315 { 316 line: "c0n-fab_u.laTION@^7.0.0", 317 expected: "c0n-fab_u.laTION", 318 }, 319 { 320 line: `"newtest@workspace:.":`, 321 expected: "newtest", 322 }, 323 { 324 line: `"color-convert@npm:^1.9.0":`, 325 expected: "color-convert", 326 }, 327 { 328 line: `"@npmcorp/code-frame@^7.1.0", "@npmcorp/code-frame@^7.10.4":`, 329 expected: "@npmcorp/code-frame", 330 }, 331 { 332 line: `"@npmcorp/code-frame@^7.2.3":`, 333 expected: "@npmcorp/code-frame", 334 }, 335 { 336 line: `"@s/odd-name@^7.1.2":`, 337 expected: "@s/odd-name", 338 }, 339 { 340 line: `"@/code-frame@^7.3.4":`, 341 expected: "", 342 }, 343 { 344 line: `"code-frame":`, 345 expected: "", 346 }, 347 } 348 349 for _, test := range tests { 350 t.Run(test.expected, func(t *testing.T) { 351 t.Parallel() 352 actual := findPackageName(test.line) 353 assert.Equal(t, test.expected, actual) 354 }) 355 } 356 } 357 358 func TestParseYarnFindPackageVersions(t *testing.T) { 359 tests := []struct { 360 line string 361 expected string 362 }{ 363 { 364 line: ` version "7.10.4"`, 365 expected: "7.10.4", 366 }, 367 { 368 line: ` version "7.11.5"`, 369 expected: "7.11.5", 370 }, 371 { 372 line: `version "7.12.6"`, 373 expected: "", 374 }, 375 { 376 line: ` version "0.0.0"`, 377 expected: "0.0.0", 378 }, 379 { 380 line: ` version "2" `, 381 expected: "2", 382 }, 383 { 384 line: ` version "9.3"`, 385 expected: "9.3", 386 }, 387 { 388 line: "ajv@^6.10.2, ajv@^6.5.5", 389 expected: "", 390 }, 391 { 392 line: "atob@^2.1.2:", 393 expected: "", 394 }, 395 { 396 line: `"color-convert@npm:^1.9.0":`, 397 expected: "", 398 }, 399 { 400 line: " version: 1.9.3", 401 expected: "1.9.3", 402 }, 403 { 404 line: " version: 2", 405 expected: "2", 406 }, 407 { 408 line: " version: 9.3", 409 expected: "9.3", 410 }, 411 { 412 line: "ajv@^6.10.2, ajv@^6.5.5", 413 expected: "", 414 }, 415 { 416 line: "atob@^2.1.2:", 417 expected: "", 418 }, 419 { 420 line: " version: 1.0.0-alpha+001", 421 expected: "1.0.0-alpha", 422 }, 423 { 424 line: " version: 1.0.0-beta_test+exp.sha.5114f85", 425 expected: "1.0.0-beta_test", 426 }, 427 { 428 line: " version: 1.0.0+21AF26D3-117B344092BD", 429 expected: "1.0.0", 430 }, 431 { 432 line: " version: 0.0.0-use.local", 433 expected: "0.0.0-use.local", 434 }, 435 } 436 437 for _, test := range tests { 438 t.Run(test.expected, func(t *testing.T) { 439 t.Parallel() 440 actual := findPackageVersion(test.line) 441 assert.Equal(t, test.expected, actual) 442 }) 443 } 444 } 445 446 func generateMockNPMHandler(responseFixture string) func(w http.ResponseWriter, r *http.Request) { 447 return func(w http.ResponseWriter, r *http.Request) { 448 w.WriteHeader(http.StatusOK) 449 // Copy the file's content to the response writer 450 file, err := os.Open(responseFixture) 451 if err != nil { 452 http.Error(w, err.Error(), http.StatusInternalServerError) 453 return 454 } 455 defer file.Close() 456 457 _, err = io.Copy(w, file) 458 if err != nil { 459 http.Error(w, err.Error(), http.StatusInternalServerError) 460 return 461 } 462 } 463 } 464 465 // setup sets up a test HTTP server for mocking requests to maven central. 466 // The returned url is injected into the Config so the client uses the test server. 467 // Tests should register handlers on mux to simulate the expected request/response structure 468 func setup() (mux *http.ServeMux, serverURL string, teardown func()) { 469 // mux is the HTTP request multiplexer used with the test server. 470 mux = http.NewServeMux() 471 472 // We want to ensure that tests catch mistakes where the endpoint URL is 473 // specified as absolute rather than relative. It only makes a difference 474 // when there's a non-empty base URL path. So, use that. See issue #752. 475 apiHandler := http.NewServeMux() 476 apiHandler.Handle("/", mux) 477 // server is a test HTTP server used to provide mock API responses. 478 server := httptest.NewServer(apiHandler) 479 480 return mux, server.URL, server.Close 481 }