github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/java/pomxml/pomxml_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 pomxml_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/java/javalockfile"
    24  	"github.com/google/osv-scalibr/extractor/filesystem/language/java/pomxml"
    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  		inputPath string
    34  		want      bool
    35  	}{
    36  		{
    37  			inputPath: "",
    38  			want:      false,
    39  		},
    40  		{
    41  			inputPath: "pom.xml",
    42  			want:      true,
    43  		},
    44  		{
    45  			inputPath: "my-app-1.0.pom",
    46  			want:      true,
    47  		},
    48  		{
    49  			inputPath: "path/to/my/pom.xml",
    50  			want:      true,
    51  		},
    52  		{
    53  			inputPath: "path/to/my/pom/my-app-1.0.pom",
    54  			want:      true,
    55  		},
    56  		{
    57  			inputPath: "path/to/my/pom.xml/file",
    58  			want:      false,
    59  		},
    60  		{
    61  			inputPath: "path/to/my/pom.xml.file",
    62  			want:      false,
    63  		},
    64  		{
    65  			inputPath: "path.to.my.pom.xml",
    66  			want:      false,
    67  		},
    68  	}
    69  	for _, tt := range tests {
    70  		t.Run(tt.inputPath, func(t *testing.T) {
    71  			e := pomxml.Extractor{}
    72  			got := e.FileRequired(simplefileapi.New(tt.inputPath, nil))
    73  			if got != tt.want {
    74  				t.Errorf("FileRequired(%s, FileInfo) got = %v, want %v", tt.inputPath, got, tt.want)
    75  			}
    76  		})
    77  	}
    78  }
    79  
    80  func TestExtractor_Extract(t *testing.T) {
    81  	tests := []extracttest.TestTableEntry{
    82  		{
    83  			Name: "invalid",
    84  			InputConfig: extracttest.ScanInputMockConfig{
    85  				Path: "testdata/not-pom.txt",
    86  			},
    87  			WantPackages: nil,
    88  			WantErr:      extracttest.ContainsErrStr{Str: "could not extract"},
    89  		},
    90  		{
    91  			Name: "invalid syntax",
    92  			InputConfig: extracttest.ScanInputMockConfig{
    93  				Path: "testdata/invalid-syntax.xml",
    94  			},
    95  			WantPackages: nil,
    96  			WantErr:      extracttest.ContainsErrStr{Str: "could not extract"},
    97  		},
    98  		{
    99  			Name: "no packages",
   100  			InputConfig: extracttest.ScanInputMockConfig{
   101  				Path: "testdata/empty.xml",
   102  			},
   103  			WantPackages: nil,
   104  		},
   105  		{
   106  			Name: "one package",
   107  			InputConfig: extracttest.ScanInputMockConfig{
   108  				Path: "testdata/one-package.xml",
   109  			},
   110  			WantPackages: []*extractor.Package{
   111  				{
   112  					Name:      "org.apache.maven:maven-artifact",
   113  					Version:   "1.0.0",
   114  					PURLType:  purl.TypeMaven,
   115  					Locations: []string{"testdata/one-package.xml"},
   116  					Metadata: &javalockfile.Metadata{
   117  						ArtifactID:   "maven-artifact",
   118  						GroupID:      "org.apache.maven",
   119  						DepGroupVals: []string{},
   120  					},
   121  				},
   122  			},
   123  		},
   124  		{
   125  			Name: "two packages",
   126  			InputConfig: extracttest.ScanInputMockConfig{
   127  				Path: "testdata/two-packages.xml",
   128  			},
   129  			WantPackages: []*extractor.Package{
   130  				{
   131  					Name:      "io.netty:netty-all",
   132  					Version:   "4.1.42.Final",
   133  					PURLType:  purl.TypeMaven,
   134  					Locations: []string{"testdata/two-packages.xml"},
   135  					Metadata: &javalockfile.Metadata{
   136  						ArtifactID:   "netty-all",
   137  						GroupID:      "io.netty",
   138  						DepGroupVals: []string{},
   139  					},
   140  				},
   141  				{
   142  					Name:      "org.slf4j:slf4j-log4j12",
   143  					Version:   "1.7.25",
   144  					PURLType:  purl.TypeMaven,
   145  					Locations: []string{"testdata/two-packages.xml"},
   146  					Metadata: &javalockfile.Metadata{
   147  						ArtifactID:   "slf4j-log4j12",
   148  						GroupID:      "org.slf4j",
   149  						DepGroupVals: []string{},
   150  					},
   151  				},
   152  			},
   153  		},
   154  		{
   155  			Name: "with dependency management",
   156  			InputConfig: extracttest.ScanInputMockConfig{
   157  				Path: "testdata/with-dependency-management.xml",
   158  			},
   159  			WantPackages: []*extractor.Package{
   160  				{
   161  					Name:      "io.netty:netty-all",
   162  					Version:   "4.1.9",
   163  					PURLType:  purl.TypeMaven,
   164  					Locations: []string{"testdata/with-dependency-management.xml"},
   165  					Metadata: &javalockfile.Metadata{
   166  						ArtifactID:   "netty-all",
   167  						GroupID:      "io.netty",
   168  						DepGroupVals: []string{},
   169  					},
   170  				},
   171  				{
   172  					Name:      "org.slf4j:slf4j-log4j12",
   173  					Version:   "1.7.25",
   174  					PURLType:  purl.TypeMaven,
   175  					Locations: []string{"testdata/with-dependency-management.xml"},
   176  					Metadata: &javalockfile.Metadata{
   177  						ArtifactID:   "slf4j-log4j12",
   178  						GroupID:      "org.slf4j",
   179  						DepGroupVals: []string{},
   180  					},
   181  				},
   182  			},
   183  		},
   184  		{
   185  			Name: "interpolation",
   186  			InputConfig: extracttest.ScanInputMockConfig{
   187  				Path: "testdata/interpolation.xml",
   188  			},
   189  			WantPackages: []*extractor.Package{
   190  				{
   191  					Name:      "org.mine:mypackage",
   192  					Version:   "1.0.0",
   193  					PURLType:  purl.TypeMaven,
   194  					Locations: []string{"testdata/interpolation.xml"},
   195  					Metadata: &javalockfile.Metadata{
   196  						ArtifactID:   "mypackage",
   197  						GroupID:      "org.mine",
   198  						DepGroupVals: []string{},
   199  					},
   200  				},
   201  				{
   202  					Name:      "org.mine:my.package",
   203  					Version:   "2.3.4",
   204  					PURLType:  purl.TypeMaven,
   205  					Locations: []string{"testdata/interpolation.xml"},
   206  					Metadata: &javalockfile.Metadata{
   207  						ArtifactID:   "my.package",
   208  						GroupID:      "org.mine",
   209  						DepGroupVals: []string{},
   210  					},
   211  				},
   212  				{
   213  					Name:      "org.mine:ranged-package",
   214  					Version:   "9.4.35.v20201120",
   215  					PURLType:  purl.TypeMaven,
   216  					Locations: []string{"testdata/interpolation.xml"},
   217  					Metadata: &javalockfile.Metadata{
   218  						ArtifactID:   "ranged-package",
   219  						GroupID:      "org.mine",
   220  						DepGroupVals: []string{},
   221  					},
   222  				},
   223  			},
   224  		},
   225  		{
   226  			Name: "with scope",
   227  			InputConfig: extracttest.ScanInputMockConfig{
   228  				Path: "testdata/with-scope.xml",
   229  			},
   230  			WantPackages: []*extractor.Package{
   231  				{
   232  					Name:      "abc:xyz",
   233  					Version:   "1.2.3",
   234  					PURLType:  purl.TypeMaven,
   235  					Locations: []string{"testdata/with-scope.xml"},
   236  					Metadata: &javalockfile.Metadata{
   237  						ArtifactID:   "xyz",
   238  						GroupID:      "abc",
   239  						DepGroupVals: []string{},
   240  					},
   241  				},
   242  				{
   243  					Name:      "junit:junit",
   244  					Version:   "4.12",
   245  					PURLType:  purl.TypeMaven,
   246  					Locations: []string{"testdata/with-scope.xml"},
   247  					Metadata: &javalockfile.Metadata{
   248  						ArtifactID:   "junit",
   249  						GroupID:      "junit",
   250  						DepGroupVals: []string{"test"},
   251  					},
   252  				},
   253  			},
   254  		},
   255  		{
   256  			Name: "with type and classifier",
   257  			InputConfig: extracttest.ScanInputMockConfig{
   258  				Path: "testdata/with-type-classifier.xml",
   259  			},
   260  			WantPackages: []*extractor.Package{
   261  				{
   262  					Name:      "abc:xyz",
   263  					Version:   "1.0.0",
   264  					PURLType:  purl.TypeMaven,
   265  					Locations: []string{"testdata/with-type-classifier.xml"},
   266  					Metadata: &javalockfile.Metadata{
   267  						ArtifactID:   "xyz",
   268  						GroupID:      "abc",
   269  						Type:         "pom",
   270  						Classifier:   "sources",
   271  						DepGroupVals: []string{},
   272  					},
   273  				},
   274  			},
   275  		},
   276  		{
   277  			Name: "with parent",
   278  			InputConfig: extracttest.ScanInputMockConfig{
   279  				Path: "testdata/with-parent.xml",
   280  			},
   281  			WantPackages: []*extractor.Package{
   282  				{
   283  					Name:      "org.alice:alice",
   284  					Version:   "1.0.0",
   285  					PURLType:  purl.TypeMaven,
   286  					Locations: []string{"testdata/with-parent.xml"},
   287  					Metadata: &javalockfile.Metadata{
   288  						ArtifactID:   "alice",
   289  						GroupID:      "org.alice",
   290  						DepGroupVals: []string{},
   291  					},
   292  				},
   293  				{
   294  					Name:      "org.bob:bob",
   295  					Version:   "2.0.0",
   296  					PURLType:  purl.TypeMaven,
   297  					Locations: []string{"testdata/with-parent.xml"},
   298  					Metadata: &javalockfile.Metadata{
   299  						ArtifactID:   "bob",
   300  						GroupID:      "org.bob",
   301  						DepGroupVals: []string{},
   302  					},
   303  				},
   304  				{
   305  					Name:      "org.chuck:chuck",
   306  					Version:   "3.0.0",
   307  					PURLType:  purl.TypeMaven,
   308  					Locations: []string{"testdata/with-parent.xml"},
   309  					Metadata: &javalockfile.Metadata{
   310  						ArtifactID:   "chuck",
   311  						GroupID:      "org.chuck",
   312  						DepGroupVals: []string{},
   313  					},
   314  				},
   315  				{
   316  					Name:      "org.dave:dave",
   317  					Version:   "4.0.0",
   318  					PURLType:  purl.TypeMaven,
   319  					Locations: []string{"testdata/with-parent.xml"},
   320  					Metadata: &javalockfile.Metadata{
   321  						ArtifactID:   "dave",
   322  						GroupID:      "org.dave",
   323  						DepGroupVals: []string{},
   324  					},
   325  				},
   326  				{
   327  					Name: "org.frank:frank",
   328  					// Version is not available in the local pom.xml.
   329  					PURLType:  purl.TypeMaven,
   330  					Locations: []string{"testdata/with-parent.xml"},
   331  					Metadata: &javalockfile.Metadata{
   332  						ArtifactID:   "frank",
   333  						GroupID:      "org.frank",
   334  						DepGroupVals: []string{},
   335  					},
   336  				},
   337  			},
   338  		},
   339  	}
   340  
   341  	for _, tt := range tests {
   342  		t.Run(tt.Name, func(t *testing.T) {
   343  			extr := pomxml.Extractor{}
   344  
   345  			scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig)
   346  			defer extracttest.CloseTestScanInput(t, scanInput)
   347  
   348  			got, err := extr.Extract(t.Context(), &scanInput)
   349  
   350  			if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" {
   351  				t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff)
   352  				return
   353  			}
   354  
   355  			wantInv := inventory.Inventory{Packages: tt.WantPackages}
   356  			if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" {
   357  				t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff)
   358  			}
   359  		})
   360  	}
   361  }