github.com/google/osv-scalibr@v0.4.1/clients/datasource/npm_registry_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 datasource_test
    16  
    17  import (
    18  	"cmp"
    19  	"path/filepath"
    20  	"strings"
    21  	"testing"
    22  
    23  	gocmp "github.com/google/go-cmp/cmp"
    24  	"github.com/google/go-cmp/cmp/cmpopts"
    25  	"github.com/google/osv-scalibr/clients/clienttest"
    26  	"github.com/google/osv-scalibr/clients/datasource"
    27  	"github.com/tidwall/gjson"
    28  )
    29  
    30  func TestNpmRegistryClient(t *testing.T) {
    31  	//nolint:gosec  // "Potential hardcoded credentials" :)
    32  	const (
    33  		auth      = "Y29vbDphdXRoCg=="
    34  		authToken = "bmljZS10b2tlbgo="
    35  	)
    36  
    37  	srv1 := clienttest.NewMockHTTPServer(t)
    38  	srv1.SetAuthorization(t, "Basic "+auth)
    39  	srv1.SetResponseFromFile(t, "/fake-package", "./testdata/npm_registry/fake-package.json")
    40  	srv1.SetResponseFromFile(t, "/fake-package/2.2.2", "./testdata/npm_registry/fake-package-2.2.2.json")
    41  
    42  	srv2 := clienttest.NewMockHTTPServer(t)
    43  	srv2.SetAuthorization(t, "Bearer "+authToken)
    44  	srv2.SetResponseFromFile(t, "/@fake-registry%2fa", "./testdata/npm_registry/fake-registry-a.json")
    45  
    46  	npmrcFile := createTempNpmrc(t, ".npmrc")
    47  	writeToNpmrc(t, npmrcFile,
    48  		"registry="+srv1.URL,
    49  		"//"+strings.TrimPrefix(srv1.URL, "http://")+"/:_auth="+auth,
    50  		"@fake-registry:registry="+srv2.URL,
    51  		"//"+strings.TrimPrefix(srv2.URL, "http://")+"/:_authToken="+authToken,
    52  	)
    53  
    54  	cl, err := datasource.NewNPMRegistryAPIClient(filepath.Dir(npmrcFile))
    55  	if err != nil {
    56  		t.Fatalf("failed creating npm api client: %v", err)
    57  	}
    58  	{
    59  		const pkg = "fake-package"
    60  		want := datasource.NPMRegistryVersions{
    61  			Versions: []string{"1.0.0", "2.2.2"},
    62  			Tags: map[string]string{
    63  				"latest":   "1.0.0",
    64  				"version1": "1.0.0",
    65  				"version2": "2.2.2",
    66  			},
    67  		}
    68  		got, err := cl.Versions(t.Context(), pkg)
    69  		if err != nil {
    70  			t.Fatalf("failed getting versions: %v", err)
    71  		}
    72  		if diff := gocmp.Diff(want, got, cmpopts.SortSlices(cmp.Less[string])); diff != "" {
    73  			t.Errorf("Versions(\"%s\") (-want +got)\n%s", pkg, diff)
    74  		}
    75  	}
    76  	{
    77  		const pkg = "@fake-registry/a"
    78  		want := datasource.NPMRegistryVersions{
    79  			Versions: []string{"1.2.3", "2.3.4"},
    80  			Tags:     map[string]string{"latest": "2.3.4"},
    81  		}
    82  		got, err := cl.Versions(t.Context(), pkg)
    83  		if err != nil {
    84  			t.Fatalf("failed getting versions: %v", err)
    85  		}
    86  		if diff := gocmp.Diff(want, got, cmpopts.SortSlices(cmp.Less[string])); diff != "" {
    87  			t.Errorf("Versions(\"%s\") (-want +got)\n%s", pkg, diff)
    88  		}
    89  	}
    90  
    91  	{
    92  		const pkg = "fake-package"
    93  		const ver = "2.2.2"
    94  		want := datasource.NPMRegistryDependencies{
    95  			Dependencies: map[string]string{
    96  				"a": "^3.0.1",
    97  				"b": "^2.0.1",
    98  				"e": "^0.2.33",
    99  				"f": "npm:g@^2.0.1",
   100  			},
   101  			DevDependencies: map[string]string{
   102  				"c": "^1.1.1",
   103  				"d": "^1.0.2",
   104  			},
   105  			PeerDependencies: map[string]string{
   106  				"h": "^1.0.0",
   107  			},
   108  			OptionalDependencies: map[string]string{
   109  				"e": "^0.2.33",
   110  				"f": "npm:g@^2.0.1",
   111  			},
   112  			BundleDependencies: []string{
   113  				"a",
   114  			},
   115  		}
   116  		got, err := cl.Dependencies(t.Context(), pkg, ver)
   117  		if err != nil {
   118  			t.Fatalf("failed getting dependencies: %v", err)
   119  		}
   120  		if diff := gocmp.Diff(want, got, cmpopts.SortSlices(cmp.Less[string])); diff != "" {
   121  			t.Errorf("Dependencies(\"%s\", \"%s\") (-want +got)\n%s", pkg, ver, diff)
   122  		}
   123  	}
   124  	{
   125  		const pkg = "fake-package"
   126  		const ver = "2.2.2"
   127  		want := gjson.Parse(`{
   128  			"name": "fake-package",
   129  			"version": "2.2.2",
   130  			"main": "index.js",
   131  			"scripts": {
   132  				"test": "echo \"Error: no test specified\" && exit 1"
   133  			},
   134  			"author": "",
   135  			"license": "ISC",
   136  			"dependencies": {
   137  				"a": "^3.0.1",
   138  				"b": "^2.0.1",
   139  				"e": "^0.2.33",
   140  				"f": "npm:g@^2.0.1"
   141  			},
   142  			"devDependencies": {
   143  				"c": "^1.1.1",
   144  				"d": "^1.0.2"
   145  			},
   146  			"optionalDependencies": {
   147  				"e": "^0.2.33",
   148  				"f": "npm:g@^2.0.1"
   149  			},
   150  			"peerDependencies": {
   151  				"h": "^1.0.0"
   152  			},
   153  			"bundleDependencies": [
   154  				"a"
   155  			],
   156  			"_id": "fake-package@2.2.2",
   157  			"_nodeVersion": "20.9.0",
   158  			"_npmVersion": "10.1.0",
   159  			"dist": {
   160  				"integrity": "sha512-NWvNE9fxykrzSQVr1CSKchzkQr5qwplvgn3O/0JL46qM6BhoGlKRjLiaZYdo1byXJWLGthghOgGpUZiEL04HQQ==",
   161  				"shasum": "8dc47515da4e67bb794a4c9c7f4750bb4d67c7fc",
   162  				"tarball": "http://localhost:4873/fake-package/-/fake-package-2.2.2.tgz"
   163  			},
   164  			"contributors": []
   165  		}`)
   166  		got, err := cl.FullJSON(t.Context(), pkg, ver)
   167  		if err != nil {
   168  			t.Fatalf("failed getting full json: %v", err)
   169  		}
   170  		wantMap := want.Value().(map[string]any)
   171  		gotMap := got.Value().(map[string]any)
   172  		if diff := gocmp.Diff(wantMap, gotMap, cmpopts.SortSlices(cmp.Less[string])); diff != "" {
   173  			t.Errorf("FullJSON(\"%s\", \"%s\") (-want +got)\n%s", pkg, ver, diff)
   174  		}
   175  	}
   176  }