github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/dotnet/packageslockjson/packageslockjson_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 packageslockjson_test 16 17 import ( 18 "io/fs" 19 "os" 20 "path/filepath" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 "github.com/google/go-cmp/cmp/cmpopts" 25 "github.com/google/osv-scalibr/extractor" 26 "github.com/google/osv-scalibr/extractor/filesystem" 27 "github.com/google/osv-scalibr/extractor/filesystem/internal/units" 28 "github.com/google/osv-scalibr/extractor/filesystem/language/dotnet/packageslockjson" 29 "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" 30 scalibrfs "github.com/google/osv-scalibr/fs" 31 "github.com/google/osv-scalibr/inventory" 32 "github.com/google/osv-scalibr/purl" 33 "github.com/google/osv-scalibr/stats" 34 "github.com/google/osv-scalibr/testing/fakefs" 35 "github.com/google/osv-scalibr/testing/testcollector" 36 ) 37 38 func TestFileRequired(t *testing.T) { 39 tests := []struct { 40 name string 41 path string 42 fileSizeBytes int64 43 maxFileSizeBytes int64 44 wantRequired bool 45 wantResultMetric stats.FileRequiredResult 46 }{ 47 { 48 name: "some project's packages.lock.json", 49 path: "project/packages.lock.json", 50 wantRequired: true, 51 wantResultMetric: stats.FileRequiredResultOK, 52 }, 53 { 54 name: "just packages.lock.json", 55 path: "packages.lock.json", 56 wantRequired: true, 57 wantResultMetric: stats.FileRequiredResultOK, 58 }, 59 { 60 name: "non packages.lock.json", 61 path: "project/some.csproj", 62 wantRequired: false, 63 }, 64 { 65 name: "packages.lock.json required if file size < max file size", 66 path: "project/packages.lock.json", 67 fileSizeBytes: 100 * units.KiB, 68 maxFileSizeBytes: 1000 * units.KiB, 69 wantRequired: true, 70 wantResultMetric: stats.FileRequiredResultOK, 71 }, 72 { 73 name: "packages.lock.json required if file size == max file size", 74 path: "project/packages.lock.json", 75 fileSizeBytes: 1000 * units.KiB, 76 maxFileSizeBytes: 1000 * units.KiB, 77 wantRequired: true, 78 wantResultMetric: stats.FileRequiredResultOK, 79 }, 80 { 81 name: "packages.lock.json not required if file size > max file size", 82 path: "project/packages.lock.json", 83 fileSizeBytes: 1000 * units.KiB, 84 maxFileSizeBytes: 100 * units.KiB, 85 wantRequired: false, 86 wantResultMetric: stats.FileRequiredResultSizeLimitExceeded, 87 }, 88 { 89 name: "packages.lock.json required if max file size set to 0", 90 path: "project/packages.lock.json", 91 fileSizeBytes: 1000 * units.KiB, 92 maxFileSizeBytes: 0, 93 wantRequired: true, 94 wantResultMetric: stats.FileRequiredResultOK, 95 }, 96 } 97 98 for _, test := range tests { 99 t.Run(test.name, func(t *testing.T) { 100 collector := testcollector.New() 101 var e filesystem.Extractor = packageslockjson.New( 102 packageslockjson.Config{ 103 Stats: collector, 104 MaxFileSizeBytes: test.maxFileSizeBytes, 105 }, 106 ) 107 108 // Set default size if not provided. 109 fileSizeBytes := test.fileSizeBytes 110 if fileSizeBytes == 0 { 111 fileSizeBytes = 100 * units.KiB 112 } 113 114 isRequired := e.FileRequired(simplefileapi.New(test.path, fakefs.FakeFileInfo{ 115 FileName: filepath.Base(test.path), 116 FileMode: fs.ModePerm, 117 FileSize: fileSizeBytes, 118 })) 119 if isRequired != test.wantRequired { 120 t.Fatalf("FileRequired(%s): got %v, want %v", test.path, isRequired, test.wantRequired) 121 } 122 123 gotResultMetric := collector.FileRequiredResult(test.path) 124 if gotResultMetric != test.wantResultMetric { 125 t.Errorf("FileRequired(%s) recorded result metric %v, want result metric %v", test.path, gotResultMetric, test.wantResultMetric) 126 } 127 }) 128 } 129 } 130 131 func TestExtractor(t *testing.T) { 132 tests := []struct { 133 name string 134 path string 135 wantPackages []*extractor.Package 136 wantErr error 137 wantResultMetric stats.FileExtractedResult 138 }{ 139 { 140 name: "valid_packages.lock.json", 141 path: "testdata/valid/packages.lock.json", 142 wantPackages: []*extractor.Package{ 143 { 144 Name: "Core.Dep", 145 Version: "1.24.0", 146 PURLType: purl.TypeNuget, 147 Locations: []string{"testdata/valid/packages.lock.json"}, 148 }, 149 { 150 Name: "Some.Dep.One", 151 Version: "1.1.1", 152 PURLType: purl.TypeNuget, 153 Locations: []string{"testdata/valid/packages.lock.json"}, 154 }, 155 { 156 Name: "Some.Dep.Two", 157 Version: "4.6.0", 158 PURLType: purl.TypeNuget, 159 Locations: []string{"testdata/valid/packages.lock.json"}, 160 }, 161 { 162 Name: "Some.Dep.Three", 163 Version: "1.0.2", 164 PURLType: purl.TypeNuget, 165 Locations: []string{"testdata/valid/packages.lock.json"}, 166 }, 167 { 168 Name: "Some.Dep.Four", 169 Version: "4.5.0", 170 PURLType: purl.TypeNuget, 171 Locations: []string{"testdata/valid/packages.lock.json"}, 172 }, 173 { 174 Name: "Some.Longer.Name.Dep", 175 Version: "4.7.2", 176 PURLType: purl.TypeNuget, 177 Locations: []string{"testdata/valid/packages.lock.json"}, 178 }, 179 { 180 Name: "Some.Dep.Five", 181 Version: "4.7.2", 182 PURLType: purl.TypeNuget, 183 Locations: []string{"testdata/valid/packages.lock.json"}, 184 }, 185 { 186 Name: "Another.Longer.Name.Dep", 187 Version: "4.5.4", 188 PURLType: purl.TypeNuget, 189 Locations: []string{"testdata/valid/packages.lock.json"}, 190 }, 191 }, 192 wantResultMetric: stats.FileExtractedResultSuccess, 193 }, 194 { 195 name: "non json input", 196 path: "testdata/invalid/invalid", 197 wantErr: cmpopts.AnyError, 198 wantResultMetric: stats.FileExtractedResultErrorUnknown, 199 }, 200 } 201 202 for _, test := range tests { 203 t.Run(test.name, func(t *testing.T) { 204 collector := testcollector.New() 205 var e filesystem.Extractor = packageslockjson.New(packageslockjson.Config{Stats: collector}) 206 207 r, err := os.Open(test.path) 208 defer func() { 209 if err = r.Close(); err != nil { 210 t.Errorf("Close(): %v", err) 211 } 212 }() 213 if err != nil { 214 t.Fatal(err) 215 } 216 217 info, err := os.Stat(test.path) 218 if err != nil { 219 t.Fatalf("Failed to stat test file: %v", err) 220 } 221 222 input := &filesystem.ScanInput{ 223 FS: scalibrfs.DirFS("."), 224 Path: test.path, 225 Reader: r, 226 Info: info, 227 } 228 got, err := e.Extract(t.Context(), input) 229 if !cmp.Equal(err, test.wantErr, cmpopts.EquateErrors()) { 230 t.Fatalf("Extract(%+v) error: got %v, want %v\n", test.name, err, test.wantErr) 231 } 232 233 sort := func(a, b *extractor.Package) bool { return a.Name < b.Name } 234 wantInv := inventory.Inventory{Packages: test.wantPackages} 235 if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(sort)); diff != "" { 236 t.Errorf("Extract(%s) (-want +got):\n%s", test.path, diff) 237 } 238 239 gotResultMetric := collector.FileExtractedResult(test.path) 240 if gotResultMetric != test.wantResultMetric { 241 t.Errorf("Extract(%s) recorded result metric %v, want result metric %v", test.path, gotResultMetric, test.wantResultMetric) 242 } 243 244 gotFileSizeMetric := collector.FileExtractedFileSize(test.path) 245 if gotFileSizeMetric != info.Size() { 246 t.Errorf("Extract(%s) recorded file size %v, want file size %v", test.path, gotFileSizeMetric, info.Size()) 247 } 248 }) 249 } 250 }