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 }