github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/javascript/yarnlock/yarnlock-v1_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 yarnlock_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/yarnlock"
    24  	"github.com/google/osv-scalibr/inventory"
    25  	"github.com/google/osv-scalibr/purl"
    26  	"github.com/google/osv-scalibr/testing/extracttest"
    27  )
    28  
    29  func TestExtractor_Extract_v1(t *testing.T) {
    30  	tests := []extracttest.TestTableEntry{
    31  		{
    32  			Name: "no packages",
    33  			InputConfig: extracttest.ScanInputMockConfig{
    34  				Path: "testdata/empty.v1.lock",
    35  			},
    36  			WantPackages: []*extractor.Package{},
    37  		},
    38  		{
    39  			Name: "one package",
    40  			InputConfig: extracttest.ScanInputMockConfig{
    41  				Path: "testdata/one-package.v1.lock",
    42  			},
    43  			WantPackages: []*extractor.Package{
    44  				{
    45  					Name:      "balanced-match",
    46  					Version:   "1.0.2",
    47  					PURLType:  purl.TypeNPM,
    48  					Locations: []string{"testdata/one-package.v1.lock"},
    49  					SourceCode: &extractor.SourceCodeIdentifier{
    50  						Commit: "",
    51  					},
    52  				},
    53  			},
    54  		},
    55  		{
    56  			Name: "package with no version in header",
    57  			InputConfig: extracttest.ScanInputMockConfig{
    58  				Path: "testdata/no-version.v1.lock",
    59  			},
    60  			WantPackages: []*extractor.Package{
    61  				{
    62  					Name:      "balanced-match",
    63  					Version:   "1.0.2",
    64  					PURLType:  purl.TypeNPM,
    65  					Locations: []string{"testdata/no-version.v1.lock"},
    66  					SourceCode: &extractor.SourceCodeIdentifier{
    67  						Commit: "",
    68  					},
    69  				},
    70  			},
    71  		},
    72  		{
    73  			Name: "two packages",
    74  			InputConfig: extracttest.ScanInputMockConfig{
    75  				Path: "testdata/two-packages.v1.lock",
    76  			},
    77  			WantPackages: []*extractor.Package{
    78  				{
    79  					Name:      "concat-stream",
    80  					Version:   "1.6.2",
    81  					PURLType:  purl.TypeNPM,
    82  					Locations: []string{"testdata/two-packages.v1.lock"},
    83  					SourceCode: &extractor.SourceCodeIdentifier{
    84  						Commit: "",
    85  					},
    86  				},
    87  				{
    88  					Name:      "concat-map",
    89  					Version:   "0.0.1",
    90  					PURLType:  purl.TypeNPM,
    91  					Locations: []string{"testdata/two-packages.v1.lock"},
    92  					SourceCode: &extractor.SourceCodeIdentifier{
    93  						Commit: "",
    94  					},
    95  				},
    96  			},
    97  		},
    98  		{
    99  			Name: "with quotes",
   100  			InputConfig: extracttest.ScanInputMockConfig{
   101  				Path: "testdata/with-quotes.v1.lock",
   102  			},
   103  			WantPackages: []*extractor.Package{
   104  				{
   105  					Name:      "concat-stream",
   106  					Version:   "1.6.2",
   107  					PURLType:  purl.TypeNPM,
   108  					Locations: []string{"testdata/with-quotes.v1.lock"},
   109  					SourceCode: &extractor.SourceCodeIdentifier{
   110  						Commit: "",
   111  					},
   112  				},
   113  				{
   114  					Name:      "concat-map",
   115  					Version:   "0.0.1",
   116  					PURLType:  purl.TypeNPM,
   117  					Locations: []string{"testdata/with-quotes.v1.lock"},
   118  					SourceCode: &extractor.SourceCodeIdentifier{
   119  						Commit: "",
   120  					},
   121  				},
   122  			},
   123  		},
   124  		{
   125  			Name: "multiple versions",
   126  			InputConfig: extracttest.ScanInputMockConfig{
   127  				Path: "testdata/multiple-versions.v1.lock",
   128  			},
   129  			WantPackages: []*extractor.Package{
   130  				{
   131  					Name:      "define-properties",
   132  					Version:   "1.1.3",
   133  					PURLType:  purl.TypeNPM,
   134  					Locations: []string{"testdata/multiple-versions.v1.lock"},
   135  					SourceCode: &extractor.SourceCodeIdentifier{
   136  						Commit: "",
   137  					},
   138  				},
   139  				{
   140  					Name:      "define-property",
   141  					Version:   "0.2.5",
   142  					PURLType:  purl.TypeNPM,
   143  					Locations: []string{"testdata/multiple-versions.v1.lock"},
   144  					SourceCode: &extractor.SourceCodeIdentifier{
   145  						Commit: "",
   146  					},
   147  				},
   148  				{
   149  					Name:      "define-property",
   150  					Version:   "1.0.0",
   151  					PURLType:  purl.TypeNPM,
   152  					Locations: []string{"testdata/multiple-versions.v1.lock"},
   153  					SourceCode: &extractor.SourceCodeIdentifier{
   154  						Commit: "",
   155  					},
   156  				},
   157  				{
   158  					Name:      "define-property",
   159  					Version:   "2.0.2",
   160  					PURLType:  purl.TypeNPM,
   161  					Locations: []string{"testdata/multiple-versions.v1.lock"},
   162  					SourceCode: &extractor.SourceCodeIdentifier{
   163  						Commit: "",
   164  					},
   165  				},
   166  			},
   167  		},
   168  		{
   169  			Name: "multiple constraints",
   170  			InputConfig: extracttest.ScanInputMockConfig{
   171  				Path: "testdata/multiple-constraints.v1.lock",
   172  			},
   173  			WantPackages: []*extractor.Package{
   174  				{
   175  					Name:      "@babel/code-frame",
   176  					Version:   "7.12.13",
   177  					PURLType:  purl.TypeNPM,
   178  					Locations: []string{"testdata/multiple-constraints.v1.lock"},
   179  					SourceCode: &extractor.SourceCodeIdentifier{
   180  						Commit: "",
   181  					},
   182  				},
   183  				{
   184  					Name:      "domelementtype",
   185  					Version:   "1.3.1",
   186  					PURLType:  purl.TypeNPM,
   187  					Locations: []string{"testdata/multiple-constraints.v1.lock"},
   188  					SourceCode: &extractor.SourceCodeIdentifier{
   189  						Commit: "",
   190  					},
   191  				},
   192  			},
   193  		},
   194  		{
   195  			Name: "scoped packages",
   196  			InputConfig: extracttest.ScanInputMockConfig{
   197  				Path: "testdata/scoped-packages.v1.lock",
   198  			},
   199  			WantPackages: []*extractor.Package{
   200  				{
   201  					Name:      "@babel/code-frame",
   202  					Version:   "7.12.11",
   203  					PURLType:  purl.TypeNPM,
   204  					Locations: []string{"testdata/scoped-packages.v1.lock"},
   205  					SourceCode: &extractor.SourceCodeIdentifier{
   206  						Commit: "",
   207  					},
   208  				},
   209  				{
   210  					Name:      "@babel/compat-data",
   211  					Version:   "7.14.0",
   212  					PURLType:  purl.TypeNPM,
   213  					Locations: []string{"testdata/scoped-packages.v1.lock"},
   214  					SourceCode: &extractor.SourceCodeIdentifier{
   215  						Commit: "",
   216  					},
   217  				},
   218  			},
   219  		},
   220  		{
   221  			Name: "with prerelease",
   222  			InputConfig: extracttest.ScanInputMockConfig{
   223  				Path: "testdata/with-prerelease.v1.lock",
   224  			},
   225  			WantPackages: []*extractor.Package{
   226  				{
   227  					Name:      "css-tree",
   228  					Version:   "1.0.0-alpha.37",
   229  					PURLType:  purl.TypeNPM,
   230  					Locations: []string{"testdata/with-prerelease.v1.lock"},
   231  					SourceCode: &extractor.SourceCodeIdentifier{
   232  						Commit: "",
   233  					},
   234  				},
   235  				{
   236  					Name:      "gensync",
   237  					Version:   "1.0.0-beta.2",
   238  					PURLType:  purl.TypeNPM,
   239  					Locations: []string{"testdata/with-prerelease.v1.lock"},
   240  					SourceCode: &extractor.SourceCodeIdentifier{
   241  						Commit: "",
   242  					},
   243  				},
   244  				{
   245  					Name:      "node-fetch",
   246  					Version:   "3.0.0-beta.9",
   247  					PURLType:  purl.TypeNPM,
   248  					Locations: []string{"testdata/with-prerelease.v1.lock"},
   249  					SourceCode: &extractor.SourceCodeIdentifier{
   250  						Commit: "",
   251  					},
   252  				},
   253  				{
   254  					Name:      "resolve",
   255  					Version:   "1.20.0",
   256  					PURLType:  purl.TypeNPM,
   257  					Locations: []string{"testdata/with-prerelease.v1.lock"},
   258  					SourceCode: &extractor.SourceCodeIdentifier{
   259  						Commit: "",
   260  					},
   261  				},
   262  				{
   263  					Name:      "resolve",
   264  					Version:   "2.0.0-next.3",
   265  					PURLType:  purl.TypeNPM,
   266  					Locations: []string{"testdata/with-prerelease.v1.lock"},
   267  					SourceCode: &extractor.SourceCodeIdentifier{
   268  						Commit: "",
   269  					},
   270  				},
   271  			},
   272  		},
   273  		{
   274  			Name: "with build string",
   275  			InputConfig: extracttest.ScanInputMockConfig{
   276  				Path: "testdata/with-build-string.v1.lock",
   277  			},
   278  			WantPackages: []*extractor.Package{
   279  				{
   280  					Name:      "domino",
   281  					Version:   "2.1.6+git",
   282  					PURLType:  purl.TypeNPM,
   283  					Locations: []string{"testdata/with-build-string.v1.lock"},
   284  					SourceCode: &extractor.SourceCodeIdentifier{
   285  						Commit: "",
   286  					},
   287  				},
   288  				{
   289  					Name:      "tslib",
   290  					Version:   "2.6.2",
   291  					PURLType:  purl.TypeNPM,
   292  					Locations: []string{"testdata/with-build-string.v1.lock"},
   293  					SourceCode: &extractor.SourceCodeIdentifier{
   294  						Commit: "",
   295  					},
   296  				},
   297  			},
   298  		},
   299  		{
   300  			Name: "commits",
   301  			InputConfig: extracttest.ScanInputMockConfig{
   302  				Path: "testdata/commits.v1.lock",
   303  			},
   304  			WantPackages: []*extractor.Package{
   305  				{
   306  					Name:      "mine1",
   307  					Version:   "1.0.0-alpha.37",
   308  					PURLType:  purl.TypeNPM,
   309  					Locations: []string{"testdata/commits.v1.lock"},
   310  					SourceCode: &extractor.SourceCodeIdentifier{
   311  						Commit: "0a2d2506c1fe299691fc5db53a2097db3bd615bc",
   312  					},
   313  				},
   314  				{
   315  					Name:      "mine2",
   316  					Version:   "0.0.1",
   317  					PURLType:  purl.TypeNPM,
   318  					Locations: []string{"testdata/commits.v1.lock"},
   319  					SourceCode: &extractor.SourceCodeIdentifier{
   320  						Commit: "0a2d2506c1fe299691fc5db53a2097db3bd615bc",
   321  					},
   322  				},
   323  				{
   324  					Name:      "mine3",
   325  					Version:   "1.2.3",
   326  					PURLType:  purl.TypeNPM,
   327  					Locations: []string{"testdata/commits.v1.lock"},
   328  					SourceCode: &extractor.SourceCodeIdentifier{
   329  						Commit: "094e581aaf927d010e4b61d706ba584551dac502",
   330  					},
   331  				},
   332  				{
   333  					Name:      "mine4",
   334  					Version:   "0.0.2",
   335  					PURLType:  purl.TypeNPM,
   336  					Locations: []string{"testdata/commits.v1.lock"},
   337  					SourceCode: &extractor.SourceCodeIdentifier{
   338  						Commit: "aa3bdfcb1d845c79f14abb66f60d35b8a3ee5998",
   339  					},
   340  				},
   341  				{
   342  					Name:      "mine4",
   343  					Version:   "0.0.4",
   344  					PURLType:  purl.TypeNPM,
   345  					Locations: []string{"testdata/commits.v1.lock"},
   346  					SourceCode: &extractor.SourceCodeIdentifier{
   347  						Commit: "aa3bdfcb1d845c79f14abb66f60d35b8a3ee5998",
   348  					},
   349  				},
   350  				{
   351  					Name:      "my-package",
   352  					Version:   "1.8.3",
   353  					PURLType:  purl.TypeNPM,
   354  					Locations: []string{"testdata/commits.v1.lock"},
   355  					SourceCode: &extractor.SourceCodeIdentifier{
   356  						Commit: "b3bd3f1b3dad036e671251f5258beaae398f983a",
   357  					},
   358  				},
   359  				{
   360  					Name:      "@bower_components/angular-animate",
   361  					Version:   "1.4.14",
   362  					PURLType:  purl.TypeNPM,
   363  					Locations: []string{"testdata/commits.v1.lock"},
   364  					SourceCode: &extractor.SourceCodeIdentifier{
   365  						Commit: "e7f778fc054a086ba3326d898a00fa1bc78650a8",
   366  					},
   367  				},
   368  				{
   369  					Name:      "@bower_components/alertify",
   370  					Version:   "0.0.0",
   371  					PURLType:  purl.TypeNPM,
   372  					Locations: []string{"testdata/commits.v1.lock"},
   373  					SourceCode: &extractor.SourceCodeIdentifier{
   374  						Commit: "e7b6c46d76604d297c389d830817b611c9a8f17c",
   375  					},
   376  				},
   377  				{
   378  					Name:      "minimist",
   379  					Version:   "0.0.8",
   380  					PURLType:  purl.TypeNPM,
   381  					Locations: []string{"testdata/commits.v1.lock"},
   382  					SourceCode: &extractor.SourceCodeIdentifier{
   383  						Commit: "3754568bfd43a841d2d72d7fb54598635aea8fa4",
   384  					},
   385  				},
   386  				{
   387  					Name:      "bats-assert",
   388  					Version:   "2.0.0",
   389  					PURLType:  purl.TypeNPM,
   390  					Locations: []string{"testdata/commits.v1.lock"},
   391  					SourceCode: &extractor.SourceCodeIdentifier{
   392  						Commit: "4bdd58d3fbcdce3209033d44d884e87add1d8405",
   393  					},
   394  				},
   395  				{
   396  					Name:      "bats-support",
   397  					Version:   "0.3.0",
   398  					PURLType:  purl.TypeNPM,
   399  					Locations: []string{"testdata/commits.v1.lock"},
   400  					SourceCode: &extractor.SourceCodeIdentifier{
   401  						Commit: "d140a65044b2d6810381935ae7f0c94c7023c8c3",
   402  					},
   403  				},
   404  				{
   405  					Name:      "bats",
   406  					Version:   "1.5.0",
   407  					PURLType:  purl.TypeNPM,
   408  					Locations: []string{"testdata/commits.v1.lock"},
   409  					SourceCode: &extractor.SourceCodeIdentifier{
   410  						Commit: "172580d2ce19ee33780b5f1df817bbddced43789",
   411  					},
   412  				},
   413  				{
   414  					Name:      "vue",
   415  					Version:   "2.6.12",
   416  					PURLType:  purl.TypeNPM,
   417  					Locations: []string{"testdata/commits.v1.lock"},
   418  					SourceCode: &extractor.SourceCodeIdentifier{
   419  						Commit: "bb253db0b3e17124b6d1fe93fbf2db35470a1347",
   420  					},
   421  				},
   422  				{
   423  					Name:      "kit",
   424  					Version:   "1.0.0",
   425  					PURLType:  purl.TypeNPM,
   426  					Locations: []string{"testdata/commits.v1.lock"},
   427  					SourceCode: &extractor.SourceCodeIdentifier{
   428  						Commit: "5b6830c0252eb73c6024d40a8ff5106d3023a2a6",
   429  					},
   430  				},
   431  				{
   432  					Name:      "casadistance",
   433  					Version:   "1.0.0",
   434  					PURLType:  purl.TypeNPM,
   435  					Locations: []string{"testdata/commits.v1.lock"},
   436  					SourceCode: &extractor.SourceCodeIdentifier{
   437  						Commit: "f0308391f0c50104182bfb2332a53e4e523a4603",
   438  					},
   439  				},
   440  				{
   441  					Name:      "babel-preset-php",
   442  					Version:   "1.1.1",
   443  					PURLType:  purl.TypeNPM,
   444  					Locations: []string{"testdata/commits.v1.lock"},
   445  					SourceCode: &extractor.SourceCodeIdentifier{
   446  						Commit: "c5a7ba5e0ad98b8db1cb8ce105403dd4b768cced",
   447  					},
   448  				},
   449  				{
   450  					Name:      "is-number",
   451  					Version:   "2.0.0",
   452  					PURLType:  purl.TypeNPM,
   453  					Locations: []string{"testdata/commits.v1.lock"},
   454  					SourceCode: &extractor.SourceCodeIdentifier{
   455  						Commit: "d5ac0584ee9ae7bd9288220a39780f155b9ad4c8",
   456  					},
   457  				},
   458  				{
   459  					Name:      "is-number",
   460  					Version:   "5.0.0",
   461  					PURLType:  purl.TypeNPM,
   462  					Locations: []string{"testdata/commits.v1.lock"},
   463  					SourceCode: &extractor.SourceCodeIdentifier{
   464  						Commit: "af885e2e890b9ef0875edd2b117305119ee5bdc5",
   465  					},
   466  				},
   467  			},
   468  		},
   469  		{
   470  			Name: "files",
   471  			InputConfig: extracttest.ScanInputMockConfig{
   472  				Path: "testdata/files.v1.lock",
   473  			},
   474  			WantPackages: []*extractor.Package{
   475  				{
   476  					Name:      "etag",
   477  					Version:   "1.8.1",
   478  					PURLType:  purl.TypeNPM,
   479  					Locations: []string{"testdata/files.v1.lock"},
   480  					SourceCode: &extractor.SourceCodeIdentifier{
   481  						Commit: "",
   482  					},
   483  				},
   484  				{
   485  					Name:      "filedep",
   486  					Version:   "1.2.0",
   487  					PURLType:  purl.TypeNPM,
   488  					Locations: []string{"testdata/files.v1.lock"},
   489  					SourceCode: &extractor.SourceCodeIdentifier{
   490  						Commit: "",
   491  					},
   492  				},
   493  				{
   494  					Name:      "lodash",
   495  					Version:   "1.3.1",
   496  					PURLType:  purl.TypeNPM,
   497  					Locations: []string{"testdata/files.v1.lock"},
   498  					SourceCode: &extractor.SourceCodeIdentifier{
   499  						Commit: "",
   500  					},
   501  				},
   502  				{
   503  					Name:      "other_package",
   504  					Version:   "0.0.2",
   505  					PURLType:  purl.TypeNPM,
   506  					Locations: []string{"testdata/files.v1.lock"},
   507  					SourceCode: &extractor.SourceCodeIdentifier{
   508  						Commit: "",
   509  					},
   510  				},
   511  				{
   512  					Name:      "sprintf-js",
   513  					Version:   "0.0.0",
   514  					PURLType:  purl.TypeNPM,
   515  					Locations: []string{"testdata/files.v1.lock"},
   516  					SourceCode: &extractor.SourceCodeIdentifier{
   517  						Commit: "",
   518  					},
   519  				},
   520  				{
   521  					Name:      "etag",
   522  					Version:   "1.8.0",
   523  					PURLType:  purl.TypeNPM,
   524  					Locations: []string{"testdata/files.v1.lock"},
   525  					SourceCode: &extractor.SourceCodeIdentifier{
   526  						Commit: "",
   527  					},
   528  				},
   529  			},
   530  		},
   531  		{
   532  			Name: "with aliases",
   533  			InputConfig: extracttest.ScanInputMockConfig{
   534  				Path: "testdata/with-aliases.v1.lock",
   535  			},
   536  			WantPackages: []*extractor.Package{
   537  				{
   538  					Name:      "@babel/helper-validator-identifier",
   539  					Version:   "7.22.20",
   540  					PURLType:  purl.TypeNPM,
   541  					Locations: []string{"testdata/with-aliases.v1.lock"},
   542  					SourceCode: &extractor.SourceCodeIdentifier{
   543  						Commit: "",
   544  					},
   545  				},
   546  				{
   547  					Name:      "ansi-regex",
   548  					Version:   "6.0.1",
   549  					PURLType:  purl.TypeNPM,
   550  					Locations: []string{"testdata/with-aliases.v1.lock"},
   551  					SourceCode: &extractor.SourceCodeIdentifier{
   552  						Commit: "",
   553  					},
   554  				},
   555  				{
   556  					Name:      "ansi-regex",
   557  					Version:   "5.0.1",
   558  					PURLType:  purl.TypeNPM,
   559  					Locations: []string{"testdata/with-aliases.v1.lock"},
   560  					SourceCode: &extractor.SourceCodeIdentifier{
   561  						Commit: "",
   562  					},
   563  				},
   564  			},
   565  		},
   566  	}
   567  
   568  	for _, tt := range tests {
   569  		t.Run(tt.Name, func(t *testing.T) {
   570  			extr := yarnlock.Extractor{}
   571  
   572  			scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig)
   573  			defer extracttest.CloseTestScanInput(t, scanInput)
   574  
   575  			got, err := extr.Extract(t.Context(), &scanInput)
   576  
   577  			if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" {
   578  				t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff)
   579  				return
   580  			}
   581  
   582  			wantInv := inventory.Inventory{Packages: tt.WantPackages}
   583  			if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" {
   584  				t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff)
   585  			}
   586  		})
   587  	}
   588  }