github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/python/pdmlock/pdmlock_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 pdmlock_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/pdmlock" 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 TestPdmExtractor_FileRequired(t *testing.T) { 32 tests := []struct { 33 name string 34 inputPath string 35 want bool 36 }{ 37 { 38 name: "empty", 39 inputPath: "", 40 want: false, 41 }, 42 { 43 name: "plain", 44 inputPath: "pdm.lock", 45 want: true, 46 }, 47 { 48 name: "absolute", 49 inputPath: "/path/to/pdm.lock", 50 want: true, 51 }, 52 { 53 name: "relative", 54 inputPath: "../../pdm.lock", 55 want: true, 56 }, 57 { 58 name: "in-path", 59 inputPath: "/path/with/pdm.lock/in/middle", 60 want: false, 61 }, 62 { 63 name: "invalid-suffix", 64 inputPath: "pdm.lock.file", 65 want: false, 66 }, 67 { 68 name: "invalid-prefix", 69 inputPath: "project.name.pdm.lock", 70 want: false, 71 }, 72 } 73 74 for _, tt := range tests { 75 t.Run(tt.name, func(t *testing.T) { 76 e := pdmlock.Extractor{} 77 got := e.FileRequired(simplefileapi.New(tt.inputPath, nil)) 78 if got != tt.want { 79 t.Errorf("FileRequired(%q, FileInfo) got = %v, want %v", tt.inputPath, got, tt.want) 80 } 81 }) 82 } 83 } 84 85 func TestExtractor_Extract(t *testing.T) { 86 tests := []extracttest.TestTableEntry{ 87 { 88 Name: "invalid toml", 89 InputConfig: extracttest.ScanInputMockConfig{ 90 Path: "testdata/not-toml.txt", 91 }, 92 WantErr: extracttest.ContainsErrStr{Str: "could not extract"}, 93 WantPackages: nil, 94 }, 95 { 96 Name: "no packages", 97 InputConfig: extracttest.ScanInputMockConfig{ 98 Path: "testdata/empty.toml", 99 }, 100 WantPackages: []*extractor.Package{}, 101 }, 102 { 103 Name: "single package", 104 InputConfig: extracttest.ScanInputMockConfig{ 105 Path: "testdata/single-package.toml", 106 }, 107 WantPackages: []*extractor.Package{ 108 { 109 Name: "toml", 110 Version: "0.10.2", 111 PURLType: purl.TypePyPi, 112 Locations: []string{"testdata/single-package.toml"}, 113 Metadata: osv.DepGroupMetadata{ 114 DepGroupVals: []string{}, 115 }, 116 }, 117 }, 118 }, 119 { 120 Name: "two packages", 121 InputConfig: extracttest.ScanInputMockConfig{ 122 Path: "testdata/two-packages.toml", 123 }, 124 WantPackages: []*extractor.Package{ 125 { 126 Name: "toml", 127 Version: "0.10.2", 128 PURLType: purl.TypePyPi, 129 Locations: []string{"testdata/two-packages.toml"}, 130 Metadata: osv.DepGroupMetadata{ 131 DepGroupVals: []string{}, 132 }, 133 }, 134 { 135 Name: "six", 136 Version: "1.16.0", 137 PURLType: purl.TypePyPi, 138 Locations: []string{"testdata/two-packages.toml"}, 139 Metadata: osv.DepGroupMetadata{ 140 DepGroupVals: []string{}, 141 }, 142 }, 143 }, 144 }, 145 { 146 Name: "package with dev dependencies", 147 InputConfig: extracttest.ScanInputMockConfig{ 148 Path: "testdata/dev-dependency.toml", 149 }, 150 WantPackages: []*extractor.Package{ 151 { 152 Name: "toml", 153 Version: "0.10.2", 154 PURLType: purl.TypePyPi, 155 Locations: []string{"testdata/dev-dependency.toml"}, 156 Metadata: osv.DepGroupMetadata{ 157 DepGroupVals: []string{}, 158 }, 159 }, 160 { 161 Name: "pyroute2", 162 Version: "0.7.11", 163 PURLType: purl.TypePyPi, 164 Locations: []string{"testdata/dev-dependency.toml"}, 165 Metadata: osv.DepGroupMetadata{ 166 DepGroupVals: []string{"dev"}, 167 }, 168 }, 169 { 170 Name: "win-inet-pton", 171 Version: "1.1.0", 172 PURLType: purl.TypePyPi, 173 Locations: []string{"testdata/dev-dependency.toml"}, 174 Metadata: osv.DepGroupMetadata{ 175 DepGroupVals: []string{"dev"}, 176 }, 177 }, 178 }, 179 }, 180 { 181 Name: "package with optional dependency", 182 InputConfig: extracttest.ScanInputMockConfig{ 183 Path: "testdata/optional-dependency.toml", 184 }, 185 WantPackages: []*extractor.Package{ 186 { 187 Name: "toml", 188 Version: "0.10.2", 189 PURLType: purl.TypePyPi, 190 Locations: []string{"testdata/optional-dependency.toml"}, 191 Metadata: osv.DepGroupMetadata{ 192 DepGroupVals: []string{}, 193 }, 194 }, 195 { 196 Name: "pyroute2", 197 Version: "0.7.11", 198 PURLType: purl.TypePyPi, 199 Locations: []string{"testdata/optional-dependency.toml"}, 200 Metadata: osv.DepGroupMetadata{ 201 DepGroupVals: []string{"optional"}, 202 }, 203 }, 204 { 205 Name: "win-inet-pton", 206 Version: "1.1.0", 207 PURLType: purl.TypePyPi, 208 Locations: []string{"testdata/optional-dependency.toml"}, 209 Metadata: osv.DepGroupMetadata{ 210 DepGroupVals: []string{"optional"}, 211 }, 212 }, 213 }, 214 }, 215 { 216 Name: "package with git dependency", 217 InputConfig: extracttest.ScanInputMockConfig{ 218 Path: "testdata/git-dependency.toml", 219 }, 220 WantPackages: []*extractor.Package{ 221 { 222 Name: "toml", 223 Version: "0.10.2", 224 PURLType: purl.TypePyPi, 225 Locations: []string{"testdata/git-dependency.toml"}, 226 Metadata: osv.DepGroupMetadata{ 227 DepGroupVals: []string{}, 228 }, 229 SourceCode: &extractor.SourceCodeIdentifier{ 230 Commit: "65bab7582ce14c55cdeec2244c65ea23039c9e6f", 231 }, 232 }, 233 }, 234 }, 235 } 236 237 for _, tt := range tests { 238 t.Run(tt.Name, func(t *testing.T) { 239 extr := pdmlock.Extractor{} 240 241 scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig) 242 defer extracttest.CloseTestScanInput(t, scanInput) 243 244 got, err := extr.Extract(t.Context(), &scanInput) 245 246 if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" { 247 t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 248 return 249 } 250 251 wantInv := inventory.Inventory{Packages: tt.WantPackages} 252 if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" { 253 t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 254 } 255 }) 256 } 257 }