github.com/google/osv-scalibr@v0.4.1/converter/converter_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 converter_test 16 17 import ( 18 "math/rand" 19 "runtime" 20 "testing" 21 22 "github.com/CycloneDX/cyclonedx-go" 23 "github.com/google/go-cmp/cmp" 24 scalibr "github.com/google/osv-scalibr" 25 "github.com/google/osv-scalibr/converter" 26 "github.com/google/osv-scalibr/extractor" 27 "github.com/google/osv-scalibr/extractor/filesystem/language/python/wheelegg" 28 "github.com/google/osv-scalibr/inventory" 29 "github.com/google/osv-scalibr/purl" 30 "github.com/google/uuid" 31 ) 32 33 func ptr[T any](v T) *T { 34 return &v 35 } 36 37 func TestToCDX(t *testing.T) { 38 // Make UUIDs deterministic 39 uuid.SetRand(rand.New(rand.NewSource(1))) 40 defaultBOM := cyclonedx.NewBOM() 41 42 testCases := []struct { 43 desc string 44 scanResult *scalibr.ScanResult 45 config converter.CDXConfig 46 want *cyclonedx.BOM 47 }{ 48 { 49 desc: "Package_with_custom_config", 50 scanResult: &scalibr.ScanResult{ 51 Inventory: inventory.Inventory{ 52 Packages: []*extractor.Package{{ 53 Name: "software", 54 Version: "1.2.3", 55 PURLType: purl.TypePyPi, 56 Plugins: []string{wheelegg.Name}, 57 }}, 58 }, 59 }, 60 config: converter.CDXConfig{ 61 ComponentName: "sbom-1", 62 ComponentVersion: "1.0.0", 63 Authors: []string{"author"}, 64 }, 65 want: &cyclonedx.BOM{ 66 Metadata: &cyclonedx.Metadata{ 67 Component: &cyclonedx.Component{ 68 Name: "sbom-1", 69 Version: "1.0.0", 70 BOMRef: "52fdfc07-2182-454f-963f-5f0f9a621d72", 71 }, 72 Authors: ptr([]cyclonedx.OrganizationalContact{{Name: "author"}}), 73 Tools: &cyclonedx.ToolsChoice{ 74 Components: &[]cyclonedx.Component{ 75 { 76 Type: cyclonedx.ComponentTypeApplication, 77 Name: "SCALIBR", 78 ExternalReferences: ptr([]cyclonedx.ExternalReference{ 79 {URL: "https://github.com/google/osv-scalibr", Type: cyclonedx.ERTypeWebsite}, 80 }), 81 }, 82 }, 83 }, 84 }, 85 Components: ptr([]cyclonedx.Component{ 86 { 87 BOMRef: "9566c74d-1003-4c4d-bbbb-0407d1e2c649", 88 Type: "library", 89 Name: "software", 90 Version: "1.2.3", 91 PackageURL: "pkg:pypi/software@1.2.3", 92 }, 93 }), 94 }, 95 }, 96 { 97 desc: "Package_with_custom_config_and_cdx-component-type", 98 scanResult: &scalibr.ScanResult{ 99 Inventory: inventory.Inventory{ 100 Packages: []*extractor.Package{{ 101 Name: "software", 102 Version: "1.2.3", 103 PURLType: purl.TypePyPi, 104 Plugins: []string{wheelegg.Name}, 105 }}, 106 }, 107 }, 108 config: converter.CDXConfig{ 109 ComponentName: "sbom-2", 110 ComponentType: "library", 111 ComponentVersion: "1.0.0", 112 Authors: []string{"author"}, 113 }, 114 want: &cyclonedx.BOM{ 115 Metadata: &cyclonedx.Metadata{ 116 Component: &cyclonedx.Component{ 117 Name: "sbom-2", 118 Type: cyclonedx.ComponentTypeLibrary, 119 Version: "1.0.0", 120 BOMRef: "81855ad8-681d-4d86-91e9-1e00167939cb", 121 }, 122 Authors: ptr([]cyclonedx.OrganizationalContact{{Name: "author"}}), 123 Tools: &cyclonedx.ToolsChoice{ 124 Components: &[]cyclonedx.Component{ 125 { 126 Type: cyclonedx.ComponentTypeApplication, 127 Name: "SCALIBR", 128 ExternalReferences: ptr([]cyclonedx.ExternalReference{ 129 {URL: "https://github.com/google/osv-scalibr", Type: cyclonedx.ERTypeWebsite}, 130 }), 131 }, 132 }, 133 }, 134 }, 135 Components: ptr([]cyclonedx.Component{ 136 { 137 BOMRef: "6694d2c4-22ac-4208-a007-2939487f6999", 138 Type: "library", 139 Name: "software", 140 Version: "1.2.3", 141 PackageURL: "pkg:pypi/software@1.2.3", 142 }, 143 }), 144 }, 145 }, 146 } 147 148 for _, tc := range testCases { 149 t.Run(tc.desc, func(t *testing.T) { 150 got := converter.ToCDX(tc.scanResult, tc.config) 151 // Can't mock time.Now() so skip verifying the timestamp. 152 tc.want.Metadata.Timestamp = got.Metadata.Timestamp 153 // Auto-populated fields 154 tc.want.XMLNS = defaultBOM.XMLNS 155 tc.want.JSONSchema = defaultBOM.JSONSchema 156 tc.want.BOMFormat = defaultBOM.BOMFormat 157 tc.want.SpecVersion = defaultBOM.SpecVersion 158 tc.want.Version = defaultBOM.Version 159 160 if diff := cmp.Diff(tc.want, got); diff != "" { 161 t.Errorf("converter.ToCDX(%v): unexpected diff (-want +got):\n%s", tc.scanResult, diff) 162 } 163 }) 164 } 165 } 166 167 func TestToPURL(t *testing.T) { 168 tests := []struct { 169 desc string 170 pkg *extractor.Package 171 want *purl.PackageURL 172 onGoos string 173 }{ 174 { 175 desc: "Valid_package_extractor", 176 pkg: &extractor.Package{ 177 Name: "software", 178 Version: "1.0.0", 179 PURLType: purl.TypePyPi, 180 Locations: []string{"/file1"}, 181 Plugins: []string{wheelegg.Name}, 182 }, 183 want: &purl.PackageURL{ 184 Type: purl.TypePyPi, 185 Name: "software", 186 Version: "1.0.0", 187 }, 188 }, 189 } 190 191 for _, tc := range tests { 192 t.Run(tc.desc, func(t *testing.T) { 193 if tc.onGoos != "" && tc.onGoos != runtime.GOOS { 194 t.Skipf("Skipping test on %s", runtime.GOOS) 195 } 196 197 got := converter.ToPURL(tc.pkg) 198 199 if diff := cmp.Diff(tc.want, got); diff != "" { 200 t.Errorf("converter.ToPURL(%v) returned unexpected diff (-want +got):\n%s", tc.pkg, diff) 201 } 202 }) 203 } 204 }