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