github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/javascript/bunlock/bunlock_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 bunlock_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/bunlock"
    24  	"github.com/google/osv-scalibr/extractor/filesystem/osv"
    25  	"github.com/google/osv-scalibr/extractor/filesystem/simplefileapi"
    26  	"github.com/google/osv-scalibr/inventory"
    27  	"github.com/google/osv-scalibr/purl"
    28  	"github.com/google/osv-scalibr/testing/extracttest"
    29  )
    30  
    31  func TestExtractor_FileRequired(t *testing.T) {
    32  	tests := []struct {
    33  		name      string
    34  		inputPath string
    35  		want      bool
    36  	}{
    37  		{
    38  			name:      "",
    39  			inputPath: "",
    40  			want:      false,
    41  		},
    42  		{
    43  			name:      "",
    44  			inputPath: "bun.lock",
    45  			want:      true,
    46  		},
    47  		{
    48  			name:      "",
    49  			inputPath: "path/to/my/bun.lock",
    50  			want:      true,
    51  		},
    52  		{
    53  			name:      "",
    54  			inputPath: "path/to/my/bun.lock/file",
    55  			want:      false,
    56  		},
    57  		{
    58  			name:      "",
    59  			inputPath: "path/to/my/bun.lock.file",
    60  			want:      false,
    61  		},
    62  		{
    63  			name:      "",
    64  			inputPath: "path.to.my.bun.lock",
    65  			want:      false,
    66  		},
    67  		{
    68  			name:      "",
    69  			inputPath: "foo/node_modules/bar/bun.lock",
    70  			want:      false,
    71  		},
    72  	}
    73  	for _, tt := range tests {
    74  		t.Run(tt.name, func(t *testing.T) {
    75  			e := bunlock.Extractor{}
    76  			got := e.FileRequired(simplefileapi.New(tt.inputPath, nil))
    77  			if got != tt.want {
    78  				t.Errorf("FileRequired(%s, FileInfo) got = %v, want %v", tt.inputPath, got, tt.want)
    79  			}
    80  		})
    81  	}
    82  }
    83  
    84  func TestExtractor_Extract(t *testing.T) {
    85  	tests := []extracttest.TestTableEntry{
    86  		{
    87  			Name: "invalid json",
    88  			InputConfig: extracttest.ScanInputMockConfig{
    89  				Path: "testdata/not-json.txt",
    90  			},
    91  			WantErr:      extracttest.ContainsErrStr{Str: "could not extract"},
    92  			WantPackages: nil,
    93  		},
    94  		{
    95  			Name: "empty",
    96  			InputConfig: extracttest.ScanInputMockConfig{
    97  				Path: "testdata/empty.json5",
    98  			},
    99  			WantPackages: []*extractor.Package{},
   100  		},
   101  		{
   102  			Name: "no packages",
   103  			InputConfig: extracttest.ScanInputMockConfig{
   104  				Path: "testdata/no-packages.json5",
   105  			},
   106  			WantPackages: []*extractor.Package{},
   107  		},
   108  		{
   109  			Name: "one package",
   110  			InputConfig: extracttest.ScanInputMockConfig{
   111  				Path: "testdata/one-package.json5",
   112  			},
   113  			WantPackages: []*extractor.Package{
   114  				{
   115  					Name:       "wrappy",
   116  					Version:    "1.0.2",
   117  					PURLType:   purl.TypeNPM,
   118  					Locations:  []string{"testdata/one-package.json5"},
   119  					SourceCode: &extractor.SourceCodeIdentifier{},
   120  					Metadata: osv.DepGroupMetadata{
   121  						DepGroupVals: []string{},
   122  					},
   123  				},
   124  			},
   125  		},
   126  		{
   127  			Name: "one package dev",
   128  			InputConfig: extracttest.ScanInputMockConfig{
   129  				Path: "testdata/one-package-dev.json5",
   130  			},
   131  			WantPackages: []*extractor.Package{
   132  				{
   133  					Name:       "wrappy",
   134  					Version:    "1.0.2",
   135  					PURLType:   purl.TypeNPM,
   136  					Locations:  []string{"testdata/one-package-dev.json5"},
   137  					SourceCode: &extractor.SourceCodeIdentifier{},
   138  					Metadata: osv.DepGroupMetadata{
   139  						DepGroupVals: []string{},
   140  					},
   141  				},
   142  			},
   143  		},
   144  		{
   145  			Name: "one package with bad tuple (first error)",
   146  			InputConfig: extracttest.ScanInputMockConfig{
   147  				Path: "testdata/bad-tuple.json5",
   148  			},
   149  			WantErr: extracttest.ContainsErrStr{Str: "could not extract 'wrappy-bad1'"},
   150  			WantPackages: []*extractor.Package{
   151  				{
   152  					Name:       "wrappy",
   153  					Version:    "1.0.2",
   154  					PURLType:   purl.TypeNPM,
   155  					Locations:  []string{"testdata/bad-tuple.json5"},
   156  					SourceCode: &extractor.SourceCodeIdentifier{},
   157  					Metadata: osv.DepGroupMetadata{
   158  						DepGroupVals: []string{},
   159  					},
   160  				},
   161  			},
   162  		},
   163  		{
   164  			Name: "one package with bad tuple (second error)",
   165  			InputConfig: extracttest.ScanInputMockConfig{
   166  				Path: "testdata/bad-tuple.json5",
   167  			},
   168  			WantErr: extracttest.ContainsErrStr{Str: "could not extract 'wrappy-bad2'"},
   169  			WantPackages: []*extractor.Package{
   170  				{
   171  					Name:       "wrappy",
   172  					Version:    "1.0.2",
   173  					PURLType:   purl.TypeNPM,
   174  					Locations:  []string{"testdata/bad-tuple.json5"},
   175  					SourceCode: &extractor.SourceCodeIdentifier{},
   176  					Metadata: osv.DepGroupMetadata{
   177  						DepGroupVals: []string{},
   178  					},
   179  				},
   180  			},
   181  		},
   182  		{
   183  			Name: "two packages",
   184  			InputConfig: extracttest.ScanInputMockConfig{
   185  				Path: "testdata/two-packages.json5",
   186  			},
   187  			WantPackages: []*extractor.Package{
   188  				{
   189  					Name:       "has-flag",
   190  					Version:    "4.0.0",
   191  					PURLType:   purl.TypeNPM,
   192  					Locations:  []string{"testdata/two-packages.json5"},
   193  					SourceCode: &extractor.SourceCodeIdentifier{},
   194  					Metadata: osv.DepGroupMetadata{
   195  						DepGroupVals: []string{},
   196  					},
   197  				},
   198  				{
   199  					Name:       "wrappy",
   200  					Version:    "1.0.2",
   201  					PURLType:   purl.TypeNPM,
   202  					Locations:  []string{"testdata/two-packages.json5"},
   203  					SourceCode: &extractor.SourceCodeIdentifier{},
   204  					Metadata: osv.DepGroupMetadata{
   205  						DepGroupVals: []string{},
   206  					},
   207  				},
   208  			},
   209  		},
   210  		{
   211  			Name: "same package in different groups",
   212  			InputConfig: extracttest.ScanInputMockConfig{
   213  				Path: "testdata/same-package-different-groups.json5",
   214  			},
   215  			WantPackages: []*extractor.Package{
   216  				{
   217  					Name:       "has-flag",
   218  					Version:    "3.0.0",
   219  					PURLType:   purl.TypeNPM,
   220  					Locations:  []string{"testdata/same-package-different-groups.json5"},
   221  					SourceCode: &extractor.SourceCodeIdentifier{},
   222  					Metadata: osv.DepGroupMetadata{
   223  						DepGroupVals: []string{},
   224  					},
   225  				},
   226  				{
   227  					Name:       "supports-color",
   228  					Version:    "5.5.0",
   229  					PURLType:   purl.TypeNPM,
   230  					Locations:  []string{"testdata/same-package-different-groups.json5"},
   231  					SourceCode: &extractor.SourceCodeIdentifier{},
   232  					Metadata: osv.DepGroupMetadata{
   233  						DepGroupVals: []string{},
   234  					},
   235  				},
   236  			},
   237  		},
   238  		{
   239  			Name: "scoped packages",
   240  			InputConfig: extracttest.ScanInputMockConfig{
   241  				Path: "testdata/scoped-packages.json5",
   242  			},
   243  			WantPackages: []*extractor.Package{
   244  				{
   245  					Name:       "@typescript-eslint/types",
   246  					Version:    "5.62.0",
   247  					PURLType:   purl.TypeNPM,
   248  					Locations:  []string{"testdata/scoped-packages.json5"},
   249  					SourceCode: &extractor.SourceCodeIdentifier{},
   250  					Metadata: osv.DepGroupMetadata{
   251  						DepGroupVals: []string{},
   252  					},
   253  				},
   254  			},
   255  		},
   256  		{
   257  			Name: "scoped packages mixed",
   258  			InputConfig: extracttest.ScanInputMockConfig{
   259  				Path: "testdata/scoped-packages-mixed.json5",
   260  			},
   261  			WantPackages: []*extractor.Package{
   262  				{
   263  					Name:       "@babel/code-frame",
   264  					Version:    "7.26.2",
   265  					PURLType:   purl.TypeNPM,
   266  					Locations:  []string{"testdata/scoped-packages-mixed.json5"},
   267  					SourceCode: &extractor.SourceCodeIdentifier{},
   268  					Metadata: osv.DepGroupMetadata{
   269  						DepGroupVals: []string{},
   270  					},
   271  				},
   272  				{
   273  					Name:       "@babel/helper-validator-identifier",
   274  					Version:    "7.25.9",
   275  					PURLType:   purl.TypeNPM,
   276  					Locations:  []string{"testdata/scoped-packages-mixed.json5"},
   277  					SourceCode: &extractor.SourceCodeIdentifier{},
   278  					Metadata: osv.DepGroupMetadata{
   279  						DepGroupVals: []string{},
   280  					},
   281  				},
   282  				{
   283  					Name:       "js-tokens",
   284  					Version:    "4.0.0",
   285  					PURLType:   purl.TypeNPM,
   286  					Locations:  []string{"testdata/scoped-packages-mixed.json5"},
   287  					SourceCode: &extractor.SourceCodeIdentifier{},
   288  					Metadata: osv.DepGroupMetadata{
   289  						DepGroupVals: []string{},
   290  					},
   291  				},
   292  				{
   293  					Name:       "picocolors",
   294  					Version:    "1.1.1",
   295  					PURLType:   purl.TypeNPM,
   296  					Locations:  []string{"testdata/scoped-packages-mixed.json5"},
   297  					SourceCode: &extractor.SourceCodeIdentifier{},
   298  					Metadata: osv.DepGroupMetadata{
   299  						DepGroupVals: []string{},
   300  					},
   301  				},
   302  				{
   303  					Name:       "wrappy",
   304  					Version:    "1.0.2",
   305  					PURLType:   purl.TypeNPM,
   306  					Locations:  []string{"testdata/scoped-packages-mixed.json5"},
   307  					SourceCode: &extractor.SourceCodeIdentifier{},
   308  					Metadata: osv.DepGroupMetadata{
   309  						DepGroupVals: []string{},
   310  					},
   311  				},
   312  			},
   313  		},
   314  		{
   315  			Name: "optional package",
   316  			InputConfig: extracttest.ScanInputMockConfig{
   317  				Path: "testdata/optional-package.json5",
   318  			},
   319  			WantPackages: []*extractor.Package{
   320  				{
   321  					Name:       "acorn",
   322  					Version:    "8.14.0",
   323  					PURLType:   purl.TypeNPM,
   324  					Locations:  []string{"testdata/optional-package.json5"},
   325  					SourceCode: &extractor.SourceCodeIdentifier{},
   326  					Metadata: osv.DepGroupMetadata{
   327  						DepGroupVals: []string{},
   328  					},
   329  				},
   330  				{
   331  					Name:       "fsevents",
   332  					Version:    "0.3.8",
   333  					PURLType:   purl.TypeNPM,
   334  					Locations:  []string{"testdata/optional-package.json5"},
   335  					SourceCode: &extractor.SourceCodeIdentifier{},
   336  					Metadata: osv.DepGroupMetadata{
   337  						DepGroupVals: []string{},
   338  					},
   339  				},
   340  				{
   341  					Name:       "nan",
   342  					Version:    "2.22.0",
   343  					PURLType:   purl.TypeNPM,
   344  					Locations:  []string{"testdata/optional-package.json5"},
   345  					SourceCode: &extractor.SourceCodeIdentifier{},
   346  					Metadata: osv.DepGroupMetadata{
   347  						DepGroupVals: []string{},
   348  					},
   349  				},
   350  			},
   351  		},
   352  		{
   353  			Name: "peer dependencies implicit",
   354  			InputConfig: extracttest.ScanInputMockConfig{
   355  				Path: "testdata/peer-dependencies-implicit.json5",
   356  			},
   357  			WantPackages: []*extractor.Package{
   358  				{
   359  					Name:       "acorn-jsx",
   360  					Version:    "5.3.2",
   361  					PURLType:   purl.TypeNPM,
   362  					Locations:  []string{"testdata/peer-dependencies-implicit.json5"},
   363  					SourceCode: &extractor.SourceCodeIdentifier{},
   364  					Metadata: osv.DepGroupMetadata{
   365  						DepGroupVals: []string{},
   366  					},
   367  				},
   368  				{
   369  					Name:       "acorn",
   370  					Version:    "8.14.0",
   371  					PURLType:   purl.TypeNPM,
   372  					Locations:  []string{"testdata/peer-dependencies-implicit.json5"},
   373  					SourceCode: &extractor.SourceCodeIdentifier{},
   374  					Metadata: osv.DepGroupMetadata{
   375  						DepGroupVals: []string{},
   376  					},
   377  				},
   378  			},
   379  		},
   380  		{
   381  			Name: "peer dependencies explicit",
   382  			InputConfig: extracttest.ScanInputMockConfig{
   383  				Path: "testdata/peer-dependencies-explicit.json5",
   384  			},
   385  			WantPackages: []*extractor.Package{
   386  				{
   387  					Name:       "acorn-jsx",
   388  					Version:    "5.3.2",
   389  					PURLType:   purl.TypeNPM,
   390  					Locations:  []string{"testdata/peer-dependencies-explicit.json5"},
   391  					SourceCode: &extractor.SourceCodeIdentifier{},
   392  					Metadata: osv.DepGroupMetadata{
   393  						DepGroupVals: []string{},
   394  					},
   395  				},
   396  				{
   397  					Name:       "acorn",
   398  					Version:    "8.14.0",
   399  					PURLType:   purl.TypeNPM,
   400  					Locations:  []string{"testdata/peer-dependencies-explicit.json5"},
   401  					SourceCode: &extractor.SourceCodeIdentifier{},
   402  					Metadata: osv.DepGroupMetadata{
   403  						DepGroupVals: []string{},
   404  					},
   405  				},
   406  			},
   407  		},
   408  		{
   409  			Name: "nested dependencies",
   410  			InputConfig: extracttest.ScanInputMockConfig{
   411  				Path: "testdata/nested-dependencies.json5",
   412  			},
   413  			WantPackages: []*extractor.Package{
   414  				{
   415  					Name:       "ansi-styles",
   416  					Version:    "4.3.0",
   417  					PURLType:   purl.TypeNPM,
   418  					Locations:  []string{"testdata/nested-dependencies.json5"},
   419  					SourceCode: &extractor.SourceCodeIdentifier{},
   420  					Metadata: osv.DepGroupMetadata{
   421  						DepGroupVals: []string{},
   422  					},
   423  				},
   424  				{
   425  					Name:       "chalk",
   426  					Version:    "4.1.2",
   427  					PURLType:   purl.TypeNPM,
   428  					Locations:  []string{"testdata/nested-dependencies.json5"},
   429  					SourceCode: &extractor.SourceCodeIdentifier{},
   430  					Metadata: osv.DepGroupMetadata{
   431  						DepGroupVals: []string{},
   432  					},
   433  				},
   434  				{
   435  					Name:       "color-convert",
   436  					Version:    "2.0.1",
   437  					PURLType:   purl.TypeNPM,
   438  					Locations:  []string{"testdata/nested-dependencies.json5"},
   439  					SourceCode: &extractor.SourceCodeIdentifier{},
   440  					Metadata: osv.DepGroupMetadata{
   441  						DepGroupVals: []string{},
   442  					},
   443  				},
   444  				{
   445  					Name:       "color-name",
   446  					Version:    "1.1.4",
   447  					PURLType:   purl.TypeNPM,
   448  					Locations:  []string{"testdata/nested-dependencies.json5"},
   449  					SourceCode: &extractor.SourceCodeIdentifier{},
   450  					Metadata: osv.DepGroupMetadata{
   451  						DepGroupVals: []string{},
   452  					},
   453  				},
   454  				{
   455  					Name:       "has-flag",
   456  					Version:    "2.0.0",
   457  					PURLType:   purl.TypeNPM,
   458  					Locations:  []string{"testdata/nested-dependencies.json5"},
   459  					SourceCode: &extractor.SourceCodeIdentifier{},
   460  					Metadata: osv.DepGroupMetadata{
   461  						DepGroupVals: []string{},
   462  					},
   463  				},
   464  				{
   465  					Name:       "supports-color",
   466  					Version:    "5.5.0",
   467  					PURLType:   purl.TypeNPM,
   468  					Locations:  []string{"testdata/nested-dependencies.json5"},
   469  					SourceCode: &extractor.SourceCodeIdentifier{},
   470  					Metadata: osv.DepGroupMetadata{
   471  						DepGroupVals: []string{},
   472  					},
   473  				},
   474  				{
   475  					Name:       "supports-color",
   476  					Version:    "7.2.0",
   477  					PURLType:   purl.TypeNPM,
   478  					Locations:  []string{"testdata/nested-dependencies.json5"},
   479  					SourceCode: &extractor.SourceCodeIdentifier{},
   480  					Metadata: osv.DepGroupMetadata{
   481  						DepGroupVals: []string{},
   482  					},
   483  				},
   484  				{
   485  					Name:       "has-flag",
   486  					Version:    "3.0.0",
   487  					PURLType:   purl.TypeNPM,
   488  					Locations:  []string{"testdata/nested-dependencies.json5"},
   489  					SourceCode: &extractor.SourceCodeIdentifier{},
   490  					Metadata: osv.DepGroupMetadata{
   491  						DepGroupVals: []string{},
   492  					},
   493  				},
   494  				{
   495  					Name:       "has-flag",
   496  					Version:    "4.0.0",
   497  					PURLType:   purl.TypeNPM,
   498  					Locations:  []string{"testdata/nested-dependencies.json5"},
   499  					SourceCode: &extractor.SourceCodeIdentifier{},
   500  					Metadata: osv.DepGroupMetadata{
   501  						DepGroupVals: []string{},
   502  					},
   503  				},
   504  			},
   505  		},
   506  		{
   507  			Name: "nested dependencies with duplicate versions",
   508  			InputConfig: extracttest.ScanInputMockConfig{
   509  				Path: "testdata/nested-dependencies-dup.json5",
   510  			},
   511  			WantPackages: []*extractor.Package{
   512  				{
   513  					Name:       "ansi-styles",
   514  					Version:    "4.3.0",
   515  					PURLType:   purl.TypeNPM,
   516  					Locations:  []string{"testdata/nested-dependencies-dup.json5"},
   517  					SourceCode: &extractor.SourceCodeIdentifier{},
   518  					Metadata: osv.DepGroupMetadata{
   519  						DepGroupVals: []string{},
   520  					},
   521  				},
   522  				{
   523  					Name:       "chalk",
   524  					Version:    "4.1.2",
   525  					PURLType:   purl.TypeNPM,
   526  					Locations:  []string{"testdata/nested-dependencies-dup.json5"},
   527  					SourceCode: &extractor.SourceCodeIdentifier{},
   528  					Metadata: osv.DepGroupMetadata{
   529  						DepGroupVals: []string{},
   530  					},
   531  				},
   532  				{
   533  					Name:       "color-convert",
   534  					Version:    "2.0.1",
   535  					PURLType:   purl.TypeNPM,
   536  					Locations:  []string{"testdata/nested-dependencies-dup.json5"},
   537  					SourceCode: &extractor.SourceCodeIdentifier{},
   538  					Metadata: osv.DepGroupMetadata{
   539  						DepGroupVals: []string{},
   540  					},
   541  				},
   542  				{
   543  					Name:       "color-name",
   544  					Version:    "1.1.4",
   545  					PURLType:   purl.TypeNPM,
   546  					Locations:  []string{"testdata/nested-dependencies-dup.json5"},
   547  					SourceCode: &extractor.SourceCodeIdentifier{},
   548  					Metadata: osv.DepGroupMetadata{
   549  						DepGroupVals: []string{},
   550  					},
   551  				},
   552  				{
   553  					Name:       "has-flag",
   554  					Version:    "2.0.0",
   555  					PURLType:   purl.TypeNPM,
   556  					Locations:  []string{"testdata/nested-dependencies-dup.json5"},
   557  					SourceCode: &extractor.SourceCodeIdentifier{},
   558  					Metadata: osv.DepGroupMetadata{
   559  						DepGroupVals: []string{},
   560  					},
   561  				},
   562  				{
   563  					Name:       "supports-color",
   564  					Version:    "7.2.0",
   565  					PURLType:   purl.TypeNPM,
   566  					Locations:  []string{"testdata/nested-dependencies-dup.json5"},
   567  					SourceCode: &extractor.SourceCodeIdentifier{},
   568  					Metadata: osv.DepGroupMetadata{
   569  						DepGroupVals: []string{},
   570  					},
   571  				},
   572  				{
   573  					Name:       "has-flag",
   574  					Version:    "4.0.0",
   575  					PURLType:   purl.TypeNPM,
   576  					Locations:  []string{"testdata/nested-dependencies-dup.json5"},
   577  					SourceCode: &extractor.SourceCodeIdentifier{},
   578  					Metadata: osv.DepGroupMetadata{
   579  						DepGroupVals: []string{},
   580  					},
   581  				},
   582  			},
   583  		},
   584  		{
   585  			Name: "alias",
   586  			InputConfig: extracttest.ScanInputMockConfig{
   587  				Path: "testdata/alias.json5",
   588  			},
   589  			WantPackages: []*extractor.Package{
   590  				{
   591  					Name:       "has-flag",
   592  					Version:    "4.0.0",
   593  					PURLType:   purl.TypeNPM,
   594  					Locations:  []string{"testdata/alias.json5"},
   595  					SourceCode: &extractor.SourceCodeIdentifier{},
   596  					Metadata: osv.DepGroupMetadata{
   597  						DepGroupVals: []string{},
   598  					},
   599  				},
   600  				{
   601  					Name:       "supports-color",
   602  					Version:    "7.2.0",
   603  					PURLType:   purl.TypeNPM,
   604  					Locations:  []string{"testdata/alias.json5"},
   605  					SourceCode: &extractor.SourceCodeIdentifier{},
   606  					Metadata: osv.DepGroupMetadata{
   607  						DepGroupVals: []string{},
   608  					},
   609  				},
   610  				{
   611  					Name:       "supports-color",
   612  					Version:    "6.1.0",
   613  					PURLType:   purl.TypeNPM,
   614  					Locations:  []string{"testdata/alias.json5"},
   615  					SourceCode: &extractor.SourceCodeIdentifier{},
   616  					Metadata: osv.DepGroupMetadata{
   617  						DepGroupVals: []string{},
   618  					},
   619  				},
   620  				{
   621  					Name:       "has-flag",
   622  					Version:    "3.0.0",
   623  					PURLType:   purl.TypeNPM,
   624  					Locations:  []string{"testdata/alias.json5"},
   625  					SourceCode: &extractor.SourceCodeIdentifier{},
   626  					Metadata: osv.DepGroupMetadata{
   627  						DepGroupVals: []string{},
   628  					},
   629  				},
   630  			},
   631  		},
   632  		{
   633  			Name: "commits",
   634  			InputConfig: extracttest.ScanInputMockConfig{
   635  				Path: "testdata/commits.json5",
   636  			},
   637  			WantPackages: []*extractor.Package{
   638  				{
   639  					Name:       "@babel/helper-plugin-utils",
   640  					Version:    "7.26.5",
   641  					PURLType:   purl.TypeNPM,
   642  					Locations:  []string{"testdata/commits.json5"},
   643  					SourceCode: &extractor.SourceCodeIdentifier{},
   644  					Metadata: osv.DepGroupMetadata{
   645  						DepGroupVals: []string{},
   646  					},
   647  				},
   648  				{
   649  					Name:       "@babel/helper-string-parser",
   650  					Version:    "7.25.9",
   651  					PURLType:   purl.TypeNPM,
   652  					Locations:  []string{"testdata/commits.json5"},
   653  					SourceCode: &extractor.SourceCodeIdentifier{},
   654  					Metadata: osv.DepGroupMetadata{
   655  						DepGroupVals: []string{},
   656  					},
   657  				},
   658  				{
   659  					Name:       "@babel/helper-validator-identifier",
   660  					Version:    "7.25.9",
   661  					PURLType:   purl.TypeNPM,
   662  					Locations:  []string{"testdata/commits.json5"},
   663  					SourceCode: &extractor.SourceCodeIdentifier{},
   664  					Metadata: osv.DepGroupMetadata{
   665  						DepGroupVals: []string{},
   666  					},
   667  				},
   668  				{
   669  					Name:       "@babel/parser",
   670  					Version:    "7.26.5",
   671  					PURLType:   purl.TypeNPM,
   672  					Locations:  []string{"testdata/commits.json5"},
   673  					SourceCode: &extractor.SourceCodeIdentifier{},
   674  					Metadata: osv.DepGroupMetadata{
   675  						DepGroupVals: []string{},
   676  					},
   677  				},
   678  				{
   679  					Name:       "@babel/types",
   680  					Version:    "7.26.5",
   681  					PURLType:   purl.TypeNPM,
   682  					Locations:  []string{"testdata/commits.json5"},
   683  					SourceCode: &extractor.SourceCodeIdentifier{},
   684  					Metadata: osv.DepGroupMetadata{
   685  						DepGroupVals: []string{},
   686  					},
   687  				},
   688  				{
   689  					Name:      "@prettier/sync",
   690  					Version:   "",
   691  					PURLType:  purl.TypeNPM,
   692  					Locations: []string{"testdata/commits.json5"},
   693  					SourceCode: &extractor.SourceCodeIdentifier{
   694  						Commit: "527e8ce",
   695  					},
   696  					Metadata: osv.DepGroupMetadata{
   697  						DepGroupVals: []string{},
   698  					},
   699  				},
   700  				{
   701  					Name:      "babel-preset-php",
   702  					Version:   "",
   703  					PURLType:  purl.TypeNPM,
   704  					Locations: []string{"testdata/commits.json5"},
   705  					SourceCode: &extractor.SourceCodeIdentifier{
   706  						Commit: "1ae6dc1267500360b411ec711b8aeac8c68b2246",
   707  					},
   708  					Metadata: osv.DepGroupMetadata{
   709  						DepGroupVals: []string{},
   710  					},
   711  				},
   712  				{
   713  					Name:      "is-number",
   714  					Version:   "",
   715  					PURLType:  purl.TypeNPM,
   716  					Locations: []string{"testdata/commits.json5"},
   717  					SourceCode: &extractor.SourceCodeIdentifier{
   718  						Commit: "98e8ff1",
   719  					},
   720  					Metadata: osv.DepGroupMetadata{
   721  						DepGroupVals: []string{},
   722  					},
   723  				},
   724  				{
   725  					Name:      "is-number",
   726  					Version:   "",
   727  					PURLType:  purl.TypeNPM,
   728  					Locations: []string{"testdata/commits.json5"},
   729  					SourceCode: &extractor.SourceCodeIdentifier{
   730  						Commit: "d5ac058",
   731  					},
   732  					Metadata: osv.DepGroupMetadata{
   733  						DepGroupVals: []string{},
   734  					},
   735  				},
   736  				{
   737  					Name:      "is-number",
   738  					Version:   "",
   739  					PURLType:  purl.TypeNPM,
   740  					Locations: []string{"testdata/commits.json5"},
   741  					SourceCode: &extractor.SourceCodeIdentifier{
   742  						Commit: "b7aef34",
   743  					},
   744  					Metadata: osv.DepGroupMetadata{
   745  						DepGroupVals: []string{},
   746  					},
   747  				},
   748  				{
   749  					Name:       "jquery",
   750  					Version:    "3.7.1",
   751  					PURLType:   purl.TypeNPM,
   752  					Locations:  []string{"testdata/commits.json5"},
   753  					SourceCode: &extractor.SourceCodeIdentifier{},
   754  					Metadata: osv.DepGroupMetadata{
   755  						DepGroupVals: []string{},
   756  					},
   757  				},
   758  				{
   759  					Name:       "lodash",
   760  					Version:    "1.3.1",
   761  					PURLType:   purl.TypeNPM,
   762  					Locations:  []string{"testdata/commits.json5"},
   763  					SourceCode: &extractor.SourceCodeIdentifier{},
   764  					Metadata: osv.DepGroupMetadata{
   765  						DepGroupVals: []string{},
   766  					},
   767  				},
   768  				{
   769  					Name:       "make-synchronized",
   770  					Version:    "0.2.9",
   771  					PURLType:   purl.TypeNPM,
   772  					Locations:  []string{"testdata/commits.json5"},
   773  					SourceCode: &extractor.SourceCodeIdentifier{},
   774  					Metadata: osv.DepGroupMetadata{
   775  						DepGroupVals: []string{},
   776  					},
   777  				},
   778  				{
   779  					Name:       "php-parser",
   780  					Version:    "2.2.0",
   781  					PURLType:   purl.TypeNPM,
   782  					Locations:  []string{"testdata/commits.json5"},
   783  					SourceCode: &extractor.SourceCodeIdentifier{},
   784  					Metadata: osv.DepGroupMetadata{
   785  						DepGroupVals: []string{},
   786  					},
   787  				},
   788  				{
   789  					Name:       "prettier",
   790  					Version:    "3.4.2",
   791  					PURLType:   purl.TypeNPM,
   792  					Locations:  []string{"testdata/commits.json5"},
   793  					SourceCode: &extractor.SourceCodeIdentifier{},
   794  					Metadata: osv.DepGroupMetadata{
   795  						DepGroupVals: []string{},
   796  					},
   797  				},
   798  				{
   799  					Name:      "raven-js",
   800  					Version:   "",
   801  					PURLType:  purl.TypeNPM,
   802  					Locations: []string{"testdata/commits.json5"},
   803  					SourceCode: &extractor.SourceCodeIdentifier{
   804  						Commit: "91ef2d4",
   805  					},
   806  					Metadata: osv.DepGroupMetadata{
   807  						DepGroupVals: []string{},
   808  					},
   809  				},
   810  				{
   811  					Name:      "slick-carousel",
   812  					Version:   "",
   813  					PURLType:  purl.TypeNPM,
   814  					Locations: []string{"testdata/commits.json5"},
   815  					SourceCode: &extractor.SourceCodeIdentifier{
   816  						Commit: "fc6f7d8",
   817  					},
   818  					Metadata: osv.DepGroupMetadata{
   819  						DepGroupVals: []string{},
   820  					},
   821  				},
   822  				{
   823  					Name:       "stopwords",
   824  					Version:    "0.0.1",
   825  					PURLType:   purl.TypeNPM,
   826  					Locations:  []string{"testdata/commits.json5"},
   827  					SourceCode: &extractor.SourceCodeIdentifier{},
   828  					Metadata: osv.DepGroupMetadata{
   829  						DepGroupVals: []string{},
   830  					},
   831  				},
   832  			},
   833  		},
   834  		{
   835  			Name: "files",
   836  			InputConfig: extracttest.ScanInputMockConfig{
   837  				Path: "testdata/files.json5",
   838  			},
   839  			WantPackages: []*extractor.Package{
   840  				{
   841  					Name:       "etag",
   842  					Version:    "",
   843  					PURLType:   purl.TypeNPM,
   844  					Locations:  []string{"testdata/files.json5"},
   845  					SourceCode: &extractor.SourceCodeIdentifier{},
   846  					Metadata: osv.DepGroupMetadata{
   847  						DepGroupVals: []string{},
   848  					},
   849  				},
   850  				{
   851  					Name:       "lodash",
   852  					Version:    "1.3.1",
   853  					PURLType:   purl.TypeNPM,
   854  					Locations:  []string{"testdata/files.json5"},
   855  					SourceCode: &extractor.SourceCodeIdentifier{},
   856  					Metadata: osv.DepGroupMetadata{
   857  						DepGroupVals: []string{},
   858  					},
   859  				},
   860  			},
   861  		},
   862  		{
   863  			Name: "sample from blog post",
   864  			InputConfig: extracttest.ScanInputMockConfig{
   865  				Path: "testdata/blog-sample.json5",
   866  			},
   867  			WantPackages: []*extractor.Package{
   868  				{
   869  					Name:      "uWebSockets.js",
   870  					Version:   "",
   871  					PURLType:  purl.TypeNPM,
   872  					Locations: []string{"testdata/blog-sample.json5"},
   873  					SourceCode: &extractor.SourceCodeIdentifier{
   874  						Commit: "6609a88",
   875  					},
   876  					Metadata: osv.DepGroupMetadata{
   877  						DepGroupVals: []string{},
   878  					},
   879  				},
   880  			},
   881  		},
   882  	}
   883  
   884  	for _, tt := range tests {
   885  		t.Run(tt.Name, func(t *testing.T) {
   886  			extr := bunlock.Extractor{}
   887  
   888  			scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig)
   889  			defer extracttest.CloseTestScanInput(t, scanInput)
   890  
   891  			got, err := extr.Extract(t.Context(), &scanInput)
   892  
   893  			if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" {
   894  				t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff)
   895  				return
   896  			}
   897  
   898  			wantInv := inventory.Inventory{Packages: tt.WantPackages}
   899  			if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" {
   900  				t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff)
   901  			}
   902  		})
   903  	}
   904  }