github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/javascript/packagelockjson/packagelockjson-v2_test.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package packagelockjson_test 16 17 import ( 18 "testing" 19 20 "github.com/google/go-cmp/cmp" 21 "github.com/google/go-cmp/cmp/cmpopts" 22 "github.com/google/osv-scalibr/extractor" 23 "github.com/google/osv-scalibr/extractor/filesystem/language/javascript/packagelockjson" 24 "github.com/google/osv-scalibr/extractor/filesystem/osv" 25 "github.com/google/osv-scalibr/inventory" 26 "github.com/google/osv-scalibr/purl" 27 "github.com/google/osv-scalibr/testing/extracttest" 28 "github.com/google/osv-scalibr/testing/testcollector" 29 ) 30 31 func TestNPMLockExtractor_Extract_V2(t *testing.T) { 32 tests := []extracttest.TestTableEntry{ 33 { 34 Name: "invalid json", 35 InputConfig: extracttest.ScanInputMockConfig{ 36 Path: "testdata/not-json.txt", 37 }, 38 WantErr: extracttest.ContainsErrStr{Str: "could not extract"}, 39 }, 40 { 41 Name: "no packages", 42 InputConfig: extracttest.ScanInputMockConfig{ 43 Path: "testdata/empty.v2.json", 44 }, 45 WantPackages: []*extractor.Package{}, 46 }, 47 { 48 Name: "one package", 49 InputConfig: extracttest.ScanInputMockConfig{ 50 Path: "testdata/one-package.v2.json", 51 }, 52 WantPackages: []*extractor.Package{ 53 { 54 Name: "wrappy", 55 Version: "1.0.2", 56 PURLType: purl.TypeNPM, 57 Locations: []string{"testdata/one-package.v2.json"}, 58 SourceCode: &extractor.SourceCodeIdentifier{ 59 Commit: "", 60 }, 61 Metadata: osv.DepGroupMetadata{ 62 DepGroupVals: []string{}, 63 }, 64 }, 65 }, 66 }, 67 { 68 Name: "one package dev", 69 InputConfig: extracttest.ScanInputMockConfig{ 70 Path: "testdata/one-package-dev.v2.json", 71 }, 72 WantPackages: []*extractor.Package{ 73 { 74 Name: "wrappy", 75 Version: "1.0.2", 76 PURLType: purl.TypeNPM, 77 Locations: []string{"testdata/one-package-dev.v2.json"}, 78 SourceCode: &extractor.SourceCodeIdentifier{ 79 Commit: "", 80 }, 81 Metadata: osv.DepGroupMetadata{ 82 DepGroupVals: []string{"dev"}, 83 }, 84 }, 85 }, 86 }, 87 { 88 Name: "two packages", 89 InputConfig: extracttest.ScanInputMockConfig{ 90 Path: "testdata/two-packages.v2.json", 91 }, 92 WantPackages: []*extractor.Package{ 93 { 94 Name: "wrappy", 95 Version: "1.0.2", 96 PURLType: purl.TypeNPM, 97 Locations: []string{"testdata/two-packages.v2.json"}, 98 SourceCode: &extractor.SourceCodeIdentifier{ 99 Commit: "", 100 }, 101 Metadata: osv.DepGroupMetadata{ 102 DepGroupVals: []string{}, 103 }, 104 }, 105 { 106 Name: "supports-color", 107 Version: "5.5.0", 108 PURLType: purl.TypeNPM, 109 Locations: []string{"testdata/two-packages.v2.json"}, 110 SourceCode: &extractor.SourceCodeIdentifier{ 111 Commit: "", 112 }, 113 Metadata: osv.DepGroupMetadata{ 114 DepGroupVals: []string{}, 115 }, 116 }, 117 }, 118 }, 119 { 120 Name: "scoped packages", 121 InputConfig: extracttest.ScanInputMockConfig{ 122 Path: "testdata/scoped-packages.v2.json", 123 }, 124 WantPackages: []*extractor.Package{ 125 { 126 Name: "wrappy", 127 Version: "1.0.2", 128 PURLType: purl.TypeNPM, 129 Locations: []string{"testdata/scoped-packages.v2.json"}, 130 SourceCode: &extractor.SourceCodeIdentifier{ 131 Commit: "", 132 }, 133 Metadata: osv.DepGroupMetadata{ 134 DepGroupVals: []string{}, 135 }, 136 }, 137 { 138 Name: "@babel/code-frame", 139 Version: "7.0.0", 140 PURLType: purl.TypeNPM, 141 Locations: []string{"testdata/scoped-packages.v2.json"}, 142 SourceCode: &extractor.SourceCodeIdentifier{ 143 Commit: "", 144 }, 145 Metadata: osv.DepGroupMetadata{ 146 DepGroupVals: []string{}, 147 }, 148 }, 149 }, 150 }, 151 { 152 Name: "nested dependencies", 153 InputConfig: extracttest.ScanInputMockConfig{ 154 Path: "testdata/nested-dependencies.v2.json", 155 }, 156 WantPackages: []*extractor.Package{ 157 { 158 Name: "postcss", 159 Version: "6.0.23", 160 PURLType: purl.TypeNPM, 161 Locations: []string{"testdata/nested-dependencies.v2.json"}, 162 SourceCode: &extractor.SourceCodeIdentifier{ 163 Commit: "", 164 }, 165 Metadata: osv.DepGroupMetadata{ 166 DepGroupVals: []string{}, 167 }, 168 }, 169 { 170 Name: "postcss", 171 Version: "7.0.16", 172 PURLType: purl.TypeNPM, 173 Locations: []string{"testdata/nested-dependencies.v2.json"}, 174 SourceCode: &extractor.SourceCodeIdentifier{ 175 Commit: "", 176 }, 177 Metadata: osv.DepGroupMetadata{ 178 DepGroupVals: []string{}, 179 }, 180 }, 181 { 182 Name: "postcss-calc", 183 Version: "7.0.1", 184 PURLType: purl.TypeNPM, 185 Locations: []string{"testdata/nested-dependencies.v2.json"}, 186 SourceCode: &extractor.SourceCodeIdentifier{ 187 Commit: "", 188 }, 189 Metadata: osv.DepGroupMetadata{ 190 DepGroupVals: []string{}, 191 }, 192 }, 193 { 194 Name: "supports-color", 195 Version: "6.1.0", 196 PURLType: purl.TypeNPM, 197 Locations: []string{"testdata/nested-dependencies.v2.json"}, 198 SourceCode: &extractor.SourceCodeIdentifier{ 199 Commit: "", 200 }, 201 Metadata: osv.DepGroupMetadata{ 202 DepGroupVals: []string{}, 203 }, 204 }, 205 { 206 Name: "supports-color", 207 Version: "5.5.0", 208 PURLType: purl.TypeNPM, 209 Locations: []string{"testdata/nested-dependencies.v2.json"}, 210 SourceCode: &extractor.SourceCodeIdentifier{ 211 Commit: "", 212 }, 213 Metadata: osv.DepGroupMetadata{ 214 DepGroupVals: []string{}, 215 }, 216 }, 217 }, 218 }, 219 { 220 Name: "nested dependencies dup", 221 InputConfig: extracttest.ScanInputMockConfig{ 222 Path: "testdata/nested-dependencies-dup.v2.json", 223 }, 224 WantPackages: []*extractor.Package{ 225 { 226 Name: "supports-color", 227 Version: "6.1.0", 228 PURLType: purl.TypeNPM, 229 Locations: []string{"testdata/nested-dependencies-dup.v2.json"}, 230 SourceCode: &extractor.SourceCodeIdentifier{ 231 Commit: "", 232 }, 233 Metadata: osv.DepGroupMetadata{ 234 DepGroupVals: []string{}, 235 }, 236 }, 237 { 238 Name: "supports-color", 239 Version: "2.0.0", 240 PURLType: purl.TypeNPM, 241 Locations: []string{"testdata/nested-dependencies-dup.v2.json"}, 242 SourceCode: &extractor.SourceCodeIdentifier{ 243 Commit: "", 244 }, 245 Metadata: osv.DepGroupMetadata{ 246 DepGroupVals: []string{}, 247 }, 248 }, 249 }, 250 }, 251 { 252 Name: "commits", 253 InputConfig: extracttest.ScanInputMockConfig{ 254 Path: "testdata/commits.v2.json", 255 }, 256 WantPackages: []*extractor.Package{ 257 { 258 Name: "@segment/analytics.js-integration-facebook-pixel", 259 Version: "2.4.1", 260 PURLType: purl.TypeNPM, 261 Locations: []string{"testdata/commits.v2.json"}, 262 SourceCode: &extractor.SourceCodeIdentifier{ 263 Commit: "3b1bb80b302c2e552685dc8a029797ec832ea7c9", 264 }, 265 Metadata: osv.DepGroupMetadata{ 266 DepGroupVals: []string{}, 267 }, 268 }, 269 { 270 Name: "ansi-styles", 271 Version: "1.0.0", 272 PURLType: purl.TypeNPM, 273 Locations: []string{"testdata/commits.v2.json"}, 274 SourceCode: &extractor.SourceCodeIdentifier{ 275 Commit: "", 276 }, 277 Metadata: osv.DepGroupMetadata{ 278 DepGroupVals: []string{}, 279 }, 280 }, 281 { 282 Name: "babel-preset-php", 283 Version: "1.1.1", 284 PURLType: purl.TypeNPM, 285 Locations: []string{"testdata/commits.v2.json"}, 286 SourceCode: &extractor.SourceCodeIdentifier{ 287 Commit: "c5a7ba5e0ad98b8db1cb8ce105403dd4b768cced", 288 }, 289 Metadata: osv.DepGroupMetadata{ 290 DepGroupVals: []string{"dev"}, 291 }, 292 }, 293 { 294 Name: "is-number-1", 295 Version: "3.0.0", 296 PURLType: purl.TypeNPM, 297 Locations: []string{"testdata/commits.v2.json"}, 298 SourceCode: &extractor.SourceCodeIdentifier{ 299 Commit: "af885e2e890b9ef0875edd2b117305119ee5bdc5", 300 }, 301 Metadata: osv.DepGroupMetadata{ 302 DepGroupVals: []string{"dev"}, 303 }, 304 }, 305 { 306 Name: "is-number-1", 307 Version: "3.0.0", 308 PURLType: purl.TypeNPM, 309 Locations: []string{"testdata/commits.v2.json"}, 310 SourceCode: &extractor.SourceCodeIdentifier{ 311 Commit: "be5935f8d2595bcd97b05718ef1eeae08d812e10", 312 }, 313 Metadata: osv.DepGroupMetadata{ 314 DepGroupVals: []string{"dev"}, 315 }, 316 }, 317 { 318 Name: "is-number-2", 319 Version: "2.0.0", 320 PURLType: purl.TypeNPM, 321 Locations: []string{"testdata/commits.v2.json"}, 322 SourceCode: &extractor.SourceCodeIdentifier{ 323 Commit: "d5ac0584ee9ae7bd9288220a39780f155b9ad4c8", 324 }, 325 Metadata: osv.DepGroupMetadata{ 326 DepGroupVals: []string{"dev"}, 327 }, 328 }, 329 { 330 Name: "is-number-2", 331 Version: "2.0.0", 332 PURLType: purl.TypeNPM, 333 Locations: []string{"testdata/commits.v2.json"}, 334 SourceCode: &extractor.SourceCodeIdentifier{ 335 Commit: "82dcc8e914dabd9305ab9ae580709a7825e824f5", 336 }, 337 Metadata: osv.DepGroupMetadata{ 338 DepGroupVals: []string{"dev"}, 339 }, 340 }, 341 { 342 Name: "is-number-3", 343 Version: "2.0.0", 344 PURLType: purl.TypeNPM, 345 Locations: []string{"testdata/commits.v2.json"}, 346 SourceCode: &extractor.SourceCodeIdentifier{ 347 Commit: "d5ac0584ee9ae7bd9288220a39780f155b9ad4c8", 348 }, 349 Metadata: osv.DepGroupMetadata{ 350 DepGroupVals: []string{"dev"}, 351 }, 352 }, 353 { 354 Name: "is-number-3", 355 Version: "3.0.0", 356 PURLType: purl.TypeNPM, 357 Locations: []string{"testdata/commits.v2.json"}, 358 SourceCode: &extractor.SourceCodeIdentifier{ 359 Commit: "82ae8802978da40d7f1be5ad5943c9e550ab2c89", 360 }, 361 Metadata: osv.DepGroupMetadata{ 362 DepGroupVals: []string{"dev"}, 363 }, 364 }, 365 { 366 Name: "is-number-4", 367 Version: "3.0.0", 368 PURLType: purl.TypeNPM, 369 Locations: []string{"testdata/commits.v2.json"}, 370 SourceCode: &extractor.SourceCodeIdentifier{ 371 Commit: "af885e2e890b9ef0875edd2b117305119ee5bdc5", 372 }, 373 Metadata: osv.DepGroupMetadata{ 374 DepGroupVals: []string{"dev"}, 375 }, 376 }, 377 { 378 Name: "is-number-5", 379 Version: "3.0.0", 380 PURLType: purl.TypeNPM, 381 Locations: []string{"testdata/commits.v2.json"}, 382 SourceCode: &extractor.SourceCodeIdentifier{ 383 Commit: "af885e2e890b9ef0875edd2b117305119ee5bdc5", 384 }, 385 Metadata: osv.DepGroupMetadata{ 386 DepGroupVals: []string{"dev"}, 387 }, 388 }, 389 { 390 Name: "postcss-calc", 391 Version: "7.0.1", 392 PURLType: purl.TypeNPM, 393 Locations: []string{"testdata/commits.v2.json"}, 394 SourceCode: &extractor.SourceCodeIdentifier{ 395 Commit: "", 396 }, 397 Metadata: osv.DepGroupMetadata{ 398 DepGroupVals: []string{}, 399 }, 400 }, 401 { 402 Name: "raven-js", 403 Version: "", 404 PURLType: purl.TypeNPM, 405 Locations: []string{"testdata/commits.v2.json"}, 406 SourceCode: &extractor.SourceCodeIdentifier{ 407 Commit: "c2b377e7a254264fd4a1fe328e4e3cfc9e245570", 408 }, 409 Metadata: osv.DepGroupMetadata{ 410 DepGroupVals: []string{}, 411 }, 412 }, 413 { 414 Name: "slick-carousel", 415 Version: "1.7.1", 416 PURLType: purl.TypeNPM, 417 Locations: []string{"testdata/commits.v2.json"}, 418 SourceCode: &extractor.SourceCodeIdentifier{ 419 Commit: "280b560161b751ba226d50c7db1e0a14a78c2de0", 420 }, 421 Metadata: osv.DepGroupMetadata{ 422 DepGroupVals: []string{"dev"}, 423 }, 424 }, 425 }, 426 }, 427 { 428 Name: "files", 429 InputConfig: extracttest.ScanInputMockConfig{ 430 Path: "testdata/files.v2.json", 431 }, 432 WantPackages: []*extractor.Package{ 433 { 434 Name: "etag", 435 Version: "1.8.0", 436 PURLType: purl.TypeNPM, 437 Locations: []string{"testdata/files.v2.json"}, 438 SourceCode: &extractor.SourceCodeIdentifier{ 439 Commit: "", 440 }, 441 Metadata: osv.DepGroupMetadata{ 442 DepGroupVals: []string{"dev"}, 443 }, 444 }, 445 { 446 Name: "abbrev", 447 Version: "1.0.9", 448 PURLType: purl.TypeNPM, 449 Locations: []string{"testdata/files.v2.json"}, 450 SourceCode: &extractor.SourceCodeIdentifier{ 451 Commit: "", 452 }, 453 Metadata: osv.DepGroupMetadata{ 454 DepGroupVals: []string{"dev"}, 455 }, 456 }, 457 { 458 Name: "abbrev", 459 Version: "2.3.4", 460 PURLType: purl.TypeNPM, 461 Locations: []string{"testdata/files.v2.json"}, 462 SourceCode: &extractor.SourceCodeIdentifier{ 463 Commit: "", 464 }, 465 Metadata: osv.DepGroupMetadata{ 466 DepGroupVals: []string{"dev"}, 467 }, 468 }, 469 }, 470 }, 471 { 472 Name: "alias", 473 InputConfig: extracttest.ScanInputMockConfig{ 474 Path: "testdata/alias.v2.json", 475 }, 476 WantPackages: []*extractor.Package{ 477 { 478 Name: "@babel/code-frame", 479 Version: "7.0.0", 480 PURLType: purl.TypeNPM, 481 Locations: []string{"testdata/alias.v2.json"}, 482 SourceCode: &extractor.SourceCodeIdentifier{ 483 Commit: "", 484 }, 485 Metadata: osv.DepGroupMetadata{ 486 DepGroupVals: []string{}, 487 }, 488 }, 489 { 490 Name: "string-width", 491 Version: "4.2.0", 492 PURLType: purl.TypeNPM, 493 Locations: []string{"testdata/alias.v2.json"}, 494 SourceCode: &extractor.SourceCodeIdentifier{ 495 Commit: "", 496 }, 497 Metadata: osv.DepGroupMetadata{ 498 DepGroupVals: []string{}, 499 }, 500 }, 501 { 502 Name: "string-width", 503 Version: "5.1.2", 504 PURLType: purl.TypeNPM, 505 Locations: []string{"testdata/alias.v2.json"}, 506 SourceCode: &extractor.SourceCodeIdentifier{ 507 Commit: "", 508 }, 509 Metadata: osv.DepGroupMetadata{ 510 DepGroupVals: []string{}, 511 }, 512 }, 513 }, 514 }, 515 { 516 Name: "optional package", 517 InputConfig: extracttest.ScanInputMockConfig{ 518 Path: "testdata/optional-package.v2.json", 519 }, 520 WantPackages: []*extractor.Package{ 521 { 522 Name: "wrappy", 523 Version: "1.0.2", 524 PURLType: purl.TypeNPM, 525 Locations: []string{"testdata/optional-package.v2.json"}, 526 SourceCode: &extractor.SourceCodeIdentifier{ 527 Commit: "", 528 }, 529 Metadata: osv.DepGroupMetadata{ 530 DepGroupVals: []string{"optional"}, 531 }, 532 }, 533 { 534 Name: "supports-color", 535 Version: "5.5.0", 536 PURLType: purl.TypeNPM, 537 Locations: []string{"testdata/optional-package.v2.json"}, 538 SourceCode: &extractor.SourceCodeIdentifier{ 539 Commit: "", 540 }, 541 Metadata: osv.DepGroupMetadata{ 542 DepGroupVals: []string{"dev", "optional"}, 543 }, 544 }, 545 }, 546 }, 547 { 548 Name: "same package different groups", 549 InputConfig: extracttest.ScanInputMockConfig{ 550 Path: "testdata/same-package-different-groups.v2.json", 551 }, 552 WantPackages: []*extractor.Package{ 553 { 554 Name: "eslint", 555 Version: "1.2.3", 556 PURLType: purl.TypeNPM, 557 Locations: []string{"testdata/same-package-different-groups.v2.json"}, 558 SourceCode: &extractor.SourceCodeIdentifier{ 559 Commit: "", 560 }, 561 Metadata: osv.DepGroupMetadata{ 562 DepGroupVals: []string{"dev"}, 563 }, 564 }, 565 { 566 Name: "table", 567 Version: "1.0.0", 568 PURLType: purl.TypeNPM, 569 Locations: []string{"testdata/same-package-different-groups.v2.json"}, 570 SourceCode: &extractor.SourceCodeIdentifier{ 571 Commit: "", 572 }, 573 Metadata: osv.DepGroupMetadata{ 574 DepGroupVals: []string{}, 575 }, 576 }, 577 { 578 Name: "ajv", 579 Version: "5.5.2", 580 PURLType: purl.TypeNPM, 581 Locations: []string{"testdata/same-package-different-groups.v2.json"}, 582 SourceCode: &extractor.SourceCodeIdentifier{ 583 Commit: "", 584 }, 585 Metadata: osv.DepGroupMetadata{ 586 DepGroupVals: []string{}, 587 }, 588 }, 589 }, 590 }, 591 { 592 Name: "bundled_dependencies_are_grouped", 593 InputConfig: extracttest.ScanInputMockConfig{ 594 Path: "testdata/bundled-dependencies.v3.json", 595 }, 596 WantPackages: []*extractor.Package{ 597 { 598 Name: "ansi-regex", 599 Version: "6.2.2", 600 PURLType: purl.TypeNPM, 601 Locations: []string{"testdata/bundled-dependencies.v3.json"}, 602 SourceCode: &extractor.SourceCodeIdentifier{ 603 Commit: "", 604 }, 605 Metadata: osv.DepGroupMetadata{ 606 DepGroupVals: []string{"bundled"}, 607 }, 608 }, 609 { 610 Name: "semver", 611 Version: "7.7.2", 612 PURLType: purl.TypeNPM, 613 Locations: []string{"testdata/bundled-dependencies.v3.json"}, 614 SourceCode: &extractor.SourceCodeIdentifier{ 615 Commit: "", 616 }, 617 Metadata: osv.DepGroupMetadata{ 618 DepGroupVals: []string{"bundled", "dev"}, 619 }, 620 }, 621 { 622 Name: "strip-ansi", 623 Version: "7.1.2", 624 PURLType: purl.TypeNPM, 625 Locations: []string{"testdata/bundled-dependencies.v3.json"}, 626 SourceCode: &extractor.SourceCodeIdentifier{ 627 Commit: "", 628 }, 629 Metadata: osv.DepGroupMetadata{ 630 DepGroupVals: []string{}, 631 }, 632 }, 633 }, 634 }, 635 } 636 637 for _, tt := range tests { 638 t.Run(tt.Name, func(t *testing.T) { 639 collector := testcollector.New() 640 extr := packagelockjson.New(packagelockjson.Config{ 641 Stats: collector, 642 }) 643 644 scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig) 645 defer extracttest.CloseTestScanInput(t, scanInput) 646 647 got, err := extr.Extract(t.Context(), &scanInput) 648 649 if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" { 650 t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 651 return 652 } 653 654 wantInv := inventory.Inventory{Packages: tt.WantPackages} 655 if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" { 656 t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 657 } 658 659 gotFileSizeMetric := collector.FileExtractedFileSize(tt.InputConfig.Path) 660 if gotFileSizeMetric != scanInput.Info.Size() { 661 t.Errorf("Extract(%s) recorded file size %v, want file size %v", tt.InputConfig.Path, gotFileSizeMetric, scanInput.Info.Size()) 662 } 663 }) 664 } 665 }