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  }