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 }