github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/formats/common/cyclonedxhelpers/decoder_test.go (about) 1 package cyclonedxhelpers 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "testing" 8 9 "github.com/CycloneDX/cyclonedx-go" 10 "github.com/nextlinux/gosbom/gosbom/artifact" 11 "github.com/nextlinux/gosbom/gosbom/sbom" 12 "github.com/stretchr/testify/assert" 13 ) 14 15 func Test_decode(t *testing.T) { 16 type expected struct { 17 os string 18 pkg string 19 ver string 20 relation string 21 purl string 22 cpe string 23 } 24 tests := []struct { 25 name string 26 input cyclonedx.BOM 27 expected []expected 28 }{ 29 { 30 name: "basic mapping from cyclonedx", 31 input: cyclonedx.BOM{ 32 Metadata: nil, 33 Components: &[]cyclonedx.Component{ 34 { 35 BOMRef: "p1", 36 Type: cyclonedx.ComponentTypeLibrary, 37 Name: "package-1", 38 Version: "1.0.1", 39 Description: "", 40 Hashes: nil, 41 Licenses: &cyclonedx.Licenses{ 42 { 43 License: &cyclonedx.License{ 44 ID: "MIT", 45 }, 46 }, 47 }, 48 CPE: "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*", 49 PackageURL: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1", 50 ExternalReferences: &[]cyclonedx.ExternalReference{ 51 { 52 URL: "", 53 Comment: "", 54 Hashes: nil, 55 Type: "", 56 }, 57 }, 58 Properties: &[]cyclonedx.Property{ 59 { 60 Name: "foundBy", 61 Value: "the-cataloger-1", 62 }, 63 { 64 Name: "language", 65 Value: "python", 66 }, 67 { 68 Name: "type", 69 Value: "python", 70 }, 71 { 72 Name: "metadataType", 73 Value: "PythonPackageMetadata", 74 }, 75 { 76 Name: "path", 77 Value: "/some/path/pkg1", 78 }, 79 }, 80 Components: nil, 81 Evidence: nil, 82 }, 83 { 84 BOMRef: "p2", 85 Type: cyclonedx.ComponentTypeLibrary, 86 Name: "package-2", 87 Version: "2.0.2", 88 Hashes: nil, 89 Licenses: &cyclonedx.Licenses{ 90 { 91 License: &cyclonedx.License{ 92 ID: "MIT", 93 }, 94 }, 95 }, 96 CPE: "cpe:2.3:*:another:package:2:*:*:*:*:*:*:*", 97 PackageURL: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2", 98 Properties: &[]cyclonedx.Property{ 99 100 { 101 Name: "foundBy", 102 Value: "apkdb-cataloger", 103 }, 104 { 105 Name: "type", 106 Value: "apk", 107 }, 108 { 109 Name: "metadataType", 110 Value: "ApkMetadata", 111 }, 112 { 113 Name: "path", 114 Value: "/lib/apk/db/installed", 115 }, 116 { 117 Name: "layerID", 118 Value: "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635", 119 }, 120 { 121 Name: "originPackage", 122 Value: "zlib", 123 }, 124 { 125 Name: "size", 126 Value: "51213", 127 }, 128 { 129 Name: "installedSize", 130 Value: "110592", 131 }, 132 { 133 Name: "pullDependencies", 134 Value: "so:libc.musl-x86_64.so.1", 135 }, 136 { 137 Name: "pullChecksum", 138 Value: "Q1uss4DfpvL16Nw2YUTwmzGBABz3Y=", 139 }, 140 { 141 Name: "gitCommitOfApkPort", 142 Value: "d2bfb22c8e8f67ad7d8d02704f35ec4d2a19f9b9", 143 }, 144 }, 145 }, 146 { 147 Type: cyclonedx.ComponentTypeOS, 148 Name: "debian", 149 Version: "1.2.3", 150 Hashes: nil, 151 Licenses: &cyclonedx.Licenses{ 152 { 153 License: &cyclonedx.License{ 154 ID: "MIT", 155 }, 156 }, 157 }, 158 Properties: &[]cyclonedx.Property{ 159 { 160 Name: "prettyName", 161 Value: "debian", 162 }, 163 { 164 Name: "id", 165 Value: "debian", 166 }, 167 { 168 Name: "versionID", 169 Value: "1.2.3", 170 }, 171 }, 172 Components: nil, 173 Evidence: nil, 174 }, 175 }, 176 Dependencies: &[]cyclonedx.Dependency{ 177 { 178 Ref: "p1", 179 Dependencies: &[]string{"p2"}, 180 }, 181 }, 182 }, 183 expected: []expected{ 184 { 185 os: "debian", 186 ver: "1.2.3", 187 }, 188 { 189 pkg: "package-1", 190 ver: "1.0.1", 191 cpe: "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*", 192 purl: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1", 193 }, 194 { 195 pkg: "package-2", 196 ver: "2.0.2", 197 purl: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2", 198 relation: "package-1", 199 }, 200 }, 201 }, 202 } 203 for _, test := range tests { 204 t.Run(test.name, func(t *testing.T) { 205 sbom, err := ToGosbomModel(&test.input) 206 assert.NoError(t, err) 207 208 test: 209 for _, e := range test.expected { 210 if e.os != "" { 211 assert.Equal(t, e.os, sbom.Artifacts.LinuxDistribution.ID) 212 assert.Equal(t, e.ver, sbom.Artifacts.LinuxDistribution.VersionID) 213 } 214 if e.pkg != "" { 215 for p := range sbom.Artifacts.Packages.Enumerate() { 216 if e.pkg != p.Name { 217 continue 218 } 219 220 assert.Equal(t, e.ver, p.Version) 221 222 if e.cpe != "" { 223 foundCPE := false 224 for _, c := range p.CPEs { 225 cstr := c.BindToFmtString() 226 if e.cpe == cstr { 227 foundCPE = true 228 break 229 } 230 } 231 if !foundCPE { 232 assert.Fail(t, fmt.Sprintf("CPE not found in package: %s", e.cpe)) 233 } 234 } 235 236 if e.purl != "" { 237 assert.Equal(t, e.purl, p.PURL) 238 } 239 240 if e.relation != "" { 241 foundRelation := false 242 for _, r := range sbom.Relationships { 243 p := sbom.Artifacts.Packages.Package(r.To.ID()) 244 if e.relation == p.Name { 245 foundRelation = true 246 break 247 } 248 } 249 if !foundRelation { 250 assert.Fail(t, fmt.Sprintf("relation not found: %s", e.relation)) 251 } 252 } 253 continue test 254 } 255 assert.Fail(t, fmt.Sprintf("package should be present: %s", e.pkg)) 256 } 257 } 258 }) 259 } 260 } 261 262 func Test_relationshipDirection(t *testing.T) { 263 cyclonedx_bom := cyclonedx.BOM{Metadata: nil, 264 Components: &[]cyclonedx.Component{ 265 { 266 BOMRef: "p1", 267 Type: cyclonedx.ComponentTypeLibrary, 268 Name: "package-1", 269 Version: "1.0.1", 270 PackageURL: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1", 271 }, 272 { 273 BOMRef: "p2", 274 Type: cyclonedx.ComponentTypeLibrary, 275 Name: "package-2", 276 Version: "2.0.2", 277 PackageURL: "pkg:some/package-2@2.0.2?arch=arm64&upstream=upstream1&distro=alpine-1", 278 }, 279 }, 280 Dependencies: &[]cyclonedx.Dependency{ 281 { 282 Ref: "p1", 283 Dependencies: &[]string{"p2"}, 284 }, 285 }} 286 sbom, err := ToGosbomModel(&cyclonedx_bom) 287 assert.Nil(t, err) 288 assert.Len(t, sbom.Relationships, 1) 289 relationship := sbom.Relationships[0] 290 291 // check that p2 -- dependency of --> p1 292 // same as p1 -- depends on --> p2 293 assert.Equal(t, artifact.DependencyOfRelationship, relationship.Type) 294 assert.Equal(t, "package-2", packageNameFromIdentifier(sbom, relationship.From)) 295 assert.Equal(t, "package-1", packageNameFromIdentifier(sbom, relationship.To)) 296 } 297 298 func packageNameFromIdentifier(model *sbom.SBOM, identifier artifact.Identifiable) string { 299 return model.Artifacts.Packages.Package(identifier.ID()).Name 300 } 301 302 func Test_missingDataDecode(t *testing.T) { 303 bom := &cyclonedx.BOM{ 304 Metadata: nil, 305 Components: &[]cyclonedx.Component{}, 306 SpecVersion: cyclonedx.SpecVersion1_4, 307 } 308 309 _, err := ToGosbomModel(bom) 310 assert.NoError(t, err) 311 312 bom.Metadata = &cyclonedx.Metadata{} 313 314 _, err = ToGosbomModel(bom) 315 assert.NoError(t, err) 316 317 pkg := decodeComponent(&cyclonedx.Component{ 318 Licenses: &cyclonedx.Licenses{ 319 { 320 License: nil, 321 }, 322 }, 323 }) 324 assert.Equal(t, pkg.Licenses.Empty(), true) 325 } 326 327 func Test_missingComponentsDecode(t *testing.T) { 328 bom := &cyclonedx.BOM{ 329 SpecVersion: cyclonedx.SpecVersion1_4, 330 } 331 bomBytes, _ := json.Marshal(&bom) 332 decode := GetDecoder(cyclonedx.BOMFileFormatJSON) 333 334 _, err := decode(bytes.NewReader(bomBytes)) 335 336 assert.NoError(t, err) 337 }