github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/java/pomxmlnet/pomxmlnet_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 pomxmlnet_test 16 17 import ( 18 "testing" 19 20 "github.com/google/go-cmp/cmp" 21 "github.com/google/go-cmp/cmp/cmpopts" 22 cpb "github.com/google/osv-scalibr/binary/proto/config_go_proto" 23 "github.com/google/osv-scalibr/clients/clienttest" 24 "github.com/google/osv-scalibr/clients/datasource" 25 "github.com/google/osv-scalibr/extractor" 26 "github.com/google/osv-scalibr/extractor/filesystem/language/java/javalockfile" 27 "github.com/google/osv-scalibr/extractor/filesystem/language/java/pomxmlnet" 28 "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" 29 "github.com/google/osv-scalibr/inventory" 30 "github.com/google/osv-scalibr/purl" 31 "github.com/google/osv-scalibr/testing/extracttest" 32 ) 33 34 func TestMavenResolverExtractor_FileRequired(t *testing.T) { 35 tests := []struct { 36 path string 37 want bool 38 }{ 39 { 40 path: "", 41 want: false, 42 }, 43 { 44 path: "pom.xml", 45 want: true, 46 }, 47 { 48 path: "path/to/my/pom.xml", 49 want: true, 50 }, 51 { 52 path: "path/to/my/pom.xml/file", 53 want: false, 54 }, 55 { 56 path: "path/to/my/pom.xml.file", 57 want: false, 58 }, 59 { 60 path: "path.to.my.pom.xml", 61 want: false, 62 }, 63 } 64 for _, tt := range tests { 65 t.Run(tt.path, func(t *testing.T) { 66 e := pomxmlnet.Extractor{} 67 got := e.FileRequired(simplefileapi.New(tt.path, nil)) 68 if got != tt.want { 69 t.Errorf("Extract() got = %v, want %v", got, tt.want) 70 } 71 }) 72 } 73 } 74 75 func TestExtractor_Extract(t *testing.T) { 76 tests := []extracttest.TestTableEntry{ 77 { 78 Name: "Not a pom file", 79 InputConfig: extracttest.ScanInputMockConfig{ 80 Path: "testdata/maven/not-pom.txt", 81 }, 82 WantErr: extracttest.ContainsErrStr{Str: "could not extract"}, 83 }, 84 { 85 Name: "invalid xml syntax", 86 InputConfig: extracttest.ScanInputMockConfig{ 87 Path: "testdata/maven/invalid-syntax.xml", 88 }, 89 WantErr: extracttest.ContainsErrStr{Str: "XML syntax error"}, 90 }, 91 { 92 Name: "empty", 93 InputConfig: extracttest.ScanInputMockConfig{ 94 Path: "testdata/maven/empty.xml", 95 }, 96 WantPackages: nil, 97 }, 98 { 99 Name: "one package", 100 InputConfig: extracttest.ScanInputMockConfig{ 101 Path: "testdata/maven/one-package.xml", 102 }, 103 WantPackages: []*extractor.Package{ 104 { 105 Name: "org.apache.maven:maven-artifact", 106 Version: "1.0.0", 107 PURLType: purl.TypeMaven, 108 Locations: []string{"testdata/maven/one-package.xml"}, 109 Metadata: &javalockfile.Metadata{ 110 ArtifactID: "maven-artifact", 111 GroupID: "org.apache.maven", 112 IsTransitive: false, 113 DepGroupVals: []string{}, 114 }, 115 }, 116 }, 117 }, 118 { 119 Name: "two packages", 120 InputConfig: extracttest.ScanInputMockConfig{ 121 Path: "testdata/maven/two-packages.xml", 122 }, 123 WantPackages: []*extractor.Package{ 124 { 125 Name: "io.netty:netty-all", 126 Version: "4.1.42.Final", 127 PURLType: purl.TypeMaven, 128 Locations: []string{"testdata/maven/two-packages.xml"}, 129 Metadata: &javalockfile.Metadata{ 130 ArtifactID: "netty-all", 131 GroupID: "io.netty", 132 IsTransitive: false, 133 DepGroupVals: []string{}, 134 }, 135 }, 136 { 137 Name: "org.slf4j:slf4j-log4j12", 138 Version: "1.7.25", 139 PURLType: purl.TypeMaven, 140 Locations: []string{"testdata/maven/two-packages.xml"}, 141 Metadata: &javalockfile.Metadata{ 142 ArtifactID: "slf4j-log4j12", 143 GroupID: "org.slf4j", 144 IsTransitive: false, 145 DepGroupVals: []string{}, 146 }, 147 }, 148 }, 149 }, 150 { 151 Name: "with dependency management", 152 InputConfig: extracttest.ScanInputMockConfig{ 153 Path: "testdata/maven/with-dependency-management.xml", 154 }, 155 WantPackages: []*extractor.Package{ 156 { 157 Name: "io.netty:netty-all", 158 Version: "4.1.9", 159 PURLType: purl.TypeMaven, 160 Locations: []string{"testdata/maven/with-dependency-management.xml"}, 161 Metadata: &javalockfile.Metadata{ 162 ArtifactID: "netty-all", 163 GroupID: "io.netty", 164 IsTransitive: false, 165 DepGroupVals: []string{}, 166 }, 167 }, 168 { 169 Name: "org.slf4j:slf4j-log4j12", 170 Version: "1.7.25", 171 PURLType: purl.TypeMaven, 172 Locations: []string{"testdata/maven/with-dependency-management.xml"}, 173 Metadata: &javalockfile.Metadata{ 174 ArtifactID: "slf4j-log4j12", 175 GroupID: "org.slf4j", 176 IsTransitive: false, 177 DepGroupVals: []string{}, 178 }, 179 }, 180 }, 181 }, 182 { 183 Name: "interpolation", 184 InputConfig: extracttest.ScanInputMockConfig{ 185 Path: "testdata/maven/interpolation.xml", 186 }, 187 WantPackages: []*extractor.Package{ 188 { 189 Name: "org.mine:mypackage", 190 Version: "1.0.0", 191 PURLType: purl.TypeMaven, 192 Locations: []string{"testdata/maven/interpolation.xml"}, 193 Metadata: &javalockfile.Metadata{ 194 ArtifactID: "mypackage", 195 GroupID: "org.mine", 196 IsTransitive: false, 197 DepGroupVals: []string{}, 198 }, 199 }, 200 { 201 Name: "org.mine:my.package", 202 Version: "2.3.4", 203 PURLType: purl.TypeMaven, 204 Locations: []string{"testdata/maven/interpolation.xml"}, 205 Metadata: &javalockfile.Metadata{ 206 ArtifactID: "my.package", 207 GroupID: "org.mine", 208 IsTransitive: false, 209 DepGroupVals: []string{}, 210 }, 211 }, 212 { 213 Name: "org.mine:ranged-package", 214 Version: "9.4.37", 215 PURLType: purl.TypeMaven, 216 Locations: []string{"testdata/maven/interpolation.xml"}, 217 Metadata: &javalockfile.Metadata{ 218 ArtifactID: "ranged-package", 219 GroupID: "org.mine", 220 IsTransitive: false, 221 DepGroupVals: []string{}, 222 }, 223 }, 224 }, 225 }, 226 { 227 Name: "with scope / dep groups", 228 InputConfig: extracttest.ScanInputMockConfig{ 229 Path: "testdata/maven/with-scope.xml", 230 }, 231 WantPackages: []*extractor.Package{ 232 { 233 Name: "junit:junit", 234 Version: "4.12", 235 PURLType: purl.TypeMaven, 236 Locations: []string{"testdata/maven/with-scope.xml"}, 237 Metadata: &javalockfile.Metadata{ 238 ArtifactID: "junit", 239 GroupID: "junit", 240 IsTransitive: false, 241 DepGroupVals: []string{"runtime"}, 242 }, 243 }, 244 }, 245 }, 246 { 247 Name: "transitive dependencies", 248 InputConfig: extracttest.ScanInputMockConfig{ 249 Path: "testdata/maven/transitive.xml", 250 }, 251 WantPackages: []*extractor.Package{ 252 { 253 Name: "org.direct:alice", 254 Version: "1.0.0", 255 PURLType: purl.TypeMaven, 256 Locations: []string{"testdata/maven/transitive.xml"}, 257 Metadata: &javalockfile.Metadata{ 258 ArtifactID: "alice", 259 GroupID: "org.direct", 260 IsTransitive: false, 261 DepGroupVals: []string{}, 262 }, 263 }, 264 { 265 Name: "org.direct:bob", 266 Version: "2.0.0", 267 PURLType: purl.TypeMaven, 268 Locations: []string{"testdata/maven/transitive.xml"}, 269 Metadata: &javalockfile.Metadata{ 270 ArtifactID: "bob", 271 GroupID: "org.direct", 272 IsTransitive: false, 273 DepGroupVals: []string{}, 274 }, 275 }, 276 { 277 Name: "org.direct:chris", 278 Version: "3.0.0", 279 PURLType: purl.TypeMaven, 280 Locations: []string{"testdata/maven/transitive.xml"}, 281 Metadata: &javalockfile.Metadata{ 282 ArtifactID: "chris", 283 GroupID: "org.direct", 284 IsTransitive: false, 285 DepGroupVals: []string{}, 286 }, 287 }, 288 { 289 Name: "org.transitive:chuck", 290 Version: "1.1.1", 291 PURLType: purl.TypeMaven, 292 Locations: []string{"testdata/maven/transitive.xml"}, 293 Metadata: &javalockfile.Metadata{ 294 ArtifactID: "chuck", 295 GroupID: "org.transitive", 296 IsTransitive: true, 297 DepGroupVals: []string{}, 298 }, 299 }, 300 { 301 Name: "org.transitive:dave", 302 Version: "2.2.2", 303 PURLType: purl.TypeMaven, 304 Locations: []string{"testdata/maven/transitive.xml"}, 305 Metadata: &javalockfile.Metadata{ 306 ArtifactID: "dave", 307 GroupID: "org.transitive", 308 IsTransitive: true, 309 DepGroupVals: []string{}, 310 }, 311 }, 312 { 313 Name: "org.transitive:eve", 314 Version: "3.3.3", 315 PURLType: purl.TypeMaven, 316 Locations: []string{"testdata/maven/transitive.xml"}, 317 Metadata: &javalockfile.Metadata{ 318 ArtifactID: "eve", 319 GroupID: "org.transitive", 320 IsTransitive: true, 321 DepGroupVals: []string{}, 322 }, 323 }, 324 { 325 Name: "org.transitive:frank", 326 Version: "4.4.4", 327 PURLType: purl.TypeMaven, 328 Locations: []string{"testdata/maven/transitive.xml"}, 329 Metadata: &javalockfile.Metadata{ 330 ArtifactID: "frank", 331 GroupID: "org.transitive", 332 IsTransitive: true, 333 DepGroupVals: []string{}, 334 }, 335 }, 336 }, 337 }, 338 } 339 340 for _, tt := range tests { 341 t.Run(tt.Name, func(t *testing.T) { 342 resolutionClient := clienttest.NewMockResolutionClient(t, "testdata/universe/basic-universe.yaml") 343 extr := pomxmlnet.New(&cpb.PluginConfig{}) 344 extr.(*pomxmlnet.Extractor).DepClient = resolutionClient 345 346 scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig) 347 defer extracttest.CloseTestScanInput(t, scanInput) 348 349 got, err := extr.Extract(t.Context(), &scanInput) 350 351 if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" { 352 t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 353 return 354 } 355 356 wantInv := inventory.Inventory{Packages: tt.WantPackages} 357 if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" { 358 t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 359 } 360 }) 361 } 362 } 363 364 func TestExtractor_Extract_WithMockServer(t *testing.T) { 365 tt := extracttest.TestTableEntry{ 366 // Name: "with parent", 367 InputConfig: extracttest.ScanInputMockConfig{ 368 Path: "testdata/maven/with-parent.xml", 369 }, 370 WantPackages: []*extractor.Package{ 371 { 372 Name: "org.alice:alice", 373 Version: "1.0.0", 374 PURLType: purl.TypeMaven, 375 Locations: []string{"testdata/maven/with-parent.xml"}, 376 Metadata: &javalockfile.Metadata{ 377 ArtifactID: "alice", 378 GroupID: "org.alice", 379 IsTransitive: false, 380 DepGroupVals: []string{}, 381 }, 382 }, 383 { 384 Name: "org.bob:bob", 385 Version: "2.0.0", 386 PURLType: purl.TypeMaven, 387 Locations: []string{"testdata/maven/with-parent.xml"}, 388 Metadata: &javalockfile.Metadata{ 389 ArtifactID: "bob", 390 GroupID: "org.bob", 391 IsTransitive: false, 392 DepGroupVals: []string{}, 393 }, 394 }, 395 { 396 Name: "org.chuck:chuck", 397 Version: "3.0.0", 398 PURLType: purl.TypeMaven, 399 Locations: []string{"testdata/maven/with-parent.xml"}, 400 Metadata: &javalockfile.Metadata{ 401 ArtifactID: "chuck", 402 GroupID: "org.chuck", 403 IsTransitive: false, 404 DepGroupVals: []string{}, 405 }, 406 }, 407 { 408 Name: "org.dave:dave", 409 Version: "4.0.0", 410 PURLType: purl.TypeMaven, 411 Locations: []string{"testdata/maven/with-parent.xml"}, 412 Metadata: &javalockfile.Metadata{ 413 ArtifactID: "dave", 414 GroupID: "org.dave", 415 IsTransitive: false, 416 DepGroupVals: []string{}, 417 }, 418 }, 419 { 420 Name: "org.eve:eve", 421 Version: "5.0.0", 422 PURLType: purl.TypeMaven, 423 Locations: []string{"testdata/maven/with-parent.xml"}, 424 Metadata: &javalockfile.Metadata{ 425 ArtifactID: "eve", 426 GroupID: "org.eve", 427 IsTransitive: false, 428 DepGroupVals: []string{}, 429 }, 430 }, 431 { 432 Name: "org.frank:frank", 433 Version: "6.0.0", 434 PURLType: purl.TypeMaven, 435 Locations: []string{"testdata/maven/with-parent.xml"}, 436 Metadata: &javalockfile.Metadata{ 437 ArtifactID: "frank", 438 GroupID: "org.frank", 439 IsTransitive: false, 440 DepGroupVals: []string{}, 441 }, 442 }, 443 }, 444 } 445 446 srv := clienttest.NewMockHTTPServer(t) 447 srv.SetResponse(t, "org/upstream/parent-pom/1.0/parent-pom-1.0.pom", []byte(` 448 <project> 449 <groupId>org.upstream</groupId> 450 <artifactId>parent-pom</artifactId> 451 <version>1.0</version> 452 <packaging>pom</packaging> 453 <dependencies> 454 <dependency> 455 <groupId>org.eve</groupId> 456 <artifactId>eve</artifactId> 457 <version>5.0.0</version> 458 </dependency> 459 </dependencies> 460 </project> 461 `)) 462 srv.SetResponse(t, "org/import/import/1.2.3/import-1.2.3.pom", []byte(` 463 <project> 464 <groupId>org.import</groupId> 465 <artifactId>import</artifactId> 466 <version>1.2.3</version> 467 <packaging>pom</packaging> 468 <dependencyManagement> 469 <dependencies> 470 <dependency> 471 <groupId>org.frank</groupId> 472 <artifactId>frank</artifactId> 473 <version>6.0.0</version> 474 </dependency> 475 </dependencies> 476 </dependencyManagement> 477 </project> 478 `)) 479 480 apiClient, err := datasource.NewDefaultMavenRegistryAPIClient(t.Context(), srv.URL) 481 if err != nil { 482 t.Fatalf("%v", err) 483 } 484 485 resolutionClient := clienttest.NewMockResolutionClient(t, "testdata/universe/basic-universe.yaml") 486 extr := pomxmlnet.New(&cpb.PluginConfig{}) 487 extr.(*pomxmlnet.Extractor).DepClient = resolutionClient 488 extr.(*pomxmlnet.Extractor).MavenClient = apiClient 489 490 scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig) 491 defer extracttest.CloseTestScanInput(t, scanInput) 492 493 got, err := extr.Extract(t.Context(), &scanInput) 494 495 if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" { 496 t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 497 return 498 } 499 500 wantInv := inventory.Inventory{Packages: tt.WantPackages} 501 if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" { 502 t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 503 } 504 }