github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/sbom/cdx/cdx_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 cdx_test
    16  
    17  import (
    18  	"os"
    19  	"testing"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	"github.com/google/go-cmp/cmp/cmpopts"
    23  	"github.com/google/osv-scalibr/extractor"
    24  	"github.com/google/osv-scalibr/extractor/filesystem"
    25  	"github.com/google/osv-scalibr/extractor/filesystem/sbom/cdx"
    26  	cdxmeta "github.com/google/osv-scalibr/extractor/filesystem/sbom/cdx/metadata"
    27  	"github.com/google/osv-scalibr/extractor/filesystem/simplefileapi"
    28  	scalibrfs "github.com/google/osv-scalibr/fs"
    29  	"github.com/google/osv-scalibr/inventory"
    30  	"github.com/google/osv-scalibr/purl"
    31  )
    32  
    33  func TestFileRequired(t *testing.T) {
    34  	var e filesystem.Extractor = cdx.Extractor{}
    35  
    36  	tests := []struct {
    37  		name           string
    38  		path           string
    39  		wantIsRequired bool
    40  	}{
    41  		{
    42  			name:           "sbom.cdx.json",
    43  			path:           "testdata/sbom.cdx.json",
    44  			wantIsRequired: true,
    45  		},
    46  		{
    47  			name:           "sbom.cdx.JSON",
    48  			path:           "testdata/sbom.cdx.JSON",
    49  			wantIsRequired: true,
    50  		},
    51  		{
    52  			name:           "sbom.cDX.json",
    53  			path:           "testdata/sbom.cDX.json",
    54  			wantIsRequired: true,
    55  		},
    56  		{
    57  			name:           "sbom.bom.json",
    58  			path:           "testdata/sbom.bom.json",
    59  			wantIsRequired: false,
    60  		},
    61  		{
    62  			name:           "sbom.bom.xml",
    63  			path:           "testdata/sbom.bom.xml",
    64  			wantIsRequired: false,
    65  		},
    66  		{
    67  			name:           "bom.json",
    68  			path:           "testdata/bom.json",
    69  			wantIsRequired: true,
    70  		},
    71  		{
    72  			name:           "bom.xml",
    73  			path:           "testdata/bom.xml",
    74  			wantIsRequired: true,
    75  		},
    76  		{
    77  			name:           "sbom.cdx.xml",
    78  			path:           "testdata/sbom.cdx.xml",
    79  			wantIsRequired: true,
    80  		},
    81  		{
    82  			name:           "random_file.ext",
    83  			path:           "testdata/random_file.ext",
    84  			wantIsRequired: false,
    85  		},
    86  		{
    87  			name:           "sbom.cdx.json.foo.ext",
    88  			path:           "testdata/sbom.cdx.json.foo.ext",
    89  			wantIsRequired: false,
    90  		},
    91  	}
    92  
    93  	for _, tt := range tests {
    94  		t.Run(tt.name, func(t *testing.T) {
    95  			if got := e.FileRequired(simplefileapi.New(tt.path, nil)); got != tt.wantIsRequired {
    96  				t.Fatalf("FileRequired(%s): got %v, want %v", tt.path, got, tt.wantIsRequired)
    97  			}
    98  		})
    99  	}
   100  }
   101  
   102  func TestExtract(t *testing.T) {
   103  	var e filesystem.Extractor = cdx.Extractor{}
   104  
   105  	tests := []struct {
   106  		name         string
   107  		path         string
   108  		wantErr      error
   109  		wantPackages []*extractor.Package
   110  	}{
   111  		{
   112  			name:         "minimal.cdx.json",
   113  			path:         "testdata/minimal.cdx.json",
   114  			wantPackages: []*extractor.Package{},
   115  		},
   116  		{
   117  			name: "sbom.cdx.json",
   118  			path: "testdata/sbom.cdx.json",
   119  			wantPackages: []*extractor.Package{
   120  				{
   121  					Name:    "Nginx",
   122  					Version: "1.21.1",
   123  					Metadata: &cdxmeta.Metadata{
   124  						CPEs: []string{"cpe:2.3:a:nginx:nginx:1.21.1"},
   125  					},
   126  					Locations: []string{"testdata/sbom.cdx.json"},
   127  				},
   128  				{
   129  					Name:     "openssl",
   130  					Version:  "1.1.1",
   131  					PURLType: purl.TypeGeneric,
   132  					Metadata: &cdxmeta.Metadata{
   133  						PURL: purlFromString(t, "pkg:generic/openssl@1.1.1"),
   134  					},
   135  					Locations: []string{"testdata/sbom.cdx.json"},
   136  				},
   137  			},
   138  		},
   139  		{
   140  			name: "sbom-with-nested-comps.cdx.json",
   141  			path: "testdata/sbom-with-nested-comps.cdx.json",
   142  			wantPackages: []*extractor.Package{
   143  				{
   144  					Name:    "Nginx",
   145  					Version: "1.21.1",
   146  					Metadata: &cdxmeta.Metadata{
   147  						CPEs: []string{"cpe:2.3:a:nginx:nginx:1.21.1"},
   148  					},
   149  					Locations: []string{"testdata/sbom-with-nested-comps.cdx.json"},
   150  				},
   151  				{
   152  					Name:     "openssl",
   153  					Version:  "1.1.1",
   154  					PURLType: purl.TypeGeneric,
   155  					Metadata: &cdxmeta.Metadata{
   156  						PURL: purlFromString(t, "pkg:generic/openssl@1.1.1"),
   157  					},
   158  					Locations: []string{"testdata/sbom-with-nested-comps.cdx.json"},
   159  				},
   160  				{
   161  					Name:     "rustls",
   162  					Version:  "0.23.13",
   163  					PURLType: purl.TypeCargo,
   164  					Metadata: &cdxmeta.Metadata{
   165  						PURL: purlFromString(t, "pkg:cargo/rustls@0.23.13"),
   166  					},
   167  					Locations: []string{"testdata/sbom-with-nested-comps.cdx.json"},
   168  				},
   169  			},
   170  		},
   171  		{
   172  			name: "sbom-with-locations.cdx.json",
   173  			path: "testdata/sbom-with-locations.cdx.json",
   174  			wantPackages: []*extractor.Package{
   175  				{
   176  					Name:     "@gar/promisify",
   177  					Version:  "1.1.3",
   178  					PURLType: purl.TypeNPM,
   179  					Metadata: &cdxmeta.Metadata{
   180  						PURL: purlFromString(t, "pkg:npm/%40gar%2Fpromisify@1.1.3"),
   181  						CDXLocations: []string{
   182  							"home/test/.vscode-server/bin/node_modules/@gar/promisify/package.json",
   183  							"usr/lib/node_modules/@gar/promisify/package.json",
   184  						},
   185  					},
   186  					Locations: []string{
   187  						"testdata/sbom-with-locations.cdx.json",
   188  					},
   189  				},
   190  			},
   191  		},
   192  		{
   193  			name: "sbom.cdx.xml",
   194  			path: "testdata/sbom.cdx.xml",
   195  			wantPackages: []*extractor.Package{
   196  				{
   197  					Name:    "Nginx",
   198  					Version: "1.21.1",
   199  					Metadata: &cdxmeta.Metadata{
   200  						CPEs: []string{"cpe:2.3:a:nginx:nginx:1.21.1"},
   201  					},
   202  					Locations: []string{"testdata/sbom.cdx.xml"},
   203  				},
   204  				{
   205  					Name:     "openssl",
   206  					Version:  "1.1.1",
   207  					PURLType: purl.TypeGeneric,
   208  					Metadata: &cdxmeta.Metadata{
   209  						PURL: purlFromString(t, "pkg:generic/openssl@1.1.1"),
   210  					},
   211  					Locations: []string{"testdata/sbom.cdx.xml"},
   212  				},
   213  			},
   214  		},
   215  		{
   216  			name:    "invalid_sbom.cdx.json",
   217  			path:    "testdata/invalid_sbom.cdxjson",
   218  			wantErr: cmpopts.AnyError,
   219  		},
   220  		{
   221  			name:    "sbom.cdx.json.foo.ext",
   222  			path:    "testdata/sbom.cdx.json.foo.ext",
   223  			wantErr: cmpopts.AnyError,
   224  		},
   225  	}
   226  
   227  	for _, tt := range tests {
   228  		t.Run(tt.name, func(t *testing.T) {
   229  			r, err := os.Open(tt.path)
   230  			defer func() {
   231  				if err = r.Close(); err != nil {
   232  					t.Errorf("Close(): %v", err)
   233  				}
   234  			}()
   235  			if err != nil {
   236  				t.Fatal(err)
   237  			}
   238  
   239  			input := &filesystem.ScanInput{FS: scalibrfs.DirFS("."), Path: tt.path, Reader: r}
   240  			got, err := e.Extract(t.Context(), input)
   241  			if diff := cmp.Diff(tt.wantErr, err, cmpopts.EquateErrors()); diff != "" {
   242  				t.Errorf("Extract(%s) unexpected error (-want +got):\n%s", tt.path, diff)
   243  			}
   244  
   245  			want := inventory.Inventory{Packages: tt.wantPackages}
   246  
   247  			if diff := cmp.Diff(want, got, cmpopts.SortSlices(pkgLess)); diff != "" {
   248  				t.Errorf("Extract(%s) (-want +got):\n%s", tt.path, diff)
   249  			}
   250  		})
   251  	}
   252  }
   253  
   254  func pkgLess(i1, i2 *extractor.Package) bool {
   255  	return i1.Name < i2.Name
   256  }
   257  
   258  func purlFromString(t *testing.T, purlStr string) *purl.PackageURL {
   259  	t.Helper()
   260  
   261  	res, err := purl.FromString(purlStr)
   262  	if err != nil {
   263  		t.Fatalf("purlFromString(%s): %v", purlStr, err)
   264  	}
   265  	return &res
   266  }