github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/syftjson/model/package_test.go (about) 1 package model 2 3 import ( 4 "encoding/json" 5 "reflect" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 11 "github.com/anchore/syft/syft/license" 12 "github.com/anchore/syft/syft/pkg" 13 ) 14 15 func Test_UnmarshalJSON(t *testing.T) { 16 tests := []struct { 17 name string 18 packageData []byte 19 assert func(*Package) 20 }{ 21 { 22 name: "unmarshal package metadata", 23 packageData: []byte(`{ 24 "id": "8b594519bc23da50", 25 "name": "gopkg.in/square/go-jose.v2", 26 "version": "v2.6.0", 27 "type": "go-module", 28 "foundBy": "go-module-binary-cataloger", 29 "locations": [ 30 { 31 "path": "/Users/hal/go/bin/syft" 32 } 33 ], 34 "licenses": [ 35 { 36 "value": "MIT", 37 "spdxExpression": "MIT", 38 "type": "declared", 39 "url": [] 40 } 41 ], 42 "language": "go", 43 "cpes": [], 44 "purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0", 45 "metadataType": "GolangBinMetadata", 46 "metadata": { 47 "goCompiledVersion": "go1.18", 48 "architecture": "amd64", 49 "h1Digest": "h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=" 50 } 51 }`), 52 assert: func(p *Package) { 53 require.NotNil(t, p.Metadata) 54 golangMetadata := p.Metadata.(pkg.GolangBinaryBuildinfoEntry) 55 require.NotEmpty(t, golangMetadata) 56 assert.Equal(t, "go1.18", golangMetadata.GoCompiledVersion) 57 }, 58 }, 59 { 60 name: "can handle package without metadata", 61 packageData: []byte(`{ 62 "id": "8b594519bc23da50", 63 "name": "gopkg.in/square/go-jose.v2", 64 "version": "v2.6.0", 65 "type": "go-module", 66 "foundBy": "go-mod-cataloger", 67 "locations": [ 68 { 69 "path": "/Users/hal/go/bin/syft" 70 } 71 ], 72 "licenses": [ 73 { 74 "value": "MIT", 75 "spdxExpression": "MIT", 76 "type": "declared", 77 "url": ["https://www.github.com"] 78 }, 79 { 80 "value": "MIT", 81 "spdxExpression": "MIT", 82 "type": "declared", 83 "locations": [{"path": "/Users/hal/go/bin/syft"}] 84 } 85 ], 86 "language": "go", 87 "cpes": [], 88 "purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0" 89 }`), 90 assert: func(p *Package) { 91 assert.Empty(t, p.MetadataType) 92 assert.Empty(t, p.Metadata) 93 }, 94 }, 95 { 96 name: "can handle package with []string licenses", 97 packageData: []byte(`{ 98 "id": "8b594519bc23da50", 99 "name": "gopkg.in/square/go-jose.v2", 100 "version": "v2.6.0", 101 "type": "go-module", 102 "foundBy": "go-mod-cataloger", 103 "locations": [ 104 { 105 "path": "/Users/hal/go/bin/syft" 106 } 107 ], 108 "licenses": ["MIT", "Apache-2.0"], 109 "language": "go", 110 "cpes": [], 111 "purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0" 112 }`), 113 assert: func(p *Package) { 114 assert.Equal(t, licenses{ 115 { 116 Value: "MIT", 117 SPDXExpression: "MIT", 118 Type: license.Declared, 119 }, 120 { 121 Value: "Apache-2.0", 122 SPDXExpression: "Apache-2.0", 123 Type: license.Declared, 124 }, 125 }, p.Licenses) 126 }, 127 }, 128 { 129 name: "can handle package with []pkg.License licenses", 130 packageData: []byte(`{ 131 "id": "8b594519bc23da50", 132 "name": "gopkg.in/square/go-jose.v2", 133 "version": "v2.6.0", 134 "type": "go-module", 135 "foundBy": "go-mod-cataloger", 136 "locations": [ 137 { 138 "path": "/Users/hal/go/bin/syft" 139 } 140 ], 141 "licenses": [ 142 { 143 "value": "MIT", 144 "spdxExpression": "MIT", 145 "type": "declared" 146 }, 147 { 148 "value": "Apache-2.0", 149 "spdxExpression": "Apache-2.0", 150 "type": "declared" 151 } 152 ], 153 "language": "go", 154 "cpes": [], 155 "purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0" 156 }`), 157 assert: func(p *Package) { 158 assert.Equal(t, licenses{ 159 { 160 Value: "MIT", 161 SPDXExpression: "MIT", 162 Type: license.Declared, 163 }, 164 { 165 Value: "Apache-2.0", 166 SPDXExpression: "Apache-2.0", 167 Type: license.Declared, 168 }, 169 }, p.Licenses) 170 }, 171 }, 172 { 173 name: "breaking v11-v12 schema change: rpm db vs archive (select db)", 174 packageData: []byte(`{ 175 "id": "739158935bfffc4d", 176 "name": "dbus", 177 "version": "1:1.12.8-12.el8", 178 "type": "rpm", 179 "foundBy": "rpm-db-cataloger", 180 "locations": [ 181 { 182 "path": "/var/lib/rpm/Packages", 183 "layerID": "sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a", 184 "annotations": { 185 "evidence": "primary" 186 } 187 } 188 ], 189 "licenses": [], 190 "language": "", 191 "cpes": [], 192 "purl": "pkg:rpm/centos/dbus@1.12.8-12.el8?arch=aarch64&epoch=1&upstream=dbus-1.12.8-12.el8.src.rpm&distro=centos-8", 193 "metadataType": "RpmMetadata", 194 "metadata": { 195 "name": "dbus", 196 "version": "1.12.8", 197 "epoch": 1, 198 "architecture": "aarch64", 199 "release": "12.el8", 200 "sourceRpm": "dbus-1.12.8-12.el8.src.rpm", 201 "size": 0, 202 "vendor": "CentOS", 203 "modularityLabel": "", 204 "files": [] 205 } 206 } 207 `), 208 assert: func(p *Package) { 209 assert.Equal(t, pkg.RpmPkg, p.Type) 210 assert.Equal(t, reflect.TypeOf(pkg.RpmDBEntry{}).Name(), reflect.TypeOf(p.Metadata).Name()) 211 }, 212 }, 213 { 214 name: "breaking v11-v12 schema change: rpm db vs archive (select archive)", 215 packageData: []byte(`{ 216 "id": "739158935bfffc4d", 217 "name": "dbus", 218 "version": "1:1.12.8-12.el8", 219 "type": "rpm", 220 "foundBy": "rpm-db-cataloger", 221 "locations": [ 222 { 223 "path": "/var/cache/dbus-1.12.8-12.el8.rpm", 224 "layerID": "sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a", 225 "annotations": { 226 "evidence": "primary" 227 } 228 } 229 ], 230 "licenses": [], 231 "language": "", 232 "cpes": [], 233 "purl": "pkg:rpm/centos/dbus@1.12.8-12.el8?arch=aarch64&epoch=1&upstream=dbus-1.12.8-12.el8.src.rpm&distro=centos-8", 234 "metadataType": "RpmMetadata", 235 "metadata": { 236 "name": "dbus", 237 "version": "1.12.8", 238 "epoch": 1, 239 "architecture": "aarch64", 240 "release": "12.el8", 241 "sourceRpm": "dbus-1.12.8-12.el8.src.rpm", 242 "size": 0, 243 "vendor": "CentOS", 244 "modularityLabel": "", 245 "files": [] 246 } 247 } 248 `), 249 assert: func(p *Package) { 250 assert.Equal(t, pkg.RpmPkg, p.Type) 251 assert.Equal(t, reflect.TypeOf(pkg.RpmArchive{}).Name(), reflect.TypeOf(p.Metadata).Name()) 252 }, 253 }, 254 { 255 name: "breaking v11-v12 schema change: stack.yaml vs stack.yaml.lock (select stack.yaml)", 256 packageData: []byte(`{ 257 "id": "46ff1a71f7715f38", 258 "name": "hspec-discover", 259 "version": "2.9.4", 260 "type": "hackage", 261 "foundBy": "haskell-cataloger", 262 "locations": [ 263 { 264 "path": "/stack.yaml", 265 "annotations": { 266 "evidence": "primary" 267 } 268 } 269 ], 270 "licenses": [], 271 "language": "haskell", 272 "cpes": [ 273 "cpe:2.3:a:hspec-discover:hspec-discover:2.9.4:*:*:*:*:*:*:*", 274 "cpe:2.3:a:hspec-discover:hspec_discover:2.9.4:*:*:*:*:*:*:*", 275 "cpe:2.3:a:hspec_discover:hspec-discover:2.9.4:*:*:*:*:*:*:*", 276 "cpe:2.3:a:hspec_discover:hspec_discover:2.9.4:*:*:*:*:*:*:*", 277 "cpe:2.3:a:hspec:hspec-discover:2.9.4:*:*:*:*:*:*:*", 278 "cpe:2.3:a:hspec:hspec_discover:2.9.4:*:*:*:*:*:*:*" 279 ], 280 "purl": "pkg:hackage/hspec-discover@2.9.4", 281 "metadataType": "HackageMetadataType", 282 "metadata": { 283 "name": "", 284 "version": "", 285 "pkgHash": "fbcf49ecfc3d4da53e797fd0275264cba776ffa324ee223e2a3f4ec2d2c9c4a6" 286 } 287 }`), 288 assert: func(p *Package) { 289 assert.Equal(t, pkg.HackagePkg, p.Type) 290 assert.Equal(t, reflect.TypeOf(pkg.HackageStackYamlEntry{}).Name(), reflect.TypeOf(p.Metadata).Name()) 291 }, 292 }, 293 { 294 name: "breaking v11-v12 schema change: stack.yaml vs stack.yaml.lock (select stack.yaml.lock)", 295 packageData: []byte(`{ 296 "id": "87939e95124ceb92", 297 "name": "optparse-applicative", 298 "version": "0.16.1.0", 299 "type": "hackage", 300 "foundBy": "haskell-cataloger", 301 "locations": [ 302 { 303 "path": "/stack.yaml.lock", 304 "annotations": { 305 "evidence": "primary" 306 } 307 } 308 ], 309 "licenses": [], 310 "language": "haskell", 311 "cpes": [ 312 "cpe:2.3:a:optparse-applicative:optparse-applicative:0.16.1.0:*:*:*:*:*:*:*", 313 "cpe:2.3:a:optparse-applicative:optparse_applicative:0.16.1.0:*:*:*:*:*:*:*", 314 "cpe:2.3:a:optparse_applicative:optparse-applicative:0.16.1.0:*:*:*:*:*:*:*", 315 "cpe:2.3:a:optparse_applicative:optparse_applicative:0.16.1.0:*:*:*:*:*:*:*", 316 "cpe:2.3:a:optparse:optparse-applicative:0.16.1.0:*:*:*:*:*:*:*", 317 "cpe:2.3:a:optparse:optparse_applicative:0.16.1.0:*:*:*:*:*:*:*" 318 ], 319 "purl": "pkg:hackage/optparse-applicative@0.16.1.0", 320 "metadataType": "HackageMetadataType", 321 "metadata": { 322 "name": "", 323 "version": "", 324 "pkgHash": "418c22ed6a19124d457d96bc66bd22c93ac22fad0c7100fe4972bbb4ac989731", 325 "snapshotURL": "https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/14.yaml" 326 } 327 }`), 328 assert: func(p *Package) { 329 assert.Equal(t, pkg.HackagePkg, p.Type) 330 assert.Equal(t, reflect.TypeOf(pkg.HackageStackYamlLockEntry{}).Name(), reflect.TypeOf(p.Metadata).Name()) 331 }, 332 }, 333 { 334 name: "breaking v11-v12 schema change: rust cargo.lock vs audit (select cargo.lock)", 335 packageData: []byte(`{ 336 "id": "95124ceb9287939e", 337 "name": "optpkg", 338 "version": "1.16.1", 339 "type": "hackage", 340 "foundBy": "rust-cataloger", 341 "locations": [ 342 { 343 "path": "/cargo.lock", 344 "annotations": { 345 "evidence": "primary" 346 } 347 } 348 ], 349 "licenses": [], 350 "language": "rust", 351 "cpes": [], 352 "purl": "pkg:cargo/optpkg@1.16.1", 353 "metadataType": "RustCargoPackageMetadata", 354 "metadata": {} 355 }`), 356 assert: func(p *Package) { 357 assert.Equal(t, pkg.HackagePkg, p.Type) 358 assert.Equal(t, reflect.TypeOf(pkg.RustCargoLockEntry{}).Name(), reflect.TypeOf(p.Metadata).Name()) 359 }, 360 }, 361 { 362 name: "breaking v11-v12 schema change: rust cargo.lock vs audit (select audit binary)", 363 packageData: []byte(`{ 364 "id": "95124ceb9287939e", 365 "name": "optpkg", 366 "version": "1.16.1", 367 "type": "hackage", 368 "foundBy": "rust-cataloger", 369 "locations": [ 370 { 371 "path": "/my-binary", 372 "annotations": { 373 "evidence": "primary" 374 } 375 } 376 ], 377 "licenses": [], 378 "language": "rust", 379 "cpes": [], 380 "purl": "pkg:cargo/optpkg@1.16.1", 381 "metadataType": "RustCargoPackageMetadata", 382 "metadata": {} 383 }`), 384 assert: func(p *Package) { 385 assert.Equal(t, pkg.HackagePkg, p.Type) 386 assert.Equal(t, reflect.TypeOf(pkg.RustBinaryAuditEntry{}).Name(), reflect.TypeOf(p.Metadata).Name()) 387 }, 388 }, 389 } 390 391 for _, test := range tests { 392 t.Run(test.name, func(t *testing.T) { 393 p := &Package{} 394 err := p.UnmarshalJSON(test.packageData) 395 require.NoError(t, err) 396 test.assert(p) 397 }) 398 } 399 } 400 401 func Test_unpackMetadata(t *testing.T) { 402 tests := []struct { 403 name string 404 packageData []byte 405 wantMetadata any 406 wantErr require.ErrorAssertionFunc 407 }{ 408 { 409 name: "unmarshal package metadata", 410 wantMetadata: pkg.GolangBinaryBuildinfoEntry{}, 411 packageData: []byte(`{ 412 "id": "8b594519bc23da50", 413 "name": "gopkg.in/square/go-jose.v2", 414 "version": "v2.6.0", 415 "type": "go-module", 416 "foundBy": "go-module-binary-cataloger", 417 "locations": [ 418 { 419 "path": "/Users/hal/go/bin/syft" 420 } 421 ], 422 "licenses": [], 423 "language": "go", 424 "cpes": [], 425 "purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0", 426 "metadataType": "GolangBinMetadata", 427 "metadata": { 428 "goCompiledVersion": "go1.18", 429 "architecture": "amd64", 430 "h1Digest": "h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=" 431 } 432 }`), 433 }, 434 { 435 name: "can handle package without metadata", 436 wantMetadata: nil, 437 packageData: []byte(`{ 438 "id": "8b594519bc23da50", 439 "name": "gopkg.in/square/go-jose.v2", 440 "version": "v2.6.0", 441 "type": "go-module", 442 "foundBy": "go-mod-cataloger", 443 "locations": [ 444 { 445 "path": "/Users/hal/go/bin/syft" 446 } 447 ], 448 "licenses": [], 449 "language": "go", 450 "cpes": [], 451 "purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0" 452 }`), 453 }, 454 { 455 name: "can handle RpmdbMetadata", 456 wantMetadata: pkg.RpmDBEntry{}, 457 packageData: []byte(`{ 458 "id": "4ac699c3b8fe1835", 459 "name": "acl", 460 "version": "2.2.53-1.el8", 461 "type": "rpm", 462 "foundBy": "rpm-db-cataloger", 463 "locations": [ 464 { 465 "path": "/var/lib/rpm/Packages", 466 "layerID": "sha256:74ddd0ec08fa43d09f32636ba91a0a3053b02cb4627c35051aff89f853606b59" 467 } 468 ], 469 "language": "", 470 "cpes": [ 471 "cpe:2.3:a:centos:acl:2.2.53-1.el8:*:*:*:*:*:*:*", 472 "cpe:2.3:a:acl:acl:2.2.53-1.el8:*:*:*:*:*:*:*" 473 ], 474 "purl": "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8", 475 "metadataType": "RpmdbMetadata", 476 "metadata": { 477 "name": "acl", 478 "version": "2.2.53", 479 "epoch": null, 480 "architecture": "x86_64", 481 "release": "1.el8", 482 "sourceRpm": "acl-2.2.53-1.el8.src.rpm", 483 "size": 205740, 484 "license": "GPLv2+", 485 "vendor": "CentOS", 486 "modularityLabel": "" 487 } 488 }`), 489 }, 490 { 491 name: "bad metadata type is an error", 492 wantErr: require.Error, 493 packageData: []byte(`{ 494 "id": "8b594519bc23da50", 495 "name": "gopkg.in/square/go-jose.v2", 496 "version": "v2.6.0", 497 "type": "go-module", 498 "foundBy": "go-mod-cataloger", 499 "locations": [ 500 { 501 "path": "/Users/hal/go/bin/syft" 502 } 503 ], 504 "licenses": [], 505 "language": "go", 506 "cpes": [], 507 "purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0", 508 "metadataType": "BOGOSITY" 509 }`), 510 }, 511 { 512 name: "unknown metadata type", 513 packageData: []byte(`{ 514 "metadataType": "NewMetadataType", 515 "metadata": { 516 "thing": "thing-1" 517 } 518 }`), 519 wantErr: require.Error, 520 wantMetadata: map[string]interface{}{ 521 "thing": "thing-1", 522 }, 523 }, 524 { 525 name: "can handle package with metadata type but missing metadata", 526 packageData: []byte(`{ 527 "metadataType": "GolangBinMetadata" 528 }`), 529 wantMetadata: pkg.GolangBinaryBuildinfoEntry{}, 530 }, 531 { 532 name: "can handle package with golang bin metadata type", 533 packageData: []byte(`{ 534 "metadataType": "GolangBinMetadata" 535 }`), 536 wantMetadata: pkg.GolangBinaryBuildinfoEntry{}, 537 }, 538 { 539 name: "can handle package with unknown metadata type and missing metadata", 540 packageData: []byte(`{ 541 "metadataType": "BadMetadata" 542 }`), 543 wantErr: require.Error, 544 }, 545 { 546 name: "can handle package with unknown metadata type and metadata", 547 packageData: []byte(`{ 548 "metadataType": "BadMetadata", 549 "metadata": { 550 "random": "thing" 551 } 552 }`), 553 wantErr: require.Error, 554 }, 555 } 556 557 for _, test := range tests { 558 t.Run(test.name, func(t *testing.T) { 559 if test.wantErr == nil { 560 test.wantErr = require.NoError 561 } 562 p := &Package{} 563 564 var unpacker packageMetadataUnpacker 565 require.NoError(t, json.Unmarshal(test.packageData, &unpacker)) 566 567 err := unpackPkgMetadata(p, unpacker) 568 test.wantErr(t, err) 569 570 if test.wantMetadata != nil { 571 if p.Metadata == nil { 572 t.Fatalf("expected metadata to be populated") 573 return 574 } 575 assert.Equal(t, reflect.TypeOf(test.wantMetadata).Name(), reflect.TypeOf(p.Metadata).Name()) 576 } 577 }) 578 } 579 }