github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/common/cyclonedxhelpers/component_test.go (about) 1 package cyclonedxhelpers 2 3 import ( 4 "fmt" 5 "reflect" 6 "testing" 7 8 "github.com/CycloneDX/cyclonedx-go" 9 "github.com/stretchr/testify/assert" 10 11 "github.com/anchore/syft/syft/file" 12 "github.com/anchore/syft/syft/pkg" 13 ) 14 15 func Test_encodeComponentProperties(t *testing.T) { 16 epoch := 2 17 tests := []struct { 18 name string 19 input pkg.Package 20 expected []cyclonedx.Property 21 }{ 22 { 23 name: "no metadata", 24 input: pkg.Package{}, 25 expected: nil, 26 }, 27 { 28 name: "from apk", 29 input: pkg.Package{ 30 FoundBy: "cataloger", 31 Locations: file.NewLocationSet( 32 file.NewLocationFromCoordinates(file.Coordinates{RealPath: "test"}), 33 ), 34 Metadata: pkg.ApkDBEntry{ 35 Package: "libc-utils", 36 OriginPackage: "libc-dev", 37 Maintainer: "Natanael Copa <ncopa@alpinelinux.org>", 38 Version: "0.7.2-r0", 39 Architecture: "x86_64", 40 URL: "http://alpinelinux.org", 41 Description: "Meta package to pull in correct libc", 42 Size: 0, 43 InstalledSize: 4096, 44 Dependencies: []string{"musl-utils"}, 45 Provides: []string{"so:libc.so.1"}, 46 Checksum: "Q1p78yvTLG094tHE1+dToJGbmYzQE=", 47 GitCommit: "97b1c2842faa3bfa30f5811ffbf16d5ff9f1a479", 48 Files: []pkg.ApkFileRecord{}, 49 }, 50 }, 51 expected: []cyclonedx.Property{ 52 {Name: "syft:package:foundBy", Value: "cataloger"}, 53 {Name: "syft:package:metadataType", Value: "apk-db-entry"}, 54 {Name: "syft:location:0:path", Value: "test"}, 55 {Name: "syft:metadata:gitCommitOfApkPort", Value: "97b1c2842faa3bfa30f5811ffbf16d5ff9f1a479"}, 56 {Name: "syft:metadata:installedSize", Value: "4096"}, 57 {Name: "syft:metadata:originPackage", Value: "libc-dev"}, 58 {Name: "syft:metadata:provides:0", Value: "so:libc.so.1"}, 59 {Name: "syft:metadata:pullChecksum", Value: "Q1p78yvTLG094tHE1+dToJGbmYzQE="}, 60 {Name: "syft:metadata:pullDependencies:0", Value: "musl-utils"}, 61 {Name: "syft:metadata:size", Value: "0"}, 62 }, 63 }, 64 { 65 name: "from dpkg", 66 input: pkg.Package{ 67 Metadata: pkg.DpkgDBEntry{ 68 Package: "tzdata", 69 Version: "2020a-0+deb10u1", 70 Source: "tzdata-dev", 71 SourceVersion: "1.0", 72 Architecture: "all", 73 InstalledSize: 3036, 74 Maintainer: "GNU Libc Maintainers <debian-glibc@lists.debian.org>", 75 Files: []pkg.DpkgFileRecord{}, 76 }, 77 }, 78 expected: []cyclonedx.Property{ 79 {Name: "syft:package:metadataType", Value: "dpkg-db-entry"}, 80 {Name: "syft:metadata:installedSize", Value: "3036"}, 81 {Name: "syft:metadata:source", Value: "tzdata-dev"}, 82 {Name: "syft:metadata:sourceVersion", Value: "1.0"}, 83 }, 84 }, 85 { 86 name: "from go bin", 87 input: pkg.Package{ 88 Name: "golang.org/x/net", 89 Version: "v0.0.0-20211006190231-62292e806868", 90 Language: pkg.Go, 91 Type: pkg.GoModulePkg, 92 Metadata: pkg.GolangBinaryBuildinfoEntry{ 93 GoCompiledVersion: "1.17", 94 Architecture: "amd64", 95 H1Digest: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k=", 96 }, 97 }, 98 expected: []cyclonedx.Property{ 99 {Name: "syft:package:language", Value: pkg.Go.String()}, 100 {Name: "syft:package:metadataType", Value: "go-module-buildinfo-entry"}, 101 {Name: "syft:package:type", Value: "go-module"}, 102 {Name: "syft:metadata:architecture", Value: "amd64"}, 103 {Name: "syft:metadata:goCompiledVersion", Value: "1.17"}, 104 {Name: "syft:metadata:h1Digest", Value: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k="}, 105 }, 106 }, 107 { 108 name: "from go mod", 109 input: pkg.Package{ 110 Name: "golang.org/x/net", 111 Version: "v0.0.0-20211006190231-62292e806868", 112 Language: pkg.Go, 113 Type: pkg.GoModulePkg, 114 Metadata: pkg.GolangModuleEntry{ 115 H1Digest: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k=", 116 }, 117 }, 118 expected: []cyclonedx.Property{ 119 {Name: "syft:package:language", Value: pkg.Go.String()}, 120 {Name: "syft:package:metadataType", Value: "go-module-entry"}, 121 {Name: "syft:package:type", Value: "go-module"}, 122 {Name: "syft:metadata:h1Digest", Value: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k="}, 123 }, 124 }, 125 { 126 name: "from rpm", 127 input: pkg.Package{ 128 Name: "dive", 129 Version: "0.9.2-1", 130 Type: pkg.RpmPkg, 131 Metadata: pkg.RpmDBEntry{ 132 Name: "dive", 133 Epoch: &epoch, 134 Arch: "x86_64", 135 Release: "1", 136 Version: "0.9.2", 137 SourceRpm: "dive-0.9.2-1.src.rpm", 138 Size: 12406784, 139 Vendor: "", 140 Files: []pkg.RpmFileRecord{}, 141 }, 142 }, 143 expected: []cyclonedx.Property{ 144 {Name: "syft:package:metadataType", Value: "rpm-db-entry"}, 145 {Name: "syft:package:type", Value: "rpm"}, 146 {Name: "syft:metadata:epoch", Value: "2"}, 147 {Name: "syft:metadata:release", Value: "1"}, 148 {Name: "syft:metadata:size", Value: "12406784"}, 149 {Name: "syft:metadata:sourceRpm", Value: "dive-0.9.2-1.src.rpm"}, 150 }, 151 }, 152 } 153 for _, test := range tests { 154 t.Run(test.name, func(t *testing.T) { 155 c := encodeComponent(test.input) 156 if test.expected == nil { 157 if c.Properties != nil { 158 t.Fatalf("expected no properties, got: %+v", *c.Properties) 159 } 160 return 161 } 162 assert.ElementsMatch(t, test.expected, *c.Properties) 163 }) 164 } 165 } 166 167 func Test_encodeCompomentType(t *testing.T) { 168 tests := []struct { 169 name string 170 pkg pkg.Package 171 want cyclonedx.Component 172 }{ 173 { 174 name: "non-binary package", 175 pkg: pkg.Package{ 176 Name: "pkg1", 177 Version: "1.9.2", 178 Type: pkg.GoModulePkg, 179 }, 180 want: cyclonedx.Component{ 181 Name: "pkg1", 182 Version: "1.9.2", 183 Type: cyclonedx.ComponentTypeLibrary, 184 Properties: &[]cyclonedx.Property{ 185 { 186 Name: "syft:package:type", 187 Value: "go-module", 188 }, 189 }, 190 }, 191 }, 192 { 193 name: "non-binary package", 194 pkg: pkg.Package{ 195 Name: "pkg1", 196 Version: "3.1.2", 197 Type: pkg.BinaryPkg, 198 }, 199 want: cyclonedx.Component{ 200 Name: "pkg1", 201 Version: "3.1.2", 202 Type: cyclonedx.ComponentTypeApplication, 203 Properties: &[]cyclonedx.Property{ 204 { 205 Name: "syft:package:type", 206 Value: "binary", 207 }, 208 }, 209 }, 210 }, 211 } 212 for _, tt := range tests { 213 t.Run(tt.name, func(t *testing.T) { 214 tt.pkg.ID() 215 p := encodeComponent(tt.pkg) 216 assert.Equal(t, tt.want, p) 217 }) 218 } 219 } 220 221 func Test_deriveBomRef(t *testing.T) { 222 pkgWithPurl := pkg.Package{ 223 Name: "django", 224 Version: "1.11.1", 225 PURL: "pkg:pypi/django@1.11.1", 226 } 227 pkgWithPurl.SetID() 228 229 pkgWithOutPurl := pkg.Package{ 230 Name: "django", 231 Version: "1.11.1", 232 PURL: "", 233 } 234 pkgWithOutPurl.SetID() 235 236 pkgWithBadPurl := pkg.Package{ 237 Name: "django", 238 Version: "1.11.1", 239 PURL: "pkg:pyjango@1.11.1", 240 } 241 pkgWithBadPurl.SetID() 242 243 tests := []struct { 244 name string 245 pkg pkg.Package 246 want string 247 }{ 248 { 249 name: "use pURL-id hybrid", 250 pkg: pkgWithPurl, 251 want: fmt.Sprintf("pkg:pypi/django@1.11.1?package-id=%s", pkgWithPurl.ID()), 252 }, 253 { 254 name: "fallback to ID when pURL is invalid", 255 pkg: pkgWithBadPurl, 256 want: string(pkgWithBadPurl.ID()), 257 }, 258 { 259 name: "fallback to ID when pURL is missing", 260 pkg: pkgWithOutPurl, 261 want: string(pkgWithOutPurl.ID()), 262 }, 263 } 264 for _, tt := range tests { 265 t.Run(tt.name, func(t *testing.T) { 266 tt.pkg.ID() 267 assert.Equal(t, tt.want, deriveBomRef(tt.pkg)) 268 }) 269 } 270 } 271 272 func Test_decodeComponent(t *testing.T) { 273 tests := []struct { 274 name string 275 component cyclonedx.Component 276 wantLanguage pkg.Language 277 wantMetadata any 278 }{ 279 { 280 name: "derive language from pURL if missing", 281 component: cyclonedx.Component{ 282 Name: "ch.qos.logback/logback-classic", 283 Version: "1.2.3", 284 PackageURL: "pkg:maven/ch.qos.logback/logback-classic@1.2.3", 285 Type: "library", 286 BOMRef: "pkg:maven/ch.qos.logback/logback-classic@1.2.3", 287 }, 288 wantLanguage: pkg.Java, 289 }, 290 { 291 name: "handle RpmdbMetadata type without properties", 292 component: cyclonedx.Component{ 293 Name: "acl", 294 Version: "2.2.53-1.el8", 295 PackageURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8", 296 Type: "library", 297 BOMRef: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8", 298 Properties: &[]cyclonedx.Property{ 299 { 300 Name: "syft:package:metadataType", 301 Value: "RpmdbMetadata", 302 }, 303 }, 304 }, 305 wantMetadata: pkg.RpmDBEntry{}, 306 }, 307 { 308 name: "handle RpmdbMetadata type with properties", 309 component: cyclonedx.Component{ 310 Name: "acl", 311 Version: "2.2.53-1.el8", 312 PackageURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8", 313 Type: "library", 314 BOMRef: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8", 315 Properties: &[]cyclonedx.Property{ 316 { 317 Name: "syft:package:metadataType", 318 Value: "RpmDBMetadata", 319 }, 320 { 321 Name: "syft:metadata:release", 322 Value: "some-release", 323 }, 324 }, 325 }, 326 wantMetadata: pkg.RpmDBEntry{ 327 Release: "some-release", 328 }, 329 }, 330 } 331 332 for _, tt := range tests { 333 t.Run(tt.name, func(t *testing.T) { 334 p := decodeComponent(&tt.component) 335 if tt.wantLanguage != "" { 336 assert.Equal(t, tt.wantLanguage, p.Language) 337 } 338 if tt.wantMetadata != nil { 339 assert.Truef(t, reflect.DeepEqual(tt.wantMetadata, p.Metadata), "metadata should match: %+v != %+v", tt.wantMetadata, p.Metadata) 340 } 341 if tt.wantMetadata == nil && tt.wantLanguage == "" { 342 t.Fatal("this is a useless test, please remove it") 343 } 344 }) 345 } 346 }