github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/formats/common/spdxhelpers/to_format_model_test.go (about)

     1  package spdxhelpers
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"testing"
     7  
     8  	"github.com/nextlinux/gosbom/gosbom/artifact"
     9  	"github.com/nextlinux/gosbom/gosbom/file"
    10  	"github.com/nextlinux/gosbom/gosbom/pkg"
    11  	"github.com/nextlinux/gosbom/gosbom/sbom"
    12  	"github.com/spdx/tools-golang/spdx"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  // TODO: Add ToFormatModel tests
    18  func Test_toPackageChecksums(t *testing.T) {
    19  	tests := []struct {
    20  		name          string
    21  		pkg           pkg.Package
    22  		expected      []spdx.Checksum
    23  		filesAnalyzed bool
    24  	}{
    25  		{
    26  			name: "Java Package",
    27  			pkg: pkg.Package{
    28  				Name:     "test",
    29  				Version:  "1.0.0",
    30  				Language: pkg.Java,
    31  				Metadata: pkg.JavaMetadata{
    32  					ArchiveDigests: []file.Digest{
    33  						{
    34  							Algorithm: "sha1", // SPDX expects these to be uppercase
    35  							Value:     "1234",
    36  						},
    37  					},
    38  				},
    39  			},
    40  			expected: []spdx.Checksum{
    41  				{
    42  					Algorithm: "SHA1",
    43  					Value:     "1234",
    44  				},
    45  			},
    46  			filesAnalyzed: true,
    47  		},
    48  		{
    49  			name: "Java Package with no archive digests",
    50  			pkg: pkg.Package{
    51  				Name:     "test",
    52  				Version:  "1.0.0",
    53  				Language: pkg.Java,
    54  				Metadata: pkg.JavaMetadata{
    55  					ArchiveDigests: []file.Digest{},
    56  				},
    57  			},
    58  			expected:      []spdx.Checksum{},
    59  			filesAnalyzed: false,
    60  		},
    61  		{
    62  			name: "Java Package with no metadata",
    63  			pkg: pkg.Package{
    64  				Name:     "test",
    65  				Version:  "1.0.0",
    66  				Language: pkg.Java,
    67  			},
    68  			expected:      []spdx.Checksum{},
    69  			filesAnalyzed: false,
    70  		},
    71  		{
    72  			name: "Go Binary Package",
    73  			pkg: pkg.Package{
    74  				Name:         "test",
    75  				Version:      "1.0.0",
    76  				Language:     pkg.Go,
    77  				MetadataType: pkg.GolangBinMetadataType,
    78  				Metadata: pkg.GolangBinMetadata{
    79  					H1Digest: "h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
    80  				},
    81  			},
    82  			expected: []spdx.Checksum{
    83  				{
    84  					Algorithm: "SHA256",
    85  					Value:     "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
    86  				},
    87  			},
    88  			filesAnalyzed: false,
    89  		},
    90  		{
    91  			name: "Package with no metadata type",
    92  			pkg: pkg.Package{
    93  				Name:     "test",
    94  				Version:  "1.0.0",
    95  				Language: pkg.Java,
    96  				Metadata: struct{}{},
    97  			},
    98  			expected:      []spdx.Checksum{},
    99  			filesAnalyzed: false,
   100  		},
   101  	}
   102  
   103  	for _, test := range tests {
   104  		t.Run(test.name, func(t *testing.T) {
   105  			commonSum, filesAnalyzed := toPackageChecksums(test.pkg)
   106  			assert.ElementsMatch(t, test.expected, commonSum)
   107  			assert.Equal(t, test.filesAnalyzed, filesAnalyzed)
   108  		})
   109  	}
   110  }
   111  
   112  func Test_toFileTypes(t *testing.T) {
   113  
   114  	tests := []struct {
   115  		name     string
   116  		metadata file.Metadata
   117  		expected []string
   118  	}{
   119  		{
   120  			name: "application",
   121  			metadata: file.Metadata{
   122  				MIMEType: "application/vnd.unknown",
   123  			},
   124  			expected: []string{
   125  				string(ApplicationFileType),
   126  			},
   127  		},
   128  		{
   129  			name: "archive",
   130  			metadata: file.Metadata{
   131  				MIMEType: "application/zip",
   132  			},
   133  			expected: []string{
   134  				string(ApplicationFileType),
   135  				string(ArchiveFileType),
   136  			},
   137  		},
   138  		{
   139  			name: "audio",
   140  			metadata: file.Metadata{
   141  				MIMEType: "audio/ogg",
   142  			},
   143  			expected: []string{
   144  				string(AudioFileType),
   145  			},
   146  		},
   147  		{
   148  			name: "video",
   149  			metadata: file.Metadata{
   150  				MIMEType: "video/3gpp",
   151  			},
   152  			expected: []string{
   153  				string(VideoFileType),
   154  			},
   155  		},
   156  		{
   157  			name: "text",
   158  			metadata: file.Metadata{
   159  				MIMEType: "text/html",
   160  			},
   161  			expected: []string{
   162  				string(TextFileType),
   163  			},
   164  		},
   165  		{
   166  			name: "image",
   167  			metadata: file.Metadata{
   168  				MIMEType: "image/png",
   169  			},
   170  			expected: []string{
   171  				string(ImageFileType),
   172  			},
   173  		},
   174  		{
   175  			name: "binary",
   176  			metadata: file.Metadata{
   177  				MIMEType: "application/x-sharedlib",
   178  			},
   179  			expected: []string{
   180  				string(ApplicationFileType),
   181  				string(BinaryFileType),
   182  			},
   183  		},
   184  	}
   185  	for _, test := range tests {
   186  		t.Run(test.name, func(t *testing.T) {
   187  			assert.ElementsMatch(t, test.expected, toFileTypes(&test.metadata))
   188  		})
   189  	}
   190  }
   191  
   192  func Test_lookupRelationship(t *testing.T) {
   193  
   194  	tests := []struct {
   195  		input   artifact.RelationshipType
   196  		exists  bool
   197  		ty      RelationshipType
   198  		comment string
   199  	}{
   200  		{
   201  			input:  artifact.ContainsRelationship,
   202  			exists: true,
   203  			ty:     ContainsRelationship,
   204  		},
   205  		{
   206  			input:   artifact.OwnershipByFileOverlapRelationship,
   207  			exists:  true,
   208  			ty:      OtherRelationship,
   209  			comment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by",
   210  		},
   211  		{
   212  			input:   artifact.EvidentByRelationship,
   213  			exists:  true,
   214  			ty:      OtherRelationship,
   215  			comment: "evident-by: indicates the package's existence is evident by the given file",
   216  		},
   217  		{
   218  			input:  "made-up",
   219  			exists: false,
   220  		},
   221  	}
   222  	for _, test := range tests {
   223  		t.Run(string(test.input), func(t *testing.T) {
   224  			exists, ty, comment := lookupRelationship(test.input)
   225  			assert.Equal(t, exists, test.exists)
   226  			assert.Equal(t, ty, test.ty)
   227  			assert.Equal(t, comment, test.comment)
   228  		})
   229  	}
   230  }
   231  
   232  func Test_toFileChecksums(t *testing.T) {
   233  	tests := []struct {
   234  		name     string
   235  		digests  []file.Digest
   236  		expected []spdx.Checksum
   237  	}{
   238  		{
   239  			name: "empty",
   240  		},
   241  		{
   242  			name: "has digests",
   243  			digests: []file.Digest{
   244  				{
   245  					Algorithm: "SHA256",
   246  					Value:     "deadbeefcafe",
   247  				},
   248  				{
   249  					Algorithm: "md5",
   250  					Value:     "meh",
   251  				},
   252  			},
   253  			expected: []spdx.Checksum{
   254  				{
   255  					Algorithm: "SHA256",
   256  					Value:     "deadbeefcafe",
   257  				},
   258  				{
   259  					Algorithm: "MD5",
   260  					Value:     "meh",
   261  				},
   262  			},
   263  		},
   264  	}
   265  	for _, test := range tests {
   266  		t.Run(test.name, func(t *testing.T) {
   267  			assert.ElementsMatch(t, test.expected, toFileChecksums(test.digests))
   268  		})
   269  	}
   270  }
   271  
   272  func Test_fileIDsForPackage(t *testing.T) {
   273  	p := pkg.Package{
   274  		Name: "bogus",
   275  	}
   276  
   277  	c := file.Coordinates{
   278  		RealPath:     "/path",
   279  		FileSystemID: "nowhere",
   280  	}
   281  
   282  	docElementId := func(identifiable artifact.Identifiable) spdx.DocElementID {
   283  		return spdx.DocElementID{
   284  			ElementRefID: toSPDXID(identifiable),
   285  		}
   286  	}
   287  
   288  	tests := []struct {
   289  		name          string
   290  		relationships []artifact.Relationship
   291  		expected      []*spdx.Relationship
   292  	}{
   293  		{
   294  			name: "package-to-file contains relationships",
   295  			relationships: []artifact.Relationship{
   296  				{
   297  					From: p,
   298  					To:   c,
   299  					Type: artifact.ContainsRelationship,
   300  				},
   301  			},
   302  			expected: []*spdx.Relationship{
   303  				{
   304  					Relationship: "CONTAINS",
   305  					RefA:         docElementId(p),
   306  					RefB:         docElementId(c),
   307  				},
   308  			},
   309  		},
   310  		{
   311  			name: "package-to-package",
   312  			relationships: []artifact.Relationship{
   313  				{
   314  					From: p,
   315  					To:   p,
   316  					Type: artifact.ContainsRelationship,
   317  				},
   318  			},
   319  			expected: []*spdx.Relationship{
   320  				{
   321  					Relationship: "CONTAINS",
   322  					RefA:         docElementId(p),
   323  					RefB:         docElementId(p),
   324  				},
   325  			},
   326  		},
   327  		{
   328  			name: "ignore file-to-file",
   329  			relationships: []artifact.Relationship{
   330  				{
   331  					From: c,
   332  					To:   c,
   333  					Type: artifact.ContainsRelationship,
   334  				},
   335  			},
   336  			expected: nil,
   337  		},
   338  		{
   339  			name: "ignore file-to-package",
   340  			relationships: []artifact.Relationship{
   341  				{
   342  					From: c,
   343  					To:   p,
   344  					Type: artifact.ContainsRelationship,
   345  				},
   346  			},
   347  			expected: nil,
   348  		},
   349  		{
   350  			name: "include package-to-file overlap relationships",
   351  			relationships: []artifact.Relationship{
   352  				{
   353  					From: p,
   354  					To:   c,
   355  					Type: artifact.OwnershipByFileOverlapRelationship,
   356  				},
   357  			},
   358  			expected: []*spdx.Relationship{
   359  				{
   360  					Relationship:        "OTHER",
   361  					RefA:                docElementId(p),
   362  					RefB:                docElementId(c),
   363  					RelationshipComment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by",
   364  				},
   365  			},
   366  		},
   367  	}
   368  	for _, test := range tests {
   369  		t.Run(test.name, func(t *testing.T) {
   370  			relationships := toRelationships(test.relationships)
   371  			assert.Equal(t, test.expected, relationships)
   372  		})
   373  	}
   374  }
   375  
   376  func Test_H1Digest(t *testing.T) {
   377  	s := sbom.SBOM{}
   378  	tests := []struct {
   379  		name           string
   380  		pkg            pkg.Package
   381  		expectedDigest string
   382  	}{
   383  		{
   384  			name: "valid h1digest",
   385  			pkg: pkg.Package{
   386  				Name:         "github.com/googleapis/gnostic",
   387  				Version:      "v0.5.5",
   388  				MetadataType: pkg.GolangBinMetadataType,
   389  				Metadata: pkg.GolangBinMetadata{
   390  					H1Digest: "h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
   391  				},
   392  			},
   393  			expectedDigest: "SHA256:f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
   394  		},
   395  		{
   396  			name: "invalid h1digest",
   397  			pkg: pkg.Package{
   398  				Name:         "github.com/googleapis/gnostic",
   399  				Version:      "v0.5.5",
   400  				MetadataType: pkg.GolangBinMetadataType,
   401  				Metadata: pkg.GolangBinMetadata{
   402  					H1Digest: "h1:9fHAtK0uzzz",
   403  				},
   404  			},
   405  			expectedDigest: "",
   406  		},
   407  		{
   408  			name: "unsupported h-digest",
   409  			pkg: pkg.Package{
   410  				Name:         "github.com/googleapis/gnostic",
   411  				Version:      "v0.5.5",
   412  				MetadataType: pkg.GolangBinMetadataType,
   413  				Metadata: pkg.GolangBinMetadata{
   414  					H1Digest: "h12:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
   415  				},
   416  			},
   417  			expectedDigest: "",
   418  		},
   419  	}
   420  
   421  	for _, test := range tests {
   422  		t.Run(test.name, func(t *testing.T) {
   423  			catalog := pkg.NewCollection(test.pkg)
   424  			pkgs := toPackages(catalog, s)
   425  			require.Len(t, pkgs, 1)
   426  			for _, p := range pkgs {
   427  				if test.expectedDigest == "" {
   428  					require.Len(t, p.PackageChecksums, 0)
   429  				} else {
   430  					require.Len(t, p.PackageChecksums, 1)
   431  					for _, c := range p.PackageChecksums {
   432  						require.Equal(t, test.expectedDigest, fmt.Sprintf("%s:%s", c.Algorithm, c.Value))
   433  					}
   434  				}
   435  			}
   436  		})
   437  	}
   438  }
   439  
   440  func Test_OtherLicenses(t *testing.T) {
   441  	tests := []struct {
   442  		name     string
   443  		pkg      pkg.Package
   444  		expected []*spdx.OtherLicense
   445  	}{
   446  		{
   447  			name: "no licenseRef",
   448  			pkg: pkg.Package{
   449  				Licenses: pkg.NewLicenseSet(),
   450  			},
   451  			expected: nil,
   452  		},
   453  		{
   454  			name: "single licenseRef",
   455  			pkg: pkg.Package{
   456  				Licenses: pkg.NewLicenseSet(
   457  					pkg.NewLicense("foobar"),
   458  				),
   459  			},
   460  			expected: []*spdx.OtherLicense{
   461  				{
   462  					LicenseIdentifier: "LicenseRef-foobar",
   463  					ExtractedText:     "foobar",
   464  				},
   465  			},
   466  		},
   467  		{
   468  			name: "multiple licenseRef",
   469  			pkg: pkg.Package{
   470  				Licenses: pkg.NewLicenseSet(
   471  					pkg.NewLicense("internal made up license name"),
   472  					pkg.NewLicense("new apple license 2.0"),
   473  				),
   474  			},
   475  			expected: []*spdx.OtherLicense{
   476  				{
   477  					LicenseIdentifier: "LicenseRef-internal-made-up-license-name",
   478  					ExtractedText:     "internal made up license name",
   479  				},
   480  				{
   481  					LicenseIdentifier: "LicenseRef-new-apple-license-2.0",
   482  					ExtractedText:     "new apple license 2.0",
   483  				},
   484  			},
   485  		},
   486  	}
   487  
   488  	for _, test := range tests {
   489  		t.Run(test.name, func(t *testing.T) {
   490  			catalog := pkg.NewCollection(test.pkg)
   491  			otherLicenses := toOtherLicenses(catalog)
   492  			require.Len(t, otherLicenses, len(test.expected))
   493  			require.Equal(t, test.expected, otherLicenses)
   494  		})
   495  	}
   496  }
   497  
   498  func Test_toSPDXID(t *testing.T) {
   499  	tests := []struct {
   500  		name     string
   501  		it       artifact.Identifiable
   502  		expected string
   503  	}{
   504  		{
   505  			name: "short filename",
   506  			it: file.Coordinates{
   507  				RealPath: "/short/path/file.txt",
   508  			},
   509  			expected: "File-short-path-file.txt",
   510  		},
   511  		{
   512  			name: "long filename",
   513  			it: file.Coordinates{
   514  				RealPath: "/some/long/path/with/a/lot/of-text/that-contains-a/file.txt",
   515  			},
   516  			expected: "File-...a-lot-of-text-that-contains-a-file.txt",
   517  		},
   518  		{
   519  			name: "package",
   520  			it: pkg.Package{
   521  				Type: pkg.NpmPkg,
   522  				Name: "some-package",
   523  			},
   524  			expected: "Package-npm-some-package",
   525  		},
   526  	}
   527  
   528  	for _, test := range tests {
   529  		t.Run(test.name, func(t *testing.T) {
   530  			got := string(toSPDXID(test.it))
   531  			// trim the hash
   532  			got = regexp.MustCompile(`-[a-z0-9]*$`).ReplaceAllString(got, "")
   533  			require.Equal(t, test.expected, got)
   534  		})
   535  	}
   536  }