github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/php/composerlock/composerlock_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 composerlock_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/php/composerlock" 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 TestExtractor_FileRequired(t *testing.T) { 32 tests := []struct { 33 name string 34 inputPath string 35 want bool 36 }{ 37 { 38 name: "empty name", 39 inputPath: "", 40 want: false, 41 }, 42 { 43 name: "composer.lock from root", 44 inputPath: "composer.lock", 45 want: true, 46 }, 47 { 48 name: "composer.lock from subpath", 49 inputPath: "path/to/my/composer.lock", 50 want: true, 51 }, 52 { 53 name: "composer.lock as a dir", 54 inputPath: "path/to/my/composer.lock/file", 55 want: false, 56 }, 57 { 58 name: "composer.lock with additional extension", 59 inputPath: "path/to/my/composer.lock.file", 60 want: false, 61 }, 62 { 63 name: "composer.lock as substring", 64 inputPath: "path.to.my.composer.lock", 65 want: false, 66 }, 67 } 68 for _, tt := range tests { 69 t.Run(tt.name, func(t *testing.T) { 70 e := composerlock.Extractor{} 71 got := e.FileRequired(simplefileapi.New(tt.inputPath, nil)) 72 if got != tt.want { 73 t.Errorf("FileRequired(%s, FileInfo) got = %v, want %v", tt.inputPath, got, tt.want) 74 } 75 }) 76 } 77 } 78 79 func TestExtractor_Extract(t *testing.T) { 80 tests := []extracttest.TestTableEntry{ 81 { 82 Name: "invalid json", 83 InputConfig: extracttest.ScanInputMockConfig{ 84 Path: "testdata/not-json.txt", 85 }, 86 WantPackages: nil, 87 WantErr: extracttest.ContainsErrStr{Str: "could not extract"}, 88 }, 89 { 90 Name: "no packages", 91 InputConfig: extracttest.ScanInputMockConfig{ 92 Path: "testdata/empty.json", 93 }, 94 WantPackages: []*extractor.Package{}, 95 }, 96 { 97 Name: "one package", 98 InputConfig: extracttest.ScanInputMockConfig{ 99 Path: "testdata/one-package.json", 100 }, 101 WantPackages: []*extractor.Package{ 102 { 103 Name: "sentry/sdk", 104 Version: "2.0.4", 105 PURLType: purl.TypeComposer, 106 Locations: []string{"testdata/one-package.json"}, 107 SourceCode: &extractor.SourceCodeIdentifier{ 108 Commit: "4c115873c86ad5bd0ac6d962db70ca53bf8fb874", 109 }, 110 Metadata: osv.DepGroupMetadata{ 111 DepGroupVals: []string{}, 112 }, 113 }, 114 }, 115 }, 116 { 117 Name: "one package dev", 118 InputConfig: extracttest.ScanInputMockConfig{ 119 Path: "testdata/one-package-dev.json", 120 }, 121 WantPackages: []*extractor.Package{ 122 { 123 Name: "sentry/sdk", 124 Version: "2.0.4", 125 PURLType: purl.TypeComposer, 126 Locations: []string{"testdata/one-package-dev.json"}, 127 SourceCode: &extractor.SourceCodeIdentifier{ 128 Commit: "4c115873c86ad5bd0ac6d962db70ca53bf8fb874", 129 }, 130 Metadata: osv.DepGroupMetadata{ 131 DepGroupVals: []string{"dev"}, 132 }, 133 }, 134 }, 135 }, 136 { 137 Name: "two packages", 138 InputConfig: extracttest.ScanInputMockConfig{ 139 Path: "testdata/two-packages.json", 140 }, 141 WantPackages: []*extractor.Package{ 142 { 143 Name: "sentry/sdk", 144 Version: "2.0.4", 145 PURLType: purl.TypeComposer, 146 Locations: []string{"testdata/two-packages.json"}, 147 SourceCode: &extractor.SourceCodeIdentifier{ 148 Commit: "4c115873c86ad5bd0ac6d962db70ca53bf8fb874", 149 }, 150 Metadata: osv.DepGroupMetadata{ 151 DepGroupVals: []string{}, 152 }, 153 }, 154 { 155 Name: "theseer/tokenizer", 156 Version: "1.1.3", 157 PURLType: purl.TypeComposer, 158 Locations: []string{"testdata/two-packages.json"}, 159 SourceCode: &extractor.SourceCodeIdentifier{ 160 Commit: "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", 161 }, 162 Metadata: osv.DepGroupMetadata{ 163 DepGroupVals: []string{"dev"}, 164 }, 165 }, 166 }, 167 }, 168 { 169 Name: "two packages alt", 170 InputConfig: extracttest.ScanInputMockConfig{ 171 Path: "testdata/two-packages-alt.json", 172 }, 173 WantPackages: []*extractor.Package{ 174 { 175 Name: "sentry/sdk", 176 Version: "2.0.4", 177 PURLType: purl.TypeComposer, 178 Locations: []string{"testdata/two-packages-alt.json"}, 179 SourceCode: &extractor.SourceCodeIdentifier{ 180 Commit: "4c115873c86ad5bd0ac6d962db70ca53bf8fb874", 181 }, 182 Metadata: osv.DepGroupMetadata{ 183 DepGroupVals: []string{}, 184 }, 185 }, 186 { 187 Name: "theseer/tokenizer", 188 Version: "1.1.3", 189 PURLType: purl.TypeComposer, 190 Locations: []string{"testdata/two-packages-alt.json"}, 191 SourceCode: &extractor.SourceCodeIdentifier{ 192 Commit: "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", 193 }, 194 Metadata: osv.DepGroupMetadata{ 195 DepGroupVals: []string{}, 196 }, 197 }, 198 }, 199 }, 200 { 201 Name: "drupal_packages", 202 InputConfig: extracttest.ScanInputMockConfig{ 203 Path: "testdata/drupal-packages.json", 204 }, 205 WantPackages: []*extractor.Package{ 206 { 207 Name: "drupal/core", 208 Version: "10.4.5", 209 PURLType: purl.TypeComposer, 210 Locations: []string{"testdata/drupal-packages.json"}, 211 SourceCode: &extractor.SourceCodeIdentifier{ 212 Commit: "5247dbaa65b42b601058555f4a8b2bd541f5611f", 213 }, 214 Metadata: osv.DepGroupMetadata{ 215 DepGroupVals: []string{}, 216 }, 217 }, 218 { 219 Name: "drupal/tfa", 220 Version: "2.0.0-alpha4", 221 PURLType: purl.TypeComposer, 222 Locations: []string{"testdata/drupal-packages.json"}, 223 SourceCode: &extractor.SourceCodeIdentifier{ 224 Commit: "", 225 }, 226 Metadata: osv.DepGroupMetadata{ 227 DepGroupVals: []string{}, 228 }, 229 }, 230 { 231 Name: "drupal/field_time", 232 Version: "1.0.0-beta5", 233 PURLType: purl.TypeComposer, 234 Locations: []string{"testdata/drupal-packages.json"}, 235 SourceCode: &extractor.SourceCodeIdentifier{ 236 Commit: "", 237 }, 238 Metadata: osv.DepGroupMetadata{ 239 DepGroupVals: []string{"dev"}, 240 }, 241 }, 242 { 243 Name: "theseer/tokenizer", 244 Version: "1.1.3", 245 PURLType: purl.TypeComposer, 246 Locations: []string{"testdata/drupal-packages.json"}, 247 SourceCode: &extractor.SourceCodeIdentifier{ 248 Commit: "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", 249 }, 250 Metadata: osv.DepGroupMetadata{ 251 DepGroupVals: []string{}, 252 }, 253 }, 254 }, 255 }, 256 } 257 258 for _, tt := range tests { 259 t.Run(tt.Name, func(t *testing.T) { 260 extr := composerlock.Extractor{} 261 262 scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig) 263 defer extracttest.CloseTestScanInput(t, scanInput) 264 265 got, err := extr.Extract(t.Context(), &scanInput) 266 267 if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" { 268 t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 269 return 270 } 271 272 wantInv := inventory.Inventory{Packages: tt.WantPackages} 273 if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" { 274 t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 275 } 276 }) 277 } 278 }