github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/javascript/parse_yarn_lock_test.go (about)

     1  package javascript
     2  
     3  import (
     4  	"io"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"os"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  
    12  	"github.com/anchore/syft/syft/artifact"
    13  	"github.com/anchore/syft/syft/file"
    14  	"github.com/anchore/syft/syft/pkg"
    15  	"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    16  )
    17  
    18  func TestParseYarnBerry(t *testing.T) {
    19  	var expectedRelationships []artifact.Relationship
    20  	fixture := "test-fixtures/yarn-berry/yarn.lock"
    21  	locations := file.NewLocationSet(file.NewLocation(fixture))
    22  
    23  	expectedPkgs := []pkg.Package{
    24  		{
    25  			Name:      "@babel/code-frame",
    26  			Version:   "7.10.4",
    27  			Locations: locations,
    28  			PURL:      "pkg:npm/%40babel/code-frame@7.10.4",
    29  			Language:  pkg.JavaScript,
    30  			Type:      pkg.NpmPkg,
    31  			Metadata:  pkg.YarnLockEntry{},
    32  		},
    33  		{
    34  			Name:      "@types/minimatch",
    35  			Version:   "3.0.3",
    36  			Locations: locations,
    37  			PURL:      "pkg:npm/%40types/minimatch@3.0.3",
    38  			Language:  pkg.JavaScript,
    39  			Type:      pkg.NpmPkg,
    40  			Metadata:  pkg.YarnLockEntry{},
    41  		},
    42  		{
    43  			Name:      "@types/qs",
    44  			Version:   "6.9.4",
    45  			Locations: locations,
    46  			PURL:      "pkg:npm/%40types/qs@6.9.4",
    47  			Language:  pkg.JavaScript,
    48  			Type:      pkg.NpmPkg,
    49  			Metadata:  pkg.YarnLockEntry{},
    50  		},
    51  		{
    52  			Name:      "ajv",
    53  			Version:   "6.12.3",
    54  			Locations: locations,
    55  			PURL:      "pkg:npm/ajv@6.12.3",
    56  			Language:  pkg.JavaScript,
    57  			Type:      pkg.NpmPkg,
    58  			Metadata:  pkg.YarnLockEntry{},
    59  		},
    60  		{
    61  			Name:      "asn1.js",
    62  			Version:   "4.10.1",
    63  			Locations: locations,
    64  			PURL:      "pkg:npm/asn1.js@4.10.1",
    65  			Language:  pkg.JavaScript,
    66  			Type:      pkg.NpmPkg,
    67  			Metadata:  pkg.YarnLockEntry{},
    68  		},
    69  		{
    70  			Name:      "atob",
    71  			Version:   "2.1.2",
    72  			Locations: locations,
    73  			PURL:      "pkg:npm/atob@2.1.2",
    74  			Language:  pkg.JavaScript,
    75  			Type:      pkg.NpmPkg,
    76  			Metadata:  pkg.YarnLockEntry{},
    77  		},
    78  		{
    79  			Name:      "aws-sdk",
    80  			Version:   "2.706.0",
    81  			PURL:      "pkg:npm/aws-sdk@2.706.0",
    82  			Locations: locations,
    83  			Language:  pkg.JavaScript,
    84  			Type:      pkg.NpmPkg,
    85  			Metadata:  pkg.YarnLockEntry{},
    86  		},
    87  		{
    88  			Name:      "c0n-fab_u.laTION",
    89  			Version:   "7.7.7",
    90  			Locations: locations,
    91  			PURL:      "pkg:npm/c0n-fab_u.laTION@7.7.7",
    92  			Language:  pkg.JavaScript,
    93  			Type:      pkg.NpmPkg,
    94  			Metadata:  pkg.YarnLockEntry{},
    95  		},
    96  		{
    97  			Name:      "jhipster-core",
    98  			Version:   "7.3.4",
    99  			Locations: locations,
   100  			PURL:      "pkg:npm/jhipster-core@7.3.4",
   101  			Language:  pkg.JavaScript,
   102  			Type:      pkg.NpmPkg,
   103  			Metadata:  pkg.YarnLockEntry{},
   104  		},
   105  	}
   106  
   107  	adapter := newGenericYarnLockAdapter(CatalogerConfig{})
   108  	pkgtest.TestFileParser(t, fixture, adapter.parseYarnLock, expectedPkgs, expectedRelationships)
   109  }
   110  
   111  func TestParseYarnLock(t *testing.T) {
   112  	var expectedRelationships []artifact.Relationship
   113  	fixture := "test-fixtures/yarn/yarn.lock"
   114  	locations := file.NewLocationSet(file.NewLocation(fixture))
   115  
   116  	expectedPkgs := []pkg.Package{
   117  		{
   118  			Name:      "@babel/code-frame",
   119  			Version:   "7.10.4",
   120  			Locations: locations,
   121  			PURL:      "pkg:npm/%40babel/code-frame@7.10.4",
   122  			Language:  pkg.JavaScript,
   123  			Type:      pkg.NpmPkg,
   124  			Metadata: pkg.YarnLockEntry{
   125  				Resolved:  "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a",
   126  				Integrity: "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
   127  			},
   128  		},
   129  		{
   130  			Name:      "@types/minimatch",
   131  			Version:   "3.0.3",
   132  			Locations: locations,
   133  			PURL:      "pkg:npm/%40types/minimatch@3.0.3",
   134  			Language:  pkg.JavaScript,
   135  			Type:      pkg.NpmPkg,
   136  			Metadata: pkg.YarnLockEntry{
   137  				Resolved:  "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d",
   138  				Integrity: "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
   139  			},
   140  		},
   141  		{
   142  			Name:      "@types/qs",
   143  			Version:   "6.9.4",
   144  			Locations: locations,
   145  			PURL:      "pkg:npm/%40types/qs@6.9.4",
   146  			Language:  pkg.JavaScript,
   147  			Type:      pkg.NpmPkg,
   148  			Metadata: pkg.YarnLockEntry{
   149  				Resolved:  "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a",
   150  				Integrity: "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==",
   151  			},
   152  		},
   153  		{
   154  			Name:      "ajv",
   155  			Version:   "6.12.3",
   156  			Locations: locations,
   157  			PURL:      "pkg:npm/ajv@6.12.3",
   158  			Language:  pkg.JavaScript,
   159  			Type:      pkg.NpmPkg,
   160  			Metadata: pkg.YarnLockEntry{
   161  				Resolved:  "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706",
   162  				Integrity: "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
   163  			},
   164  		},
   165  		{
   166  			Name:      "asn1.js",
   167  			Version:   "4.10.1",
   168  			Locations: locations,
   169  			PURL:      "pkg:npm/asn1.js@4.10.1",
   170  			Language:  pkg.JavaScript,
   171  			Type:      pkg.NpmPkg,
   172  			Metadata: pkg.YarnLockEntry{
   173  				Resolved:  "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0",
   174  				Integrity: "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
   175  			},
   176  		},
   177  		{
   178  			Name:      "atob",
   179  			Version:   "2.1.2",
   180  			Locations: locations,
   181  
   182  			PURL:     "pkg:npm/atob@2.1.2",
   183  			Language: pkg.JavaScript,
   184  			Type:     pkg.NpmPkg,
   185  			Metadata: pkg.YarnLockEntry{
   186  				Resolved:  "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9",
   187  				Integrity: "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
   188  			},
   189  		},
   190  		{
   191  			Name:      "aws-sdk",
   192  			Version:   "2.706.0",
   193  			Locations: locations,
   194  			PURL:      "pkg:npm/aws-sdk@2.706.0",
   195  			Language:  pkg.JavaScript,
   196  			Type:      pkg.NpmPkg,
   197  			Metadata: pkg.YarnLockEntry{
   198  				Resolved:  "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.706.0.tgz#09f65e9a91ecac5a635daf934082abae30eca953",
   199  				Integrity: "sha512-7GT+yrB5Wb/zOReRdv/Pzkb2Qt+hz6B/8FGMVaoysX3NryHvQUdz7EQWi5yhg9CxOjKxdw5lFwYSs69YlSp1KA==",
   200  			},
   201  		},
   202  		{
   203  			Name:      "jhipster-core",
   204  			Version:   "7.3.4",
   205  			Locations: locations,
   206  			PURL:      "pkg:npm/jhipster-core@7.3.4",
   207  			Language:  pkg.JavaScript,
   208  			Type:      pkg.NpmPkg,
   209  			Metadata: pkg.YarnLockEntry{
   210  				Resolved:  "https://registry.yarnpkg.com/jhipster-core/-/jhipster-core-7.3.4.tgz#c34b8c97c7f4e8b7518dae015517e2112c73cc80",
   211  				Integrity: "sha512-AUhT69kNkqppaJZVfan/xnKG4Gs9Ggj7YLtTZFVe+xg+THrbMb5Ng7PL07PDlDw4KAEA33GMCwuAf65E8EpC4g==",
   212  			},
   213  		},
   214  		{
   215  			Name:      "something-i-made-up",
   216  			Version:   "7.7.7",
   217  			Locations: locations,
   218  			PURL:      "pkg:npm/something-i-made-up@7.7.7",
   219  			Language:  pkg.JavaScript,
   220  			Type:      pkg.NpmPkg,
   221  			Metadata: pkg.YarnLockEntry{
   222  				Resolved:  "https://registry.yarnpkg.com/something-i-made-up/-/c0n-fab_u.laTION-7.7.7.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0",
   223  				Integrity: "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
   224  			},
   225  		},
   226  	}
   227  
   228  	adapter := newGenericYarnLockAdapter(CatalogerConfig{})
   229  	pkgtest.TestFileParser(t, fixture, adapter.parseYarnLock, expectedPkgs, expectedRelationships)
   230  }
   231  
   232  type handlerPath struct {
   233  	path    string
   234  	handler func(w http.ResponseWriter, r *http.Request)
   235  }
   236  
   237  func TestSearchYarnForLicenses(t *testing.T) {
   238  	fixture := "test-fixtures/yarn-remote/yarn.lock"
   239  	locations := file.NewLocationSet(file.NewLocation(fixture))
   240  	mux, url, teardown := setup()
   241  	defer teardown()
   242  	tests := []struct {
   243  		name             string
   244  		fixture          string
   245  		config           CatalogerConfig
   246  		requestHandlers  []handlerPath
   247  		expectedPackages []pkg.Package
   248  	}{
   249  		{
   250  			name:   "search remote licenses returns the expected licenses when search is set to true",
   251  			config: CatalogerConfig{SearchRemoteLicenses: true},
   252  			requestHandlers: []handlerPath{
   253  				{
   254  					// https://registry.yarnpkg.com/@babel/code-frame/7.10.4
   255  					path:    "/@babel/code-frame/7.10.4",
   256  					handler: generateMockNPMHandler("test-fixtures/yarn-remote/registry_response.json"),
   257  				},
   258  			},
   259  			expectedPackages: []pkg.Package{
   260  				{
   261  					Name:      "@babel/code-frame",
   262  					Version:   "7.10.4",
   263  					Locations: locations,
   264  					PURL:      "pkg:npm/%40babel/code-frame@7.10.4",
   265  					Licenses:  pkg.NewLicenseSet(pkg.NewLicense("MIT")),
   266  					Language:  pkg.JavaScript,
   267  					Type:      pkg.NpmPkg,
   268  					Metadata: pkg.YarnLockEntry{
   269  						Resolved:  "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a",
   270  						Integrity: "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
   271  					},
   272  				},
   273  			},
   274  		},
   275  	}
   276  
   277  	for _, tc := range tests {
   278  		t.Run(tc.name, func(t *testing.T) {
   279  			// set up the mock server
   280  			for _, handler := range tc.requestHandlers {
   281  				mux.HandleFunc(handler.path, handler.handler)
   282  			}
   283  			tc.config.NPMBaseURL = url
   284  			adapter := newGenericYarnLockAdapter(tc.config)
   285  			pkgtest.TestFileParser(t, fixture, adapter.parseYarnLock, tc.expectedPackages, nil)
   286  		})
   287  	}
   288  }
   289  
   290  func TestParseYarnFindPackageNames(t *testing.T) {
   291  	tests := []struct {
   292  		line     string
   293  		expected string
   294  	}{
   295  		{
   296  			line:     `"@babel/code-frame@npm:7.10.4":`,
   297  			expected: "@babel/code-frame",
   298  		},
   299  		{
   300  			line:     `"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":`,
   301  			expected: "@babel/code-frame",
   302  		},
   303  		{
   304  			line:     "ajv@^6.10.2, ajv@^6.5.5:",
   305  			expected: "ajv",
   306  		},
   307  		{
   308  			line:     "aws-sdk@2.706.0:",
   309  			expected: "aws-sdk",
   310  		},
   311  		{
   312  			line:     "asn1.js@^4.0.0:",
   313  			expected: "asn1.js",
   314  		},
   315  		{
   316  			line:     "c0n-fab_u.laTION@^7.0.0",
   317  			expected: "c0n-fab_u.laTION",
   318  		},
   319  		{
   320  			line:     `"newtest@workspace:.":`,
   321  			expected: "newtest",
   322  		},
   323  		{
   324  			line:     `"color-convert@npm:^1.9.0":`,
   325  			expected: "color-convert",
   326  		},
   327  		{
   328  			line:     `"@npmcorp/code-frame@^7.1.0", "@npmcorp/code-frame@^7.10.4":`,
   329  			expected: "@npmcorp/code-frame",
   330  		},
   331  		{
   332  			line:     `"@npmcorp/code-frame@^7.2.3":`,
   333  			expected: "@npmcorp/code-frame",
   334  		},
   335  		{
   336  			line:     `"@s/odd-name@^7.1.2":`,
   337  			expected: "@s/odd-name",
   338  		},
   339  		{
   340  			line:     `"@/code-frame@^7.3.4":`,
   341  			expected: "",
   342  		},
   343  		{
   344  			line:     `"code-frame":`,
   345  			expected: "",
   346  		},
   347  	}
   348  
   349  	for _, test := range tests {
   350  		t.Run(test.expected, func(t *testing.T) {
   351  			t.Parallel()
   352  			actual := findPackageName(test.line)
   353  			assert.Equal(t, test.expected, actual)
   354  		})
   355  	}
   356  }
   357  
   358  func TestParseYarnFindPackageVersions(t *testing.T) {
   359  	tests := []struct {
   360  		line     string
   361  		expected string
   362  	}{
   363  		{
   364  			line:     `  version "7.10.4"`,
   365  			expected: "7.10.4",
   366  		},
   367  		{
   368  			line:     ` version "7.11.5"`,
   369  			expected: "7.11.5",
   370  		},
   371  		{
   372  			line:     `version "7.12.6"`,
   373  			expected: "",
   374  		},
   375  		{
   376  			line:     `  version "0.0.0"`,
   377  			expected: "0.0.0",
   378  		},
   379  		{
   380  			line:     `  version "2" `,
   381  			expected: "2",
   382  		},
   383  		{
   384  			line:     `  version "9.3"`,
   385  			expected: "9.3",
   386  		},
   387  		{
   388  			line:     "ajv@^6.10.2, ajv@^6.5.5",
   389  			expected: "",
   390  		},
   391  		{
   392  			line:     "atob@^2.1.2:",
   393  			expected: "",
   394  		},
   395  		{
   396  			line:     `"color-convert@npm:^1.9.0":`,
   397  			expected: "",
   398  		},
   399  		{
   400  			line:     "  version: 1.9.3",
   401  			expected: "1.9.3",
   402  		},
   403  		{
   404  			line:     "  version: 2",
   405  			expected: "2",
   406  		},
   407  		{
   408  			line:     "  version: 9.3",
   409  			expected: "9.3",
   410  		},
   411  		{
   412  			line:     "ajv@^6.10.2, ajv@^6.5.5",
   413  			expected: "",
   414  		},
   415  		{
   416  			line:     "atob@^2.1.2:",
   417  			expected: "",
   418  		},
   419  		{
   420  			line:     "  version: 1.0.0-alpha+001",
   421  			expected: "1.0.0-alpha",
   422  		},
   423  		{
   424  			line:     "  version: 1.0.0-beta_test+exp.sha.5114f85",
   425  			expected: "1.0.0-beta_test",
   426  		},
   427  		{
   428  			line:     "  version: 1.0.0+21AF26D3-117B344092BD",
   429  			expected: "1.0.0",
   430  		},
   431  		{
   432  			line:     "  version: 0.0.0-use.local",
   433  			expected: "0.0.0-use.local",
   434  		},
   435  	}
   436  
   437  	for _, test := range tests {
   438  		t.Run(test.expected, func(t *testing.T) {
   439  			t.Parallel()
   440  			actual := findPackageVersion(test.line)
   441  			assert.Equal(t, test.expected, actual)
   442  		})
   443  	}
   444  }
   445  
   446  func generateMockNPMHandler(responseFixture string) func(w http.ResponseWriter, r *http.Request) {
   447  	return func(w http.ResponseWriter, r *http.Request) {
   448  		w.WriteHeader(http.StatusOK)
   449  		// Copy the file's content to the response writer
   450  		file, err := os.Open(responseFixture)
   451  		if err != nil {
   452  			http.Error(w, err.Error(), http.StatusInternalServerError)
   453  			return
   454  		}
   455  		defer file.Close()
   456  
   457  		_, err = io.Copy(w, file)
   458  		if err != nil {
   459  			http.Error(w, err.Error(), http.StatusInternalServerError)
   460  			return
   461  		}
   462  	}
   463  }
   464  
   465  // setup sets up a test HTTP server for mocking requests to maven central.
   466  // The returned url is injected into the Config so the client uses the test server.
   467  // Tests should register handlers on mux to simulate the expected request/response structure
   468  func setup() (mux *http.ServeMux, serverURL string, teardown func()) {
   469  	// mux is the HTTP request multiplexer used with the test server.
   470  	mux = http.NewServeMux()
   471  
   472  	// We want to ensure that tests catch mistakes where the endpoint URL is
   473  	// specified as absolute rather than relative. It only makes a difference
   474  	// when there's a non-empty base URL path. So, use that. See issue #752.
   475  	apiHandler := http.NewServeMux()
   476  	apiHandler.Handle("/", mux)
   477  	// server is a test HTTP server used to provide mock API responses.
   478  	server := httptest.NewServer(apiHandler)
   479  
   480  	return mux, server.URL, server.Close
   481  }