github.com/google/osv-scalibr@v0.4.1/guidedremediation/internal/lockfile/npm/packagelockjson_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 npm_test
    16  
    17  import (
    18  	"os"
    19  	"path/filepath"
    20  	"testing"
    21  
    22  	"deps.dev/util/resolve"
    23  	"deps.dev/util/resolve/schema"
    24  	"github.com/google/go-cmp/cmp"
    25  	"github.com/google/osv-scalibr/clients/clienttest"
    26  	scalibrfs "github.com/google/osv-scalibr/fs"
    27  	"github.com/google/osv-scalibr/guidedremediation/internal/lockfile/npm"
    28  	"github.com/google/osv-scalibr/guidedremediation/result"
    29  )
    30  
    31  func TestReadV1(t *testing.T) {
    32  	// This lockfile was generated using a private registry with https://verdaccio.org/
    33  	// Mock packages were published to it and installed with npm.
    34  	rw, err := npm.GetReadWriter()
    35  	if err != nil {
    36  		t.Fatalf("error creating ReadWriter: %v", err)
    37  	}
    38  	fsys := scalibrfs.DirFS("./testdata/v1")
    39  	got, err := rw.Read("package-lock.json", fsys)
    40  	if err != nil {
    41  		t.Fatalf("error reading lockfile: %v", err)
    42  	}
    43  
    44  	if err := got.Canon(); err != nil {
    45  		t.Fatalf("failed canonicalizing got graph: %v", err)
    46  	}
    47  
    48  	want, err := schema.ParseResolve(`
    49  r 1.0.0
    50  	@fake-registry/a@^1.2.3 1.2.3
    51  		$b@^1.0.0
    52  	b: @fake-registry/b@^1.0.1 1.0.1
    53  	Dev KnownAs a-dev|@fake-registry/a@^2.3.4 2.3.4
    54  		# all indirect dependencies become regular because it's impossible to tell type in v1
    55  		@fake-registry/b@^2.0.0 2.0.0
    56  			@fake-registry/c@^1.0.0 1.1.1
    57  				# peerDependencies are not supported in v1
    58  			@fake-registry/d@^2.0.0 2.2.2
    59  	# v1 does not support workspaces
    60  `, resolve.NPM)
    61  	if err != nil {
    62  		t.Fatalf("error parsing want graph: %v", err)
    63  	}
    64  
    65  	if err := want.Canon(); err != nil {
    66  		t.Fatalf("failed canonicalizing want graph: %v", err)
    67  	}
    68  
    69  	if diff := cmp.Diff(want, got); diff != "" {
    70  		t.Errorf("npm lockfile mismatch (-want +got):\n%s", diff)
    71  	}
    72  }
    73  
    74  func TestReadV2(t *testing.T) {
    75  	// This lockfile was generated using a private registry with https://verdaccio.org/
    76  	// Mock packages were published to it and installed with npm.
    77  	rw, err := npm.GetReadWriter()
    78  	if err != nil {
    79  		t.Fatalf("error creating ReadWriter: %v", err)
    80  	}
    81  	fsys := scalibrfs.DirFS("./testdata/v2")
    82  	got, err := rw.Read("package-lock.json", fsys)
    83  	if err != nil {
    84  		t.Fatalf("error reading lockfile: %v", err)
    85  	}
    86  
    87  	if err := got.Canon(); err != nil {
    88  		t.Fatalf("failed canonicalizing got graph: %v", err)
    89  	}
    90  
    91  	want, err := schema.ParseResolve(`
    92  r 1.0.0
    93  	@fake-registry/a@^1.2.3 1.2.3
    94  		Opt|$b@^1.0.0
    95  	b: @fake-registry/b@^1.0.1 1.0.1
    96  	Dev KnownAs a-dev|@fake-registry/a@^2.3.4 2.3.4
    97  		@fake-registry/b@^2.0.0 2.0.0
    98  			c: @fake-registry/c@^1.0.0 1.1.1
    99  				Scope peer|$d@^2.0.0
   100  			d: @fake-registry/d@^2.0.0 2.2.2
   101  	# workspace
   102  	w@* 1.0.0
   103  		Dev|@fake-registry/a@^2.3.4 2.3.4
   104  			@fake-registry/b@^2.0.0 2.0.0
   105  				$c@^1.0.0
   106  				$d@^2.0.0
   107  `, resolve.NPM)
   108  	if err != nil {
   109  		t.Fatalf("error parsing want graph: %v", err)
   110  	}
   111  
   112  	if err := want.Canon(); err != nil {
   113  		t.Fatalf("failed canonicalizing want graph: %v", err)
   114  	}
   115  
   116  	if diff := cmp.Diff(want, got); diff != "" {
   117  		t.Errorf("npm lockfile mismatch (-want +got):\n%s", diff)
   118  	}
   119  }
   120  
   121  func TestTypeOrdering(t *testing.T) {
   122  	// Testing the behavior when a package is included in multiple dependency type fields.
   123  	// Empirically, devDependencies > optionalDependencies > dependencies > peerDependencies
   124  
   125  	// This lockfile was manually constructed.
   126  	rw, err := npm.GetReadWriter()
   127  	if err != nil {
   128  		t.Fatalf("error creating ReadWriter: %v", err)
   129  	}
   130  	fsys := scalibrfs.DirFS("./testdata/type_order")
   131  	got, err := rw.Read("package-lock.json", fsys)
   132  	if err != nil {
   133  		t.Fatalf("error reading lockfile: %v", err)
   134  	}
   135  
   136  	if err := got.Canon(); err != nil {
   137  		t.Fatalf("failed canonicalizing got graph: %v", err)
   138  	}
   139  
   140  	want, err := schema.ParseResolve(`
   141  root 1.0.0
   142  	Dev|a@4.0.0 4.0.0
   143  	Opt|b@3.0.0 3.0.0
   144  	c@2.0.0 2.0.0
   145  	Scope peer|d@1.0.0 1.0.0
   146  `, resolve.NPM)
   147  	if err != nil {
   148  		t.Fatalf("error parsing want graph: %v", err)
   149  	}
   150  
   151  	if err := want.Canon(); err != nil {
   152  		t.Fatalf("failed canonicalizing want graph: %v", err)
   153  	}
   154  
   155  	if diff := cmp.Diff(want, got); diff != "" {
   156  		t.Errorf("npm lockfile mismatch (-want +got):\n%s", diff)
   157  	}
   158  }
   159  
   160  func TestPeerMeta(t *testing.T) {
   161  	// Testing the behavior with peerDependencies and peerDependenciesMeta.
   162  
   163  	// This lockfile was manually constructed.
   164  	rw, err := npm.GetReadWriter()
   165  	if err != nil {
   166  		t.Fatalf("error creating ReadWriter: %v", err)
   167  	}
   168  	fsys := scalibrfs.DirFS("./testdata/peer_meta")
   169  	got, err := rw.Read("package-lock.json", fsys)
   170  	if err != nil {
   171  		t.Fatalf("error reading lockfile: %v", err)
   172  	}
   173  
   174  	if err := got.Canon(); err != nil {
   175  		t.Fatalf("failed canonicalizing got graph: %v", err)
   176  	}
   177  
   178  	want, err := schema.ParseResolve(`
   179  root 1.0.0
   180  	dep@^1.0.0 1.0.0
   181  		p2: Opt Scope peer|peer2@^2.0.0 2.0.0
   182  		Scope peer KnownAs peer3|peer2@^3.0.0 3.0.0
   183  	$p2@^2.0.0
   184  `, resolve.NPM)
   185  	if err != nil {
   186  		t.Fatalf("error parsing want graph: %v", err)
   187  	}
   188  
   189  	if err := want.Canon(); err != nil {
   190  		t.Fatalf("failed canonicalizing want graph: %v", err)
   191  	}
   192  
   193  	if diff := cmp.Diff(want, got); diff != "" {
   194  		t.Errorf("npm lockfile mismatch (-want +got):\n%s", diff)
   195  	}
   196  }
   197  
   198  func TestWrite(t *testing.T) {
   199  	// Set up mock npm registry
   200  	srv := clienttest.NewMockHTTPServer(t)
   201  	srv.SetResponseFromFile(t, "/@fake-registry%2fa/1.2.4", "testdata/fake_registry/a-1.2.4.json")
   202  	srv.SetResponseFromFile(t, "/@fake-registry%2fa/2.3.5", "testdata/fake_registry/a-2.3.5.json")
   203  
   204  	// Create output directory with npmrc pointing to the registry
   205  	outDir := t.TempDir()
   206  	if err := os.WriteFile(filepath.Join(outDir, ".npmrc"), []byte("registry="+srv.URL+"\n"), 0644); err != nil {
   207  		t.Fatalf("error writing npmrc: %v", err)
   208  	}
   209  
   210  	// Create patches to write
   211  	patches := []result.Patch{
   212  		{
   213  			PackageUpdates: []result.PackageUpdate{
   214  				{
   215  					Name:        "@fake-registry/a",
   216  					VersionFrom: "1.2.3",
   217  					VersionTo:   "1.2.4",
   218  				},
   219  				{
   220  					Name:        "@fake-registry/a",
   221  					VersionFrom: "2.3.4",
   222  					VersionTo:   "2.3.5",
   223  				},
   224  			},
   225  		},
   226  	}
   227  
   228  	want, err := os.ReadFile("testdata/write/want.package-lock.json")
   229  	if err != nil {
   230  		t.Fatalf("error reading want lockfile: %v", err)
   231  	}
   232  
   233  	// Write the patched lockfile
   234  	rw, err := npm.GetReadWriter()
   235  	if err != nil {
   236  		t.Fatalf("error creating ReadWriter: %v", err)
   237  	}
   238  	gotPath := filepath.Join(outDir, "package-lock.json")
   239  	if err := rw.Write("write/package-lock.json", scalibrfs.DirFS("testdata"), patches, gotPath); err != nil {
   240  		t.Fatalf("error writing lockfile: %v", err)
   241  	}
   242  	got, err := os.ReadFile(gotPath)
   243  	if err != nil {
   244  		t.Fatalf("error reading got lockfile: %v", err)
   245  	}
   246  
   247  	if diff := cmp.Diff(want, got); diff != "" {
   248  		t.Errorf("npm lockfile mismatch (-want +got):\n%s", diff)
   249  	}
   250  }