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  }