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 }