github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/python/uvlock/uvlock_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 uvlock_test 16 17 import ( 18 "testing" 19 20 "github.com/google/go-cmp/cmp" 21 "github.com/google/go-cmp/cmp/cmpopts" 22 "github.com/google/osv-scalibr/extractor" 23 "github.com/google/osv-scalibr/extractor/filesystem/language/python/uvlock" 24 "github.com/google/osv-scalibr/extractor/filesystem/osv" 25 "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" 26 "github.com/google/osv-scalibr/inventory" 27 "github.com/google/osv-scalibr/purl" 28 "github.com/google/osv-scalibr/testing/extracttest" 29 ) 30 31 func pkg(t *testing.T, name string, version string, location string) *extractor.Package { 32 t.Helper() 33 34 return &extractor.Package{ 35 Name: name, 36 Version: version, 37 PURLType: purl.TypePyPi, 38 Locations: []string{location}, 39 Metadata: osv.DepGroupMetadata{ 40 DepGroupVals: []string{}, 41 }, 42 } 43 } 44 45 func TestExtractor_FileRequired(t *testing.T) { 46 tests := []struct { 47 name string 48 inputPath string 49 want bool 50 }{ 51 { 52 name: "", 53 inputPath: "", 54 want: false, 55 }, 56 { 57 name: "", 58 inputPath: "uv.lock", 59 want: true, 60 }, 61 { 62 name: "", 63 inputPath: "path/to/my/uv.lock", 64 want: true, 65 }, 66 { 67 name: "", 68 inputPath: "path/to/my/uv.lock/file", 69 want: false, 70 }, 71 { 72 name: "", 73 inputPath: "path/to/my/uv.lock.file", 74 want: false, 75 }, 76 { 77 name: "", 78 inputPath: "path.to.my.uv.lock", 79 want: false, 80 }, 81 } 82 for _, tt := range tests { 83 t.Run(tt.name, func(t *testing.T) { 84 e := uvlock.Extractor{} 85 got := e.FileRequired(simplefileapi.New(tt.inputPath, nil)) 86 if got != tt.want { 87 t.Errorf("FileRequired(%q, FileInfo) got = %v, want %v", tt.inputPath, got, tt.want) 88 } 89 }) 90 } 91 } 92 93 func TestExtractor_Extract(t *testing.T) { 94 tests := []extracttest.TestTableEntry{ 95 { 96 Name: "invalid toml", 97 InputConfig: extracttest.ScanInputMockConfig{ 98 Path: "testdata/not-toml.txt", 99 }, 100 WantErr: extracttest.ContainsErrStr{Str: "could not extract"}, 101 WantPackages: nil, 102 }, 103 { 104 Name: "empty file", 105 InputConfig: extracttest.ScanInputMockConfig{ 106 Path: "testdata/empty.lock", 107 }, 108 WantPackages: []*extractor.Package{}, 109 }, 110 { 111 Name: "no dependencies", 112 InputConfig: extracttest.ScanInputMockConfig{ 113 Path: "testdata/empty.lock", 114 }, 115 WantPackages: []*extractor.Package{}, 116 }, 117 { 118 Name: "no packages", 119 InputConfig: extracttest.ScanInputMockConfig{ 120 Path: "testdata/empty.lock", 121 }, 122 WantPackages: []*extractor.Package{}, 123 }, 124 { 125 Name: "one package", 126 InputConfig: extracttest.ScanInputMockConfig{ 127 Path: "testdata/one-package.lock", 128 }, 129 WantPackages: []*extractor.Package{ 130 pkg(t, "emoji", "2.14.0", "testdata/one-package.lock"), 131 }, 132 }, 133 { 134 Name: "two packages", 135 InputConfig: extracttest.ScanInputMockConfig{ 136 Path: "testdata/two-packages.lock", 137 }, 138 WantPackages: []*extractor.Package{ 139 pkg(t, "emoji", "2.14.0", "testdata/two-packages.lock"), 140 pkg(t, "protobuf", "4.25.5", "testdata/two-packages.lock"), 141 }, 142 }, 143 { 144 Name: "source git", 145 InputConfig: extracttest.ScanInputMockConfig{ 146 Path: "testdata/source-git.lock", 147 }, 148 WantPackages: []*extractor.Package{ 149 { 150 Name: "ruff", 151 Version: "0.8.1", 152 PURLType: purl.TypePyPi, 153 Locations: []string{"testdata/source-git.lock"}, 154 SourceCode: &extractor.SourceCodeIdentifier{ 155 Commit: "84748be16341b76e073d117329f7f5f4ee2941ad", 156 }, 157 Metadata: osv.DepGroupMetadata{ 158 DepGroupVals: []string{}, 159 }, 160 }, 161 }, 162 }, 163 { 164 Name: "grouped packages", 165 InputConfig: extracttest.ScanInputMockConfig{ 166 Path: "testdata/grouped-packages.lock", 167 }, 168 WantPackages: []*extractor.Package{ 169 pkg(t, "emoji", "2.14.0", "testdata/grouped-packages.lock"), 170 { 171 Name: "click", 172 Version: "8.1.7", 173 PURLType: purl.TypePyPi, 174 Locations: []string{"testdata/grouped-packages.lock"}, 175 Metadata: osv.DepGroupMetadata{ 176 DepGroupVals: []string{"cli"}, 177 }, 178 }, 179 pkg(t, "colorama", "0.4.6", "testdata/grouped-packages.lock"), 180 { 181 Name: "black", 182 Version: "24.10.0", 183 PURLType: purl.TypePyPi, 184 Locations: []string{"testdata/grouped-packages.lock"}, 185 Metadata: osv.DepGroupMetadata{ 186 DepGroupVals: []string{"dev", "test"}, 187 }, 188 }, 189 { 190 Name: "flake8", 191 Version: "7.1.1", 192 PURLType: purl.TypePyPi, 193 Locations: []string{"testdata/grouped-packages.lock"}, 194 Metadata: osv.DepGroupMetadata{ 195 DepGroupVals: []string{"test"}, 196 }, 197 }, 198 pkg(t, "mccabe", "0.7.0", "testdata/grouped-packages.lock"), 199 pkg(t, "mypy-extensions", "1.0.0", "testdata/grouped-packages.lock"), 200 pkg(t, "packaging", "24.2", "testdata/grouped-packages.lock"), 201 pkg(t, "pathspec", "0.12.1", "testdata/grouped-packages.lock"), 202 pkg(t, "platformdirs", "4.3.6", "testdata/grouped-packages.lock"), 203 pkg(t, "pycodestyle", "2.12.1", "testdata/grouped-packages.lock"), 204 pkg(t, "pyflakes", "3.2.0", "testdata/grouped-packages.lock"), 205 pkg(t, "tomli", "2.2.1", "testdata/grouped-packages.lock"), 206 pkg(t, "typing-extensions", "4.12.2", "testdata/grouped-packages.lock"), 207 }, 208 }, 209 } 210 211 for _, tt := range tests { 212 t.Run(tt.Name, func(t *testing.T) { 213 extr := uvlock.Extractor{} 214 215 scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig) 216 defer extracttest.CloseTestScanInput(t, scanInput) 217 218 got, err := extr.Extract(t.Context(), &scanInput) 219 220 if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" { 221 t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 222 return 223 } 224 225 wantInv := inventory.Inventory{Packages: tt.WantPackages} 226 if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" { 227 t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 228 } 229 }) 230 } 231 }