github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/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/nextlinux/gosbom/gosbom/artifact"
    11  	"github.com/nextlinux/gosbom/gosbom/sbom"
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  func Test_decode(t *testing.T) {
    16  	type expected struct {
    17  		os       string
    18  		pkg      string
    19  		ver      string
    20  		relation string
    21  		purl     string
    22  		cpe      string
    23  	}
    24  	tests := []struct {
    25  		name     string
    26  		input    cyclonedx.BOM
    27  		expected []expected
    28  	}{
    29  		{
    30  			name: "basic mapping from cyclonedx",
    31  			input: cyclonedx.BOM{
    32  				Metadata: nil,
    33  				Components: &[]cyclonedx.Component{
    34  					{
    35  						BOMRef:      "p1",
    36  						Type:        cyclonedx.ComponentTypeLibrary,
    37  						Name:        "package-1",
    38  						Version:     "1.0.1",
    39  						Description: "",
    40  						Hashes:      nil,
    41  						Licenses: &cyclonedx.Licenses{
    42  							{
    43  								License: &cyclonedx.License{
    44  									ID: "MIT",
    45  								},
    46  							},
    47  						},
    48  						CPE:        "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
    49  						PackageURL: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1",
    50  						ExternalReferences: &[]cyclonedx.ExternalReference{
    51  							{
    52  								URL:     "",
    53  								Comment: "",
    54  								Hashes:  nil,
    55  								Type:    "",
    56  							},
    57  						},
    58  						Properties: &[]cyclonedx.Property{
    59  							{
    60  								Name:  "foundBy",
    61  								Value: "the-cataloger-1",
    62  							},
    63  							{
    64  								Name:  "language",
    65  								Value: "python",
    66  							},
    67  							{
    68  								Name:  "type",
    69  								Value: "python",
    70  							},
    71  							{
    72  								Name:  "metadataType",
    73  								Value: "PythonPackageMetadata",
    74  							},
    75  							{
    76  								Name:  "path",
    77  								Value: "/some/path/pkg1",
    78  							},
    79  						},
    80  						Components: nil,
    81  						Evidence:   nil,
    82  					},
    83  					{
    84  						BOMRef:  "p2",
    85  						Type:    cyclonedx.ComponentTypeLibrary,
    86  						Name:    "package-2",
    87  						Version: "2.0.2",
    88  						Hashes:  nil,
    89  						Licenses: &cyclonedx.Licenses{
    90  							{
    91  								License: &cyclonedx.License{
    92  									ID: "MIT",
    93  								},
    94  							},
    95  						},
    96  						CPE:        "cpe:2.3:*:another:package:2:*:*:*:*:*:*:*",
    97  						PackageURL: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2",
    98  						Properties: &[]cyclonedx.Property{
    99  
   100  							{
   101  								Name:  "foundBy",
   102  								Value: "apkdb-cataloger",
   103  							},
   104  							{
   105  								Name:  "type",
   106  								Value: "apk",
   107  							},
   108  							{
   109  								Name:  "metadataType",
   110  								Value: "ApkMetadata",
   111  							},
   112  							{
   113  								Name:  "path",
   114  								Value: "/lib/apk/db/installed",
   115  							},
   116  							{
   117  								Name:  "layerID",
   118  								Value: "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635",
   119  							},
   120  							{
   121  								Name:  "originPackage",
   122  								Value: "zlib",
   123  							},
   124  							{
   125  								Name:  "size",
   126  								Value: "51213",
   127  							},
   128  							{
   129  								Name:  "installedSize",
   130  								Value: "110592",
   131  							},
   132  							{
   133  								Name:  "pullDependencies",
   134  								Value: "so:libc.musl-x86_64.so.1",
   135  							},
   136  							{
   137  								Name:  "pullChecksum",
   138  								Value: "Q1uss4DfpvL16Nw2YUTwmzGBABz3Y=",
   139  							},
   140  							{
   141  								Name:  "gitCommitOfApkPort",
   142  								Value: "d2bfb22c8e8f67ad7d8d02704f35ec4d2a19f9b9",
   143  							},
   144  						},
   145  					},
   146  					{
   147  						Type:    cyclonedx.ComponentTypeOS,
   148  						Name:    "debian",
   149  						Version: "1.2.3",
   150  						Hashes:  nil,
   151  						Licenses: &cyclonedx.Licenses{
   152  							{
   153  								License: &cyclonedx.License{
   154  									ID: "MIT",
   155  								},
   156  							},
   157  						},
   158  						Properties: &[]cyclonedx.Property{
   159  							{
   160  								Name:  "prettyName",
   161  								Value: "debian",
   162  							},
   163  							{
   164  								Name:  "id",
   165  								Value: "debian",
   166  							},
   167  							{
   168  								Name:  "versionID",
   169  								Value: "1.2.3",
   170  							},
   171  						},
   172  						Components: nil,
   173  						Evidence:   nil,
   174  					},
   175  				},
   176  				Dependencies: &[]cyclonedx.Dependency{
   177  					{
   178  						Ref:          "p1",
   179  						Dependencies: &[]string{"p2"},
   180  					},
   181  				},
   182  			},
   183  			expected: []expected{
   184  				{
   185  					os:  "debian",
   186  					ver: "1.2.3",
   187  				},
   188  				{
   189  					pkg:  "package-1",
   190  					ver:  "1.0.1",
   191  					cpe:  "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
   192  					purl: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1",
   193  				},
   194  				{
   195  					pkg:      "package-2",
   196  					ver:      "2.0.2",
   197  					purl:     "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2",
   198  					relation: "package-1",
   199  				},
   200  			},
   201  		},
   202  	}
   203  	for _, test := range tests {
   204  		t.Run(test.name, func(t *testing.T) {
   205  			sbom, err := ToGosbomModel(&test.input)
   206  			assert.NoError(t, err)
   207  
   208  		test:
   209  			for _, e := range test.expected {
   210  				if e.os != "" {
   211  					assert.Equal(t, e.os, sbom.Artifacts.LinuxDistribution.ID)
   212  					assert.Equal(t, e.ver, sbom.Artifacts.LinuxDistribution.VersionID)
   213  				}
   214  				if e.pkg != "" {
   215  					for p := range sbom.Artifacts.Packages.Enumerate() {
   216  						if e.pkg != p.Name {
   217  							continue
   218  						}
   219  
   220  						assert.Equal(t, e.ver, p.Version)
   221  
   222  						if e.cpe != "" {
   223  							foundCPE := false
   224  							for _, c := range p.CPEs {
   225  								cstr := c.BindToFmtString()
   226  								if e.cpe == cstr {
   227  									foundCPE = true
   228  									break
   229  								}
   230  							}
   231  							if !foundCPE {
   232  								assert.Fail(t, fmt.Sprintf("CPE not found in package: %s", e.cpe))
   233  							}
   234  						}
   235  
   236  						if e.purl != "" {
   237  							assert.Equal(t, e.purl, p.PURL)
   238  						}
   239  
   240  						if e.relation != "" {
   241  							foundRelation := false
   242  							for _, r := range sbom.Relationships {
   243  								p := sbom.Artifacts.Packages.Package(r.To.ID())
   244  								if e.relation == p.Name {
   245  									foundRelation = true
   246  									break
   247  								}
   248  							}
   249  							if !foundRelation {
   250  								assert.Fail(t, fmt.Sprintf("relation not found: %s", e.relation))
   251  							}
   252  						}
   253  						continue test
   254  					}
   255  					assert.Fail(t, fmt.Sprintf("package should be present: %s", e.pkg))
   256  				}
   257  			}
   258  		})
   259  	}
   260  }
   261  
   262  func Test_relationshipDirection(t *testing.T) {
   263  	cyclonedx_bom := cyclonedx.BOM{Metadata: nil,
   264  		Components: &[]cyclonedx.Component{
   265  			{
   266  				BOMRef:     "p1",
   267  				Type:       cyclonedx.ComponentTypeLibrary,
   268  				Name:       "package-1",
   269  				Version:    "1.0.1",
   270  				PackageURL: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1",
   271  			},
   272  			{
   273  				BOMRef:     "p2",
   274  				Type:       cyclonedx.ComponentTypeLibrary,
   275  				Name:       "package-2",
   276  				Version:    "2.0.2",
   277  				PackageURL: "pkg:some/package-2@2.0.2?arch=arm64&upstream=upstream1&distro=alpine-1",
   278  			},
   279  		},
   280  		Dependencies: &[]cyclonedx.Dependency{
   281  			{
   282  				Ref:          "p1",
   283  				Dependencies: &[]string{"p2"},
   284  			},
   285  		}}
   286  	sbom, err := ToGosbomModel(&cyclonedx_bom)
   287  	assert.Nil(t, err)
   288  	assert.Len(t, sbom.Relationships, 1)
   289  	relationship := sbom.Relationships[0]
   290  
   291  	// check that p2 -- dependency of --> p1
   292  	// same as p1 -- depends on --> p2
   293  	assert.Equal(t, artifact.DependencyOfRelationship, relationship.Type)
   294  	assert.Equal(t, "package-2", packageNameFromIdentifier(sbom, relationship.From))
   295  	assert.Equal(t, "package-1", packageNameFromIdentifier(sbom, relationship.To))
   296  }
   297  
   298  func packageNameFromIdentifier(model *sbom.SBOM, identifier artifact.Identifiable) string {
   299  	return model.Artifacts.Packages.Package(identifier.ID()).Name
   300  }
   301  
   302  func Test_missingDataDecode(t *testing.T) {
   303  	bom := &cyclonedx.BOM{
   304  		Metadata:    nil,
   305  		Components:  &[]cyclonedx.Component{},
   306  		SpecVersion: cyclonedx.SpecVersion1_4,
   307  	}
   308  
   309  	_, err := ToGosbomModel(bom)
   310  	assert.NoError(t, err)
   311  
   312  	bom.Metadata = &cyclonedx.Metadata{}
   313  
   314  	_, err = ToGosbomModel(bom)
   315  	assert.NoError(t, err)
   316  
   317  	pkg := decodeComponent(&cyclonedx.Component{
   318  		Licenses: &cyclonedx.Licenses{
   319  			{
   320  				License: nil,
   321  			},
   322  		},
   323  	})
   324  	assert.Equal(t, pkg.Licenses.Empty(), true)
   325  }
   326  
   327  func Test_missingComponentsDecode(t *testing.T) {
   328  	bom := &cyclonedx.BOM{
   329  		SpecVersion: cyclonedx.SpecVersion1_4,
   330  	}
   331  	bomBytes, _ := json.Marshal(&bom)
   332  	decode := GetDecoder(cyclonedx.BOMFileFormatJSON)
   333  
   334  	_, err := decode(bytes.NewReader(bomBytes))
   335  
   336  	assert.NoError(t, err)
   337  }