github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/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  			name: "map-based java metadata",
   391  			packageData: []byte(`{
   392    "id": "e6f845bdaa69ddb2",
   393    "name": "SparseBitSet",
   394    "version": "1.2",
   395    "type": "java-archive",
   396    "foundBy": "java-archive-cataloger",
   397    "locations": [],
   398    "licenses": [],
   399    "language": "java",
   400    "cpes": [],
   401    "purl": "pkg:maven/com.zaxxer/SparseBitSet@1.2",
   402    "metadataType": "java-archive",
   403    "metadata": {
   404      "virtualPath": "/opt/solr-9.4.1/modules/extraction/lib/SparseBitSet-1.2.jar",
   405      "manifest": {
   406        "main": {
   407          "Archiver-Version": "Plexus Archiver",
   408          "Build-Jdk": "1.8.0_73",
   409          "Built-By": "lbayer",
   410          "Created-By": "Apache Maven 3.5.0",
   411          "Manifest-Version": "1.0"
   412        },
   413        "namedSections": {
   414          "META-INF/mailcap": {
   415            "SHA-256-Digest": "kXN4VupOQOJhduMGwxumj4ijmD/YAlz97a9Mp7CVXtk="
   416          },
   417          "META-INF/versions/9/module-info.class": {
   418            "SHA-256-Digest": "cMeIRa5l8DWPgrVWavr/6TKVBUGixVKGcu6yOTZMlKk="
   419          }
   420        }
   421      }
   422    }
   423  }`),
   424  			assert: func(p *Package) {
   425  				meta := p.Metadata.(pkg.JavaArchive)
   426  				manifest := meta.Manifest
   427  				assert.Equal(t, "1.8.0_73", manifest.Main.MustGet("Build-Jdk"))
   428  				require.Equal(t, 2, len(manifest.Sections))
   429  				assert.Equal(t, "META-INF/mailcap", manifest.Sections[0].MustGet("Name"))
   430  				assert.Equal(t, "kXN4VupOQOJhduMGwxumj4ijmD/YAlz97a9Mp7CVXtk=", manifest.Sections[0].MustGet("SHA-256-Digest"))
   431  				assert.Equal(t, "META-INF/versions/9/module-info.class", manifest.Sections[1].MustGet("Name"))
   432  				assert.Equal(t, "cMeIRa5l8DWPgrVWavr/6TKVBUGixVKGcu6yOTZMlKk=", manifest.Sections[1].MustGet("SHA-256-Digest"))
   433  			},
   434  		},
   435  		{
   436  			name: "pre key-value golang metadata",
   437  			packageData: []byte(`{
   438    "id": "e348ed25484a94c9",
   439    "name": "github.com/anchore/syft",
   440    "version": "v0.101.1-SNAPSHOT-4c777834",
   441    "type": "go-module",
   442    "foundBy": "go-module-binary-cataloger",
   443    "locations": [
   444      {
   445        "path": "/syft",
   446        "layerID": "sha256:2274947a5f3527e48d8725a96646aefdcce3d99340c1eefb1e7c894043863c92",
   447        "accessPath": "/syft",
   448        "annotations": {
   449          "evidence": "primary"
   450        }
   451      }
   452    ],
   453    "licenses": [],
   454    "language": "go",
   455    "cpes": [
   456      "cpe:2.3:a:anchore:syft:v0.101.1-SNAPSHOT-4c777834:*:*:*:*:*:*:*"
   457    ],
   458    "purl": "pkg:golang/github.com/anchore/syft@v0.101.1-SNAPSHOT-4c777834",
   459    "metadataType": "go-module-buildinfo-entry",
   460    "metadata": {
   461      "goBuildSettings": {
   462        "-buildmode": "exe",
   463        "-compiler": "gc",
   464        "-ldflags": "-w -s -extldflags '-static' -X main.version=0.101.1-SNAPSHOT-4c777834 -X main.gitCommit=4c777834618b2ad8ad94cd200a45d6670bc1c013 -X main.buildDate=2024-01-22T16:43:49Z -X main.gitDescription=v0.101.1-4-g4c777834 ",
   465        "CGO_ENABLED": "0",
   466        "GOAMD64": "v1",
   467        "GOARCH": "amd64",
   468        "GOOS": "linux",
   469        "vcs": "git",
   470        "vcs.modified": "false",
   471        "vcs.revision": "4c777834618b2ad8ad94cd200a45d6670bc1c013",
   472        "vcs.time": "2024-01-22T16:31:41Z"
   473      },
   474      "goCompiledVersion": "go1.21.2",
   475      "architecture": "amd64",
   476      "mainModule": "github.com/anchore/syft"
   477    }
   478  }
   479  `),
   480  			assert: func(p *Package) {
   481  				buildInfo := p.Metadata.(pkg.GolangBinaryBuildinfoEntry)
   482  				assert.Equal(t, "exe", buildInfo.BuildSettings.MustGet("-buildmode"))
   483  			},
   484  		},
   485  		{
   486  			name: "conan lock with legacy options",
   487  			packageData: []byte(`{
   488    "id": "75eb35307226c921",
   489    "name": "boost",
   490    "version": "1.75.0",
   491    "type": "conan",
   492    "foundBy": "conan-cataloger",
   493    "locations": [
   494      {
   495        "path": "/conan.lock",
   496        "accessPath": "/conan.lock",
   497        "annotations": {
   498          "evidence": "primary"
   499        }
   500      }
   501    ],
   502    "licenses": [],
   503    "language": "c++",
   504    "cpes": [
   505      "cpe:2.3:a:boost:boost:1.75.0:*:*:*:*:*:*:*"
   506    ],
   507    "purl": "pkg:conan/boost@1.75.0",
   508    "metadataType": "c-conan-lock-entry",
   509    "metadata": {
   510      "ref": "boost/1.75.0#a9c318f067216f900900e044e7af4ab1",
   511      "package_id": "dc8aedd23a0f0a773a5fcdcfe1ae3e89c4205978",
   512      "prev": "b9d7912e6131dfa453c725593b36c808",
   513      "options": {
   514        "addr2line_location": "/usr/bin/addr2line",
   515        "asio_no_deprecated": "False",
   516        "zstd": "False"
   517      },
   518      "context": "host"
   519    }
   520  }`),
   521  			assert: func(p *Package) {
   522  				metadata := p.Metadata.(pkg.ConanV1LockEntry)
   523  				assert.Equal(t, "False", metadata.Options.MustGet("asio_no_deprecated"))
   524  			},
   525  		},
   526  	}
   527  
   528  	for _, test := range tests {
   529  		t.Run(test.name, func(t *testing.T) {
   530  			p := &Package{}
   531  			err := p.UnmarshalJSON(test.packageData)
   532  			require.NoError(t, err)
   533  			test.assert(p)
   534  		})
   535  	}
   536  }
   537  
   538  func Test_unpackMetadata(t *testing.T) {
   539  	tests := []struct {
   540  		name         string
   541  		packageData  []byte
   542  		wantMetadata any
   543  		wantErr      require.ErrorAssertionFunc
   544  	}{
   545  		{
   546  			name:         "unmarshal package metadata",
   547  			wantMetadata: pkg.GolangBinaryBuildinfoEntry{},
   548  			packageData: []byte(`{
   549  				"id": "8b594519bc23da50",
   550  				"name": "gopkg.in/square/go-jose.v2",
   551  				"version": "v2.6.0",
   552  				"type": "go-module",
   553  				"foundBy": "go-module-binary-cataloger",
   554  				"locations": [
   555  				  {
   556  				    "path": "/Users/hal/go/bin/syft"
   557  				  }
   558  				],
   559  				"licenses": [],
   560  				"language": "go",
   561  				"cpes": [],
   562  				"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0",
   563  				"metadataType": "GolangBinMetadata",
   564  				"metadata": {
   565  				  "goCompiledVersion": "go1.18",
   566  				  "architecture": "amd64",
   567  				  "h1Digest": "h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI="
   568  				}
   569  			}`),
   570  		},
   571  		{
   572  			name:         "can handle package without metadata",
   573  			wantMetadata: nil,
   574  			packageData: []byte(`{
   575  				"id": "8b594519bc23da50",
   576  				"name": "gopkg.in/square/go-jose.v2",
   577  				"version": "v2.6.0",
   578  				"type": "go-module",
   579  				"foundBy": "go-mod-cataloger",
   580  				"locations": [
   581  				  {
   582  				    "path": "/Users/hal/go/bin/syft"
   583  				  }
   584  				],
   585  				"licenses": [],
   586  				"language": "go",
   587  				"cpes": [],
   588  				"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0"
   589  			}`),
   590  		},
   591  		{
   592  			name:         "can handle RpmdbMetadata",
   593  			wantMetadata: pkg.RpmDBEntry{},
   594  			packageData: []byte(`{
   595  				"id": "4ac699c3b8fe1835",
   596  				"name": "acl",
   597  				"version": "2.2.53-1.el8",
   598  				"type": "rpm",
   599  				"foundBy": "rpm-db-cataloger",
   600  				"locations": [
   601  					{
   602  					 "path": "/var/lib/rpm/Packages",
   603  					 "layerID": "sha256:74ddd0ec08fa43d09f32636ba91a0a3053b02cb4627c35051aff89f853606b59"
   604  					}
   605  				],
   606  				"language": "",
   607  				"cpes": [
   608  					"cpe:2.3:a:centos:acl:2.2.53-1.el8:*:*:*:*:*:*:*",
   609  					"cpe:2.3:a:acl:acl:2.2.53-1.el8:*:*:*:*:*:*:*"
   610  				],
   611  				"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",
   612  				"metadataType": "RpmdbMetadata",
   613  				"metadata": {
   614  					"name": "acl",
   615  					"version": "2.2.53",
   616  					"epoch": null,
   617  					"architecture": "x86_64",
   618  					"release": "1.el8",
   619  					"sourceRpm": "acl-2.2.53-1.el8.src.rpm",
   620  					"size": 205740,
   621  					"license": "GPLv2+",
   622  					"vendor": "CentOS",
   623  					"modularityLabel": ""
   624  				}
   625  			}`),
   626  		},
   627  		{
   628  			name:    "bad metadata type is an error",
   629  			wantErr: require.Error,
   630  			packageData: []byte(`{
   631  				"id": "8b594519bc23da50",
   632  				"name": "gopkg.in/square/go-jose.v2",
   633  				"version": "v2.6.0",
   634  				"type": "go-module",
   635  				"foundBy": "go-mod-cataloger",
   636  				"locations": [
   637  				  {
   638  				    "path": "/Users/hal/go/bin/syft"
   639  				  }
   640  				],
   641  				"licenses": [],
   642  				"language": "go",
   643  				"cpes": [],
   644  				"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0",
   645  				"metadataType": "BOGOSITY"
   646  			}`),
   647  		},
   648  		{
   649  			name: "unknown metadata type",
   650  			packageData: []byte(`{
   651  				"metadataType": "NewMetadataType",
   652  				"metadata": {
   653  					"thing": "thing-1"
   654  				}
   655  			}`),
   656  			wantErr: require.Error,
   657  			wantMetadata: map[string]interface{}{
   658  				"thing": "thing-1",
   659  			},
   660  		},
   661  		{
   662  			name: "can handle package with metadata type but missing metadata",
   663  			packageData: []byte(`{
   664  				"metadataType": "GolangBinMetadata"
   665  			}`),
   666  			wantMetadata: pkg.GolangBinaryBuildinfoEntry{},
   667  		},
   668  		{
   669  			name: "can handle package with golang bin metadata type",
   670  			packageData: []byte(`{
   671  				"metadataType": "GolangBinMetadata"
   672  			}`),
   673  			wantMetadata: pkg.GolangBinaryBuildinfoEntry{},
   674  		},
   675  		{
   676  			name: "can handle package with unknown metadata type and missing metadata",
   677  			packageData: []byte(`{
   678  				"metadataType": "BadMetadata"
   679  			}`),
   680  			wantErr: require.Error,
   681  		},
   682  		{
   683  			name: "can handle package with unknown metadata type and metadata",
   684  			packageData: []byte(`{
   685  				"metadataType": "BadMetadata",
   686  				"metadata": {
   687  					"random": "thing"
   688  				}
   689  			}`),
   690  			wantErr: require.Error,
   691  		},
   692  	}
   693  
   694  	for _, test := range tests {
   695  		t.Run(test.name, func(t *testing.T) {
   696  			if test.wantErr == nil {
   697  				test.wantErr = require.NoError
   698  			}
   699  			p := &Package{}
   700  
   701  			var unpacker packageMetadataUnpacker
   702  			require.NoError(t, json.Unmarshal(test.packageData, &unpacker))
   703  
   704  			err := unpackPkgMetadata(p, unpacker)
   705  			test.wantErr(t, err)
   706  
   707  			if test.wantMetadata != nil {
   708  				if p.Metadata == nil {
   709  					t.Fatalf("expected metadata to be populated")
   710  					return
   711  				}
   712  				assert.Equal(t, reflect.TypeOf(test.wantMetadata).Name(), reflect.TypeOf(p.Metadata).Name())
   713  			}
   714  		})
   715  	}
   716  }