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  }