github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go (about) 1 package yarn 2 3 import ( 4 "context" 5 "os" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 11 "github.com/devseccon/trivy/pkg/fanal/analyzer" 12 "github.com/devseccon/trivy/pkg/fanal/types" 13 ) 14 15 func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { 16 tests := []struct { 17 name string 18 dir string 19 want *analyzer.AnalysisResult 20 }{ 21 { 22 name: "happy path", 23 dir: "testdata/happy", 24 want: &analyzer.AnalysisResult{ 25 Applications: []types.Application{ 26 { 27 Type: types.Yarn, 28 FilePath: "yarn.lock", 29 Libraries: types.Packages{ 30 { 31 ID: "js-tokens@2.0.0", 32 Name: "js-tokens", 33 Version: "2.0.0", 34 Locations: []types.Location{ 35 { 36 StartLine: 5, 37 EndLine: 8, 38 }, 39 }, 40 }, 41 { 42 ID: "js-tokens@4.0.0", 43 Name: "js-tokens", 44 Version: "4.0.0", 45 Indirect: true, 46 Locations: []types.Location{ 47 { 48 StartLine: 10, 49 EndLine: 13, 50 }, 51 }, 52 }, 53 { 54 ID: "loose-envify@1.4.0", 55 Name: "loose-envify", 56 Version: "1.4.0", 57 Indirect: true, 58 Locations: []types.Location{ 59 { 60 StartLine: 15, 61 EndLine: 20, 62 }, 63 }, 64 DependsOn: []string{ 65 "js-tokens@4.0.0", 66 }, 67 }, 68 { 69 ID: "object-assign@4.1.1", 70 Name: "object-assign", 71 Version: "4.1.1", 72 Indirect: true, 73 Locations: []types.Location{ 74 { 75 StartLine: 22, 76 EndLine: 25, 77 }, 78 }, 79 }, 80 { 81 ID: "prop-types@15.7.2", 82 Name: "prop-types", 83 Version: "15.7.2", 84 Dev: true, 85 Locations: []types.Location{ 86 { 87 StartLine: 27, 88 EndLine: 34, 89 }, 90 }, 91 DependsOn: []string{ 92 "loose-envify@1.4.0", 93 "object-assign@4.1.1", 94 "react-is@16.13.1", 95 }, 96 }, 97 { 98 ID: "react-is@16.13.1", 99 Name: "react-is", 100 Version: "16.13.1", 101 Dev: true, 102 Indirect: true, 103 Locations: []types.Location{ 104 { 105 StartLine: 36, 106 EndLine: 39, 107 }, 108 }, 109 }, 110 { 111 ID: "scheduler@0.13.6", 112 Name: "scheduler", 113 Version: "0.13.6", 114 Locations: []types.Location{ 115 { 116 StartLine: 41, 117 EndLine: 47, 118 }, 119 }, 120 DependsOn: []string{ 121 "loose-envify@1.4.0", 122 "object-assign@4.1.1", 123 }, 124 }, 125 }, 126 }, 127 }, 128 }, 129 }, 130 { 131 name: "no package.json", 132 dir: "testdata/no-packagejson", 133 want: &analyzer.AnalysisResult{ 134 Applications: []types.Application{ 135 { 136 Type: types.Yarn, 137 FilePath: "yarn.lock", 138 Libraries: types.Packages{ 139 { 140 ID: "js-tokens@2.0.0", 141 Name: "js-tokens", 142 Version: "2.0.0", 143 Locations: []types.Location{ 144 { 145 StartLine: 5, 146 EndLine: 8, 147 }, 148 }, 149 }, 150 { 151 ID: "js-tokens@4.0.0", 152 Name: "js-tokens", 153 Version: "4.0.0", 154 Locations: []types.Location{ 155 { 156 StartLine: 10, 157 EndLine: 13, 158 }, 159 }, 160 }, 161 { 162 ID: "loose-envify@1.4.0", 163 Name: "loose-envify", 164 Version: "1.4.0", 165 Locations: []types.Location{ 166 { 167 StartLine: 15, 168 EndLine: 20, 169 }, 170 }, 171 DependsOn: []string{ 172 "js-tokens@4.0.0", 173 }, 174 }, 175 { 176 ID: "object-assign@4.1.1", 177 Name: "object-assign", 178 Version: "4.1.1", 179 Locations: []types.Location{ 180 { 181 StartLine: 22, 182 EndLine: 25, 183 }, 184 }, 185 }, 186 { 187 ID: "prop-types@15.7.2", 188 Name: "prop-types", 189 Version: "15.7.2", 190 Locations: []types.Location{ 191 { 192 StartLine: 27, 193 EndLine: 34, 194 }, 195 }, 196 DependsOn: []string{ 197 "loose-envify@1.4.0", 198 "object-assign@4.1.1", 199 "react-is@16.13.1", 200 }, 201 }, 202 { 203 ID: "react-is@16.13.1", 204 Name: "react-is", 205 Version: "16.13.1", 206 Locations: []types.Location{ 207 { 208 StartLine: 36, 209 EndLine: 39, 210 }, 211 }, 212 }, 213 { 214 ID: "scheduler@0.13.6", 215 Name: "scheduler", 216 Version: "0.13.6", 217 Locations: []types.Location{ 218 { 219 StartLine: 41, 220 EndLine: 47, 221 }, 222 }, 223 DependsOn: []string{ 224 "loose-envify@1.4.0", 225 "object-assign@4.1.1", 226 }, 227 }, 228 }, 229 }, 230 }, 231 }, 232 }, 233 { 234 name: "wrong package.json", 235 dir: "testdata/wrong-packagejson", 236 want: &analyzer.AnalysisResult{ 237 Applications: []types.Application{ 238 { 239 Type: types.Yarn, 240 FilePath: "yarn.lock", 241 Libraries: types.Packages{ 242 { 243 ID: "js-tokens@2.0.0", 244 Name: "js-tokens", 245 Version: "2.0.0", 246 Locations: []types.Location{ 247 { 248 StartLine: 5, 249 EndLine: 8, 250 }, 251 }, 252 }, 253 }, 254 }, 255 }, 256 }, 257 }, 258 { 259 name: "unsupported_protocol", 260 dir: "testdata/unsupported_protocol", 261 want: &analyzer.AnalysisResult{}, 262 }, 263 // docker run --rm -it node@sha256:2d5e8a8a51bc341fd5f2eed6d91455c3a3d147e91a14298fc564b5dc519c1666 sh 264 // mkdir test && cd "$_" 265 // yarn set version 3.4.1 266 // yarn add is-callable@1.2.7 is-odd@3.0.1 267 // yarn unplug is-callable@1.2.7 268 // rm .yarn/cache/is-callable-npm* 269 { 270 name: "parse licenses (yarn v2+)", 271 dir: "testdata/yarn-licenses", 272 want: &analyzer.AnalysisResult{ 273 Applications: []types.Application{ 274 { 275 Type: types.Yarn, 276 FilePath: "yarn.lock", 277 Libraries: []types.Package{ 278 { 279 ID: "is-callable@1.2.7", 280 Name: "is-callable", 281 Version: "1.2.7", 282 Licenses: []string{"MIT"}, 283 Locations: []types.Location{ 284 { 285 StartLine: 8, 286 EndLine: 13, 287 }, 288 }, 289 }, 290 { 291 ID: "is-number@6.0.0", 292 Name: "is-number", 293 Version: "6.0.0", 294 Licenses: []string{"MIT"}, 295 Indirect: true, 296 Locations: []types.Location{ 297 { 298 StartLine: 15, 299 EndLine: 20, 300 }, 301 }, 302 }, 303 { 304 ID: "is-odd@3.0.1", 305 Name: "is-odd", 306 Version: "3.0.1", 307 Licenses: []string{"MIT"}, 308 DependsOn: []string{"is-number@6.0.0"}, 309 Locations: []types.Location{ 310 { 311 StartLine: 22, 312 EndLine: 29, 313 }, 314 }, 315 }, 316 }, 317 }, 318 }, 319 }, 320 }, 321 { 322 name: "monorepo", 323 dir: "testdata/monorepo", 324 want: &analyzer.AnalysisResult{ 325 Applications: []types.Application{ 326 { 327 Type: types.Yarn, 328 FilePath: "yarn.lock", 329 Libraries: types.Packages{ 330 { 331 ID: "is-number@6.0.0", 332 Name: "is-number", 333 Version: "6.0.0", 334 Indirect: true, 335 Locations: []types.Location{ 336 { 337 StartLine: 16, 338 EndLine: 21, 339 }, 340 }, 341 }, 342 { 343 ID: "is-number@7.0.0", 344 Name: "is-number", 345 Version: "7.0.0", 346 Locations: []types.Location{ 347 { 348 StartLine: 23, 349 EndLine: 28, 350 }, 351 }, 352 }, 353 { 354 ID: "is-odd@3.0.1", 355 Name: "is-odd", 356 Version: "3.0.1", 357 DependsOn: []string{"is-number@6.0.0"}, 358 Locations: []types.Location{ 359 { 360 StartLine: 30, 361 EndLine: 37, 362 }, 363 }, 364 }, 365 { 366 ID: "js-tokens@4.0.0", 367 Name: "js-tokens", 368 Version: "4.0.0", 369 Indirect: true, 370 Locations: []types.Location{ 371 { 372 StartLine: 39, 373 EndLine: 44, 374 }, 375 }, 376 }, 377 { 378 ID: "js-tokens@8.0.1", 379 Name: "js-tokens", 380 Version: "8.0.1", 381 Locations: []types.Location{ 382 { 383 StartLine: 46, 384 EndLine: 51, 385 }, 386 }, 387 }, 388 { 389 ID: "loose-envify@1.4.0", 390 Name: "loose-envify", 391 Version: "1.4.0", 392 Indirect: true, 393 DependsOn: []string{"js-tokens@4.0.0"}, 394 Locations: []types.Location{ 395 { 396 StartLine: 53, 397 EndLine: 62, 398 }, 399 }, 400 }, 401 { 402 ID: "object-assign@4.1.1", 403 Name: "object-assign", 404 Version: "4.1.1", 405 Indirect: true, 406 Dev: true, 407 Locations: []types.Location{ 408 { 409 StartLine: 64, 410 EndLine: 69, 411 }, 412 }, 413 }, 414 { 415 ID: "prettier@2.8.8", 416 Name: "prettier", 417 Version: "2.8.8", 418 Dev: true, 419 Locations: []types.Location{ 420 { 421 StartLine: 87, 422 EndLine: 94, 423 }, 424 }, 425 }, 426 { 427 ID: "prop-types@15.8.1", 428 Name: "prop-types", 429 Version: "15.8.1", 430 Dev: true, 431 Locations: []types.Location{ 432 { 433 StartLine: 96, 434 EndLine: 105, 435 }, 436 }, 437 DependsOn: []string{ 438 "loose-envify@1.4.0", 439 "object-assign@4.1.1", 440 "react-is@16.13.1", 441 }, 442 }, 443 { 444 ID: "react-is@16.13.1", 445 Name: "react-is", 446 Version: "16.13.1", 447 Dev: true, 448 Indirect: true, 449 Locations: []types.Location{ 450 { 451 StartLine: 107, 452 EndLine: 112, 453 }, 454 }, 455 }, 456 { 457 ID: "scheduler@0.23.0", 458 Name: "scheduler", 459 Version: "0.23.0", 460 DependsOn: []string{"loose-envify@1.4.0"}, 461 Locations: []types.Location{ 462 { 463 StartLine: 114, 464 EndLine: 121, 465 }, 466 }, 467 }, 468 }, 469 }, 470 }, 471 }, 472 }, 473 // docker run --rm -it node@sha256:2d5e8a8a51bc341fd5f2eed6d91455c3a3d147e91a14298fc564b5dc519c1666 sh 474 // mkdir test && cd "$_" 475 // yarn set version 1.22.19 476 // yarn add @vue/compiler-sfc@2.7.14 477 { 478 name: "parse licenses (yarn classic)", 479 dir: "testdata/yarn-classic-licenses", 480 want: &analyzer.AnalysisResult{ 481 Applications: []types.Application{ 482 { 483 Type: types.Yarn, 484 FilePath: "yarn.lock", 485 Libraries: []types.Package{ 486 { 487 ID: "@babel/parser@7.22.7", 488 Name: "@babel/parser", 489 Version: "7.22.7", 490 Indirect: true, 491 Locations: []types.Location{ 492 { 493 StartLine: 5, 494 EndLine: 8, 495 }, 496 }, 497 Licenses: []string{"MIT"}, 498 }, 499 { 500 ID: "@vue/compiler-sfc@2.7.14", 501 Name: "@vue/compiler-sfc", 502 Version: "2.7.14", 503 Indirect: false, 504 Locations: []types.Location{ 505 { 506 StartLine: 10, 507 EndLine: 17, 508 }, 509 }, 510 Licenses: []string{"MIT"}, 511 DependsOn: []string{ 512 "@babel/parser@7.22.7", 513 "postcss@8.4.27", 514 "source-map@0.6.1", 515 }, 516 }, 517 { 518 ID: "nanoid@3.3.6", 519 Name: "nanoid", 520 Version: "3.3.6", 521 Indirect: true, 522 Locations: []types.Location{ 523 { 524 StartLine: 19, 525 EndLine: 22, 526 }, 527 }, 528 Licenses: []string{"MIT"}, 529 }, 530 { 531 ID: "picocolors@1.0.0", 532 Name: "picocolors", 533 Version: "1.0.0", 534 Indirect: true, 535 Locations: []types.Location{ 536 { 537 StartLine: 24, 538 EndLine: 27, 539 }, 540 }, 541 Licenses: []string{"ISC"}, 542 }, 543 { 544 ID: "postcss@8.4.27", 545 Name: "postcss", 546 Version: "8.4.27", 547 Indirect: true, 548 Locations: []types.Location{ 549 { 550 StartLine: 29, 551 EndLine: 36, 552 }, 553 }, 554 Licenses: []string{"MIT"}, 555 DependsOn: []string{ 556 "nanoid@3.3.6", 557 "picocolors@1.0.0", 558 "source-map-js@1.0.2", 559 }, 560 }, 561 { 562 ID: "source-map@0.6.1", 563 Name: "source-map", 564 Version: "0.6.1", 565 Indirect: true, 566 Locations: []types.Location{ 567 { 568 StartLine: 43, 569 EndLine: 46, 570 }, 571 }, 572 Licenses: []string{"BSD-3-Clause"}, 573 }, 574 { 575 ID: "source-map-js@1.0.2", 576 Name: "source-map-js", 577 Version: "1.0.2", 578 Indirect: true, 579 Locations: []types.Location{ 580 { 581 StartLine: 38, 582 EndLine: 41, 583 }, 584 }, 585 Licenses: []string{"BSD-3-Clause"}, 586 }, 587 }, 588 }, 589 }, 590 }, 591 }, 592 } 593 for _, tt := range tests { 594 t.Run(tt.name, func(t *testing.T) { 595 a, err := newYarnAnalyzer(analyzer.AnalyzerOptions{}) 596 require.NoError(t, err) 597 598 got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ 599 FS: os.DirFS(tt.dir), 600 }) 601 602 assert.NoError(t, err) 603 assert.Equal(t, tt.want, got) 604 }) 605 } 606 } 607 608 func Test_yarnLibraryAnalyzer_Required(t *testing.T) { 609 tests := []struct { 610 name string 611 filePath string 612 want bool 613 }{ 614 { 615 name: "happy path yarn.lock", 616 filePath: "test/yarn.lock", 617 want: true, 618 }, 619 { 620 name: "sad path", 621 filePath: "test/package-lock.json", 622 want: false, 623 }, 624 { 625 name: "yarn cache", 626 filePath: ".yarn/cache/websocket-driver-npm-0.7.4-a72739da70-fffe5a33fe.zip", 627 want: true, 628 }, 629 { 630 name: "not a yarn cache", 631 filePath: "cache/is-number-npm-6.0.0-30881e83e6-f73bfced03.zip", 632 want: false, 633 }, 634 { 635 name: "yarn.lock in node_modules", 636 filePath: "somedir/node_modules/uri-js/yarn.lock", 637 want: false, 638 }, 639 { 640 name: "yarn.lock in unplugged", 641 filePath: "somedir/.yarn/unplugged/uri-js/yarn.lock", 642 want: false, 643 }, 644 { 645 name: "deep package.json", 646 filePath: "somedir/node_modules/canvg/node_modules/parse5/package.json", 647 want: true, 648 }, 649 { 650 name: "license file", 651 filePath: "node_modules/@vue/compiler-sfc/LICENSE", 652 want: true, 653 }, 654 { 655 name: "txt license file", 656 filePath: "node_modules/@vue/compiler-sfc/LICENSE.txt", 657 want: true, 658 }, 659 } 660 for _, tt := range tests { 661 t.Run(tt.name, func(t *testing.T) { 662 a := yarnAnalyzer{} 663 got := a.Required(tt.filePath, nil) 664 assert.Equal(t, tt.want, got) 665 }) 666 } 667 }