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