github.com/google/osv-scalibr@v0.4.1/guidedremediation/internal/manifest/npm/packagejson_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 "fmt" 19 "os" 20 "path/filepath" 21 "testing" 22 23 "deps.dev/util/resolve" 24 "deps.dev/util/resolve/dep" 25 "github.com/google/go-cmp/cmp" 26 scalibrfs "github.com/google/osv-scalibr/fs" 27 "github.com/google/osv-scalibr/guidedremediation/internal/manifest" 28 "github.com/google/osv-scalibr/guidedremediation/internal/manifest/npm" 29 "github.com/google/osv-scalibr/guidedremediation/result" 30 ) 31 32 func aliasType(t *testing.T, aliasedName string) dep.Type { 33 t.Helper() 34 var typ dep.Type 35 typ.AddAttr(dep.KnownAs, aliasedName) 36 37 return typ 38 } 39 40 func makeVK(t *testing.T, name, version string, versionType resolve.VersionType) resolve.VersionKey { 41 t.Helper() 42 return resolve.VersionKey{ 43 PackageKey: resolve.PackageKey{ 44 System: resolve.NPM, 45 Name: name, 46 }, 47 Version: version, 48 VersionType: versionType, 49 } 50 } 51 52 func makeReqKey(t *testing.T, name, knownAs string) manifest.RequirementKey { 53 t.Helper() 54 var typ dep.Type 55 if knownAs != "" { 56 typ.AddAttr(dep.KnownAs, knownAs) 57 } 58 59 return npm.MakeRequirementKey(resolve.RequirementVersion{ 60 VersionKey: resolve.VersionKey{ 61 PackageKey: resolve.PackageKey{ 62 Name: name, 63 System: resolve.NPM, 64 }, 65 }, 66 Type: typ, 67 }) 68 } 69 70 type testManifest struct { 71 FilePath string 72 Root resolve.Version 73 System resolve.System 74 Requirements []resolve.RequirementVersion 75 Groups map[manifest.RequirementKey][]string 76 LocalManifests []testManifest 77 } 78 79 func checkManifest(t *testing.T, name string, got manifest.Manifest, want testManifest) { 80 t.Helper() 81 if want.FilePath != got.FilePath() { 82 t.Errorf("%s.FilePath() = %q, want %q", name, got.FilePath(), want.FilePath) 83 } 84 if diff := cmp.Diff(want.Root, got.Root()); diff != "" { 85 t.Errorf("%s.Root() (-want +got):\n%s", name, diff) 86 } 87 if want.System != got.System() { 88 t.Errorf("%s.System() = %v, want %v", name, got.System(), want.System) 89 } 90 if diff := cmp.Diff(want.Requirements, got.Requirements()); diff != "" { 91 t.Errorf("%s.Requirements() (-want +got):\n%s", name, diff) 92 } 93 if diff := cmp.Diff(want.Groups, got.Groups()); diff != "" { 94 t.Errorf("%s.Groups() (-want +got):\n%s", name, diff) 95 } 96 gotLocal := got.LocalManifests() 97 if len(want.LocalManifests) != len(got.LocalManifests()) { 98 t.Errorf("got %d %s.LocalManifests(), want %d", len(gotLocal), name, len(want.LocalManifests)) 99 } 100 n := min(len(gotLocal), len(want.LocalManifests)) 101 for i := range n { 102 checkManifest(t, fmt.Sprintf("%s.LocalManifests[%d]", name, i), gotLocal[i], want.LocalManifests[i]) 103 } 104 } 105 106 func TestRead(t *testing.T) { 107 rw, err := npm.GetReadWriter() 108 if err != nil { 109 t.Fatalf("error creating ReadWriter: %v", err) 110 } 111 fsys := scalibrfs.DirFS("./testdata") 112 got, err := rw.Read("package.json", fsys) 113 if err != nil { 114 t.Fatalf("error reading manifest: %v", err) 115 } 116 117 want := testManifest{ 118 FilePath: "package.json", 119 Root: resolve.Version{ 120 VersionKey: makeVK(t, "npm-manifest", "1.0.0", resolve.Concrete), 121 }, 122 System: resolve.NPM, 123 Requirements: []resolve.RequirementVersion{ 124 { 125 Type: aliasType(t, "cliui"), // sorts on aliased name, not real package name 126 VersionKey: makeVK(t, "@isaacs/cliui", "^8.0.2", resolve.Requirement), 127 }, 128 { 129 // Type: dep.NewType(dep.Dev), devDependencies treated as prod to make resolution work 130 VersionKey: makeVK(t, "eslint", "^8.57.0", resolve.Requirement), 131 }, 132 { 133 Type: dep.NewType(dep.Opt), 134 VersionKey: makeVK(t, "glob", "^10.3.10", resolve.Requirement), 135 }, 136 { 137 VersionKey: makeVK(t, "jquery", "latest", resolve.Requirement), 138 }, 139 { 140 VersionKey: makeVK(t, "lodash", "4.17.17", resolve.Requirement), 141 }, 142 { 143 VersionKey: makeVK(t, "string-width", "^5.1.2", resolve.Requirement), 144 }, 145 { 146 Type: aliasType(t, "string-width-aliased"), 147 VersionKey: makeVK(t, "string-width", "^4.2.3", resolve.Requirement), 148 }, 149 }, 150 Groups: map[manifest.RequirementKey][]string{ 151 makeReqKey(t, "eslint", ""): {"dev"}, 152 makeReqKey(t, "glob", ""): {"optional"}, 153 }, 154 } 155 156 checkManifest(t, "Manifest", got, want) 157 } 158 159 func TestReadWithWorkspaces(t *testing.T) { 160 rw, err := npm.GetReadWriter() 161 if err != nil { 162 t.Fatalf("error creating ReadWriter: %v", err) 163 } 164 fsys := scalibrfs.DirFS("./testdata/workspaces") 165 got, err := rw.Read("package.json", fsys) 166 if err != nil { 167 t.Fatalf("error reading manifest: %v", err) 168 } 169 170 want := testManifest{ 171 FilePath: "package.json", 172 Root: resolve.Version{ 173 VersionKey: makeVK(t, "npm-workspace-test", "1.0.0", resolve.Concrete), 174 }, 175 System: resolve.NPM, 176 Requirements: []resolve.RequirementVersion{ 177 // root dependencies always before workspace 178 { 179 Type: aliasType(t, "jquery-real"), 180 VersionKey: makeVK(t, "jquery", "^3.7.1", resolve.Requirement), 181 }, 182 // workspaces in path order 183 { 184 VersionKey: makeVK(t, "jquery:workspace", "^3.7.1", resolve.Requirement), 185 }, 186 { 187 VersionKey: makeVK(t, "@workspace/ugh:workspace", "*", resolve.Requirement), 188 }, 189 { 190 VersionKey: makeVK(t, "z-z-z:workspace", "*", resolve.Requirement), 191 }, 192 }, 193 Groups: map[manifest.RequirementKey][]string{ 194 makeReqKey(t, "jquery", "jquery-real"): {"dev"}, 195 // excludes workspace dev dependency 196 }, 197 LocalManifests: []testManifest{ 198 { 199 FilePath: "ws/jquery/package.json", 200 Root: resolve.Version{ 201 VersionKey: makeVK(t, "jquery:workspace", "3.7.1", resolve.Concrete), 202 }, 203 System: resolve.NPM, 204 Requirements: []resolve.RequirementVersion{ 205 { 206 VersionKey: makeVK(t, "semver", "^7.6.0", resolve.Requirement), 207 }, 208 }, 209 Groups: map[manifest.RequirementKey][]string{}, 210 }, 211 { 212 FilePath: "ws/ugh/package.json", 213 Root: resolve.Version{ 214 VersionKey: makeVK(t, "@workspace/ugh:workspace", "0.0.1", resolve.Concrete), 215 }, 216 System: resolve.NPM, 217 Requirements: []resolve.RequirementVersion{ 218 { 219 VersionKey: makeVK(t, "jquery:workspace", "*", resolve.Requirement), 220 }, 221 { 222 VersionKey: makeVK(t, "semver", "^6.3.1", resolve.Requirement), 223 }, 224 }, 225 Groups: map[manifest.RequirementKey][]string{ 226 makeReqKey(t, "jquery:workspace", ""): {"dev"}, 227 makeReqKey(t, "semver", ""): {"dev"}, 228 }, 229 }, 230 { 231 FilePath: "z/package.json", 232 Root: resolve.Version{ 233 VersionKey: makeVK(t, "z-z-z:workspace", "1.0.0", resolve.Concrete), 234 }, 235 System: resolve.NPM, 236 Requirements: []resolve.RequirementVersion{ 237 { 238 VersionKey: makeVK(t, "@workspace/ugh:workspace", "*", resolve.Requirement), 239 }, 240 { 241 VersionKey: makeVK(t, "semver", "^5.7.2", resolve.Requirement), 242 }, 243 }, 244 Groups: map[manifest.RequirementKey][]string{}, 245 }, 246 }, 247 } 248 249 checkManifest(t, "Manifest", got, want) 250 } 251 252 func TestWrite(t *testing.T) { 253 rw, err := npm.GetReadWriter() 254 if err != nil { 255 t.Fatalf("error creating ReadWriter: %v", err) 256 } 257 fsys := scalibrfs.DirFS("./testdata") 258 manif, err := rw.Read("package.json", fsys) 259 if err != nil { 260 t.Fatalf("error reading manifest: %v", err) 261 } 262 263 patches := []result.Patch{ 264 { 265 PackageUpdates: []result.PackageUpdate{ 266 { 267 Name: "lodash", 268 VersionFrom: "4.17.17", 269 VersionTo: "^4.17.21", 270 }, 271 { 272 Name: "eslint", 273 VersionFrom: "^8.57.0", 274 VersionTo: "*", 275 }, 276 { 277 Name: "glob", 278 VersionFrom: "^10.3.10", 279 VersionTo: "^1.0.0", 280 }, 281 { 282 Name: "jquery", 283 VersionFrom: "latest", 284 VersionTo: "~0.0.1", 285 }, 286 }, 287 }, 288 { 289 PackageUpdates: []result.PackageUpdate{ 290 { 291 Name: "@isaacs/cliui", 292 VersionFrom: "^8.0.2", 293 VersionTo: "^9.0.0", 294 Type: aliasType(t, "cliui"), 295 }, 296 { 297 Name: "string-width", 298 VersionFrom: "^5.1.2", 299 VersionTo: "^7.1.0", 300 }, 301 { 302 Name: "string-width", 303 VersionFrom: "^4.2.3", 304 VersionTo: "^6.1.0", 305 Type: aliasType(t, "string-width-aliased"), 306 }, 307 }, 308 }, 309 } 310 outDir := t.TempDir() 311 outFile := filepath.Join(outDir, "package.json") 312 313 if err := rw.Write(manif, fsys, patches, outFile); err != nil { 314 t.Fatalf("failed to write package.json: %v", err) 315 } 316 317 got, err := os.ReadFile(outFile) 318 if err != nil { 319 t.Fatalf("failed to read got package.json: %v", err) 320 } 321 want, err := os.ReadFile(filepath.Join("./testdata", "write_want.package.json")) 322 if err != nil { 323 t.Fatalf("failed to read want package.json: %v", err) 324 } 325 if diff := cmp.Diff(want, got); diff != "" { 326 t.Errorf("package.json (-want +got):\n%s", diff) 327 } 328 }