package spdx_test

import (
	"hash/fnv"
	"sort"
	"testing"
	"time"

	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/mitchellh/hashstructure/v2"
	"github.com/package-url/packageurl-go"
	"github.com/spdx/tools-golang/spdx"
	"github.com/spdx/tools-golang/spdx/v2/common"
	"github.com/spdx/tools-golang/spdxlib"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/aquasecurity/trivy/pkg/clock"
	ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
	"github.com/aquasecurity/trivy/pkg/report"
	"github.com/aquasecurity/trivy/pkg/sbom/core"
	tspdx "github.com/aquasecurity/trivy/pkg/sbom/spdx"
	"github.com/aquasecurity/trivy/pkg/types"
	"github.com/aquasecurity/trivy/pkg/uuid"
)

func annotation(t *testing.T, comment string) spdx.Annotation {
	t.Helper()

	return spdx.Annotation{
		AnnotationDate: "2021-08-25T12:20:30Z",
		AnnotationType: spdx.CategoryOther,
		Annotator: spdx.Annotator{
			Annotator:     "trivy-0.56.2",
			AnnotatorType: tspdx.PackageAnnotatorToolField,
		},
		AnnotationComment: comment,
	}
}

func TestMarshaler_Marshal(t *testing.T) {
	testCases := []struct {
		name        string
		inputReport types.Report
		wantSBOM    *spdx.Document
	}{
		{
			name: "happy path for container scan",
			inputReport: types.Report{
				SchemaVersion: report.SchemaVersion,
				ArtifactName:  "rails:latest",
				ArtifactType:  ftypes.TypeContainerImage,
				Metadata: types.Metadata{
					Size: 1024,
					OS: &ftypes.OS{
						Family: ftypes.CentOS,
						Name:   "8.3.2011",
						Eosl:   true,
					},
					ImageID:     "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6",
					RepoTags:    []string{"rails:latest"},
					DiffIDs:     []string{"sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a"},
					RepoDigests: []string{"rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177"},
					ImageConfig: v1.ConfigFile{
						Architecture: "arm64",
						Config: v1.Config{
							Labels: map[string]string{
								"vendor": "aquasecurity",
							},
						},
					},
				},
				Results: types.Results{
					{
						Target: "rails:latest (centos 8.3.2011)",
						Class:  types.ClassOSPkg,
						Type:   ftypes.CentOS,
						Packages: []ftypes.Package{
							{
								Name:    "binutils",
								Version: "2.30",
								Release: "93.el8",
								Epoch:   0,
								Arch:    "aarch64",
								Identifier: ftypes.PkgIdentifier{
									UID: "F4C10A4371C93487",
									PURL: &packageurl.PackageURL{
										Type:      packageurl.TypeRPM,
										Namespace: "centos",
										Name:      "binutils",
										Version:   "2.30-93.el8",
										Qualifiers: packageurl.Qualifiers{
											{
												Key:   "arch",
												Value: "aarch64",
											},
											{
												Key:   "distro",
												Value: "centos-8.3.2011",
											},
										},
									},
								},
								SrcName:         "binutils",
								SrcVersion:      "2.30",
								SrcRelease:      "93.el8",
								SrcEpoch:        0,
								Modularitylabel: "",
								Licenses:        []string{"GPLv3+"},
								Maintainer:      "CentOS",
								Digest:          "md5:7459cec61bb4d1b0ca8107e25e0dd005",
							},
						},
					},
					{
						Target: "app/subproject/Gemfile.lock",
						Class:  types.ClassLangPkg,
						Type:   ftypes.Bundler,
						Packages: []ftypes.Package{
							{
								Name:    "actionpack",
								Version: "7.0.1",
								Identifier: ftypes.PkgIdentifier{
									UID: "B1A9DE534F2737AF",
									PURL: &packageurl.PackageURL{
										Type:    packageurl.TypeGem,
										Name:    "actionpack",
										Version: "7.0.1",
									},
								},
							},
							{
								Name:    "actioncontroller",
								Version: "7.0.1",
								Identifier: ftypes.PkgIdentifier{
									UID: "1628B51BD543965D",
									PURL: &packageurl.PackageURL{
										Type:    packageurl.TypeGem,
										Name:    "actioncontroller",
										Version: "7.0.1",
									},
								},
							},
						},
					},
					{
						Target: "app/Gemfile.lock",
						Class:  types.ClassLangPkg,
						Type:   ftypes.Bundler,
						Packages: []ftypes.Package{
							{
								Name:    "actionpack",
								Version: "7.0.1",
								Identifier: ftypes.PkgIdentifier{
									UID: "92D6B6D3FF6F8FF5",
									PURL: &packageurl.PackageURL{
										Type:    packageurl.TypeGem,
										Name:    "actionpack",
										Version: "7.0.1",
									},
								},
							},
						},
					},
				},
			},
			wantSBOM: &spdx.Document{
				SPDXVersion:       spdx.Version,
				DataLicense:       spdx.DataLicense,
				SPDXIdentifier:    "DOCUMENT",
				DocumentName:      "rails:latest",
				DocumentNamespace: "http://trivy.dev/container_image/rails:latest-3ff14136-e09f-4df9-80ea-000000000009",
				CreationInfo: &spdx.CreationInfo{
					Creators: []common.Creator{
						{
							Creator:     "aquasecurity",
							CreatorType: "Organization",
						},
						{
							Creator:     "trivy-0.56.2",
							CreatorType: "Tool",
						},
					},
					Created: "2021-08-25T12:20:30Z",
				},
				Packages: []*spdx.Package{
					{
						PackageSPDXIdentifier:   spdx.ElementID("Application-9f48cdd13858abaf"),
						PackageDownloadLocation: "NONE",
						PackageName:             "app/Gemfile.lock",
						PrimaryPackagePurpose:   tspdx.PackagePurposeApplication,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						Annotations: []spdx.Annotation{
							annotation(t, "Class: lang-pkgs"),
							annotation(t, "Type: bundler"),
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Application-692290f4b2235359"),
						PackageDownloadLocation: "NONE",
						PackageName:             "app/subproject/Gemfile.lock",
						PrimaryPackagePurpose:   tspdx.PackagePurposeApplication,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						Annotations: []spdx.Annotation{
							annotation(t, "Class: lang-pkgs"),
							annotation(t, "Type: bundler"),
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("ContainerImage-9396d894cd0cb6cb"),
						PackageDownloadLocation: "NONE",
						PackageName:             "rails:latest",
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:oci/rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?arch=arm64&repository_url=index.docker.io%2Flibrary%2Frails",
							},
						},
						Annotations: []spdx.Annotation{
							annotation(t, "DiffID: sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a"),
							annotation(t, "ImageID: sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6"),
							annotation(t, "Labels:vendor: aquasecurity"),
							annotation(t, "RepoDigest: rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177"),
							annotation(t, "RepoTag: rails:latest"),
							annotation(t, "SchemaVersion: 2"),
							annotation(t, "Size: 1024"),
						},
						PrimaryPackagePurpose:   tspdx.PackagePurposeContainer,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-b8d4663e6d412e7"),
						PackageDownloadLocation: "NONE",
						PackageName:             "actioncontroller",
						PackageVersion:          "7.0.1",
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						Annotations: []spdx.Annotation{
							annotation(t, "PkgType: bundler"),
						},
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:gem/actioncontroller@7.0.1",
							},
						},
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						PackageSourceInfo:     "package found in: app/subproject/Gemfile.lock",
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-3b51e821f6796568"),
						PackageDownloadLocation: "NONE",
						PackageName:             "actionpack",
						PackageVersion:          "7.0.1",
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						Annotations: []spdx.Annotation{
							annotation(t, "PkgType: bundler"),
						},
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:gem/actionpack@7.0.1",
							},
						},
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						PackageSourceInfo:     "package found in: app/subproject/Gemfile.lock",
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-fb5630bc7d55a21c"),
						PackageDownloadLocation: "NONE",
						PackageName:             "actionpack",
						PackageVersion:          "7.0.1",
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						Annotations: []spdx.Annotation{
							annotation(t, "PkgType: bundler"),
						},
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:gem/actionpack@7.0.1",
							},
						},
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						PackageSourceInfo:     "package found in: app/Gemfile.lock",
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-5d43902b18ed2e2c"),
						PackageDownloadLocation: "NONE",
						PackageName:             "binutils",
						PackageVersion:          "2.30-93.el8",
						PackageLicenseConcluded: "GPL-3.0-or-later",
						PackageLicenseDeclared:  "GPL-3.0-or-later",
						Annotations: []spdx.Annotation{
							annotation(t, "PkgType: centos"),
						},
						PackageSupplier: &spdx.Supplier{
							SupplierType: tspdx.PackageSupplierOrganization,
							Supplier:     "CentOS",
						},
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:rpm/centos/binutils@2.30-93.el8?arch=aarch64&distro=centos-8.3.2011",
							},
						},
						PackageSourceInfo:     "built package from: binutils 2.30-93.el8",
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageChecksums: []common.Checksum{
							{
								Algorithm: common.MD5,
								Value:     "7459cec61bb4d1b0ca8107e25e0dd005",
							},
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("OperatingSystem-20f7fa3049cc748c"),
						PackageDownloadLocation: "NONE",
						PackageName:             "centos",
						PackageVersion:          "8.3.2011",
						PrimaryPackagePurpose:   tspdx.PackagePurposeOS,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						Annotations: []spdx.Annotation{
							annotation(t, "Class: os-pkgs"),
							annotation(t, "Type: centos"),
						},
					},
				},
				Relationships: []*spdx.Relationship{
					{
						RefA:         spdx.DocElementID{ElementRefID: "Application-692290f4b2235359"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-3b51e821f6796568"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Application-692290f4b2235359"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-b8d4663e6d412e7"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Application-9f48cdd13858abaf"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-fb5630bc7d55a21c"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"},
						RefB:         spdx.DocElementID{ElementRefID: "Application-692290f4b2235359"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"},
						RefB:         spdx.DocElementID{ElementRefID: "Application-9f48cdd13858abaf"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"},
						RefB:         spdx.DocElementID{ElementRefID: "OperatingSystem-20f7fa3049cc748c"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
						RefB:         spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"},
						Relationship: "DESCRIBES",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "OperatingSystem-20f7fa3049cc748c"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-5d43902b18ed2e2c"},
						Relationship: "CONTAINS",
					},
				},
				OtherLicenses: nil,
				Annotations:   nil,
				Reviews:       nil,
			},
		},
		{
			name: "happy path for local container scan",
			inputReport: types.Report{
				SchemaVersion: report.SchemaVersion,
				ArtifactName:  "centos:latest",
				ArtifactType:  ftypes.TypeContainerImage,
				Metadata: types.Metadata{
					Size: 1024,
					OS: &ftypes.OS{
						Family: ftypes.CentOS,
						Name:   "8.3.2011",
						Eosl:   true,
					},
					ImageID:     "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6",
					RepoTags:    []string{"centos:latest"},
					RepoDigests: []string{},
					ImageConfig: v1.ConfigFile{
						Architecture: "arm64",
					},
				},
				Results: types.Results{
					{
						Target: "centos:latest (centos 8.3.2011)",
						Class:  types.ClassOSPkg,
						Type:   ftypes.CentOS,
						Packages: []ftypes.Package{
							{
								Name:    "acl",
								Version: "2.2.53",
								Release: "1.el8",
								Epoch:   1,
								Arch:    "aarch64",
								Identifier: ftypes.PkgIdentifier{
									UID: "740219943F17B1DF",
									PURL: &packageurl.PackageURL{
										Type:      packageurl.TypeRPM,
										Namespace: "centos",
										Name:      "acl",
										Version:   "2.2.53-1.el8",
										Qualifiers: packageurl.Qualifiers{
											{
												Key:   "arch",
												Value: "aarch64",
											},
											{
												Key:   "distro",
												Value: "centos-8.3.2011",
											},
											{
												Key:   "epoch",
												Value: "1",
											},
										},
									},
								},
								SrcName:         "acl",
								SrcVersion:      "2.2.53",
								SrcRelease:      "1.el8",
								SrcEpoch:        1,
								Modularitylabel: "",
								Licenses:        []string{"GPLv2+"},
								Digest:          "md5:483792b8b5f9eb8be7dc4407733118d0",
							},
						},
					},
					{
						Target: "Ruby",
						Class:  types.ClassLangPkg,
						Type:   ftypes.GemSpec,
						Packages: []ftypes.Package{
							{
								Name:    "actionpack",
								Version: "7.0.1",
								Identifier: ftypes.PkgIdentifier{
									UID: "E8DB2C6E35F8B990",
									PURL: &packageurl.PackageURL{
										Type:    packageurl.TypeGem,
										Name:    "actionpack",
										Version: "7.0.1",
									},
								},
								Layer: ftypes.Layer{
									DiffID: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488",
								},
								FilePath: "tools/project-john/specifications/actionpack.gemspec",
								Digest:   "sha1:d2f9f9aed5161f6e4116a3f9573f41cd832f137c",
							},
							{
								Name:    "actionpack",
								Version: "7.0.1",
								Identifier: ftypes.PkgIdentifier{
									UID: "B3E70B2159CFAC50",
									PURL: &packageurl.PackageURL{
										Type:    packageurl.TypeGem,
										Name:    "actionpack",
										Version: "7.0.1",
									},
								},
								Layer: ftypes.Layer{
									DiffID: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488",
								},
								FilePath: "tools/project-doe/specifications/actionpack.gemspec",
								Digest:   "sha512:bf690311ee7b95e713ba568322e3533f2dd1cb880b189e99d4edef13592b81764daec43e2c54c61d5c558dc5cfb35ecb85b65519e74026ff17675b6f8f916f4a", // Changed for tests
							},
						},
					},
				},
			},
			wantSBOM: &spdx.Document{
				SPDXVersion:       spdx.Version,
				DataLicense:       spdx.DataLicense,
				SPDXIdentifier:    "DOCUMENT",
				DocumentName:      "centos:latest",
				DocumentNamespace: "http://trivy.dev/container_image/centos:latest-3ff14136-e09f-4df9-80ea-000000000006",
				CreationInfo: &spdx.CreationInfo{
					Creators: []common.Creator{
						{
							Creator:     "aquasecurity",
							CreatorType: "Organization",
						},
						{
							Creator:     "trivy-0.56.2",
							CreatorType: "Tool",
						},
					},
					Created: "2021-08-25T12:20:30Z",
				},
				Packages: []*spdx.Package{
					{
						PackageName:             "centos:latest",
						PackageSPDXIdentifier:   "ContainerImage-413bfede37ad01fc",
						PackageDownloadLocation: "NONE",
						Annotations: []spdx.Annotation{
							annotation(t, "ImageID: sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6"),
							annotation(t, "RepoTag: centos:latest"),
							annotation(t, "SchemaVersion: 2"),
							annotation(t, "Size: 1024"),
						},
						PrimaryPackagePurpose:   tspdx.PackagePurposeContainer,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-40c4059fe08523bf"),
						PackageDownloadLocation: "NONE",
						PackageName:             "acl",
						PackageVersion:          "1:2.2.53-1.el8",
						PackageLicenseConcluded: "GPL-2.0-or-later",
						PackageLicenseDeclared:  "GPL-2.0-or-later",
						Annotations: []spdx.Annotation{
							annotation(t, "PkgType: centos"),
						},
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1",
							},
						},
						PackageSourceInfo:     "built package from: acl 1:2.2.53-1.el8",
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						PackageChecksums: []common.Checksum{
							{
								Algorithm: common.MD5,
								Value:     "483792b8b5f9eb8be7dc4407733118d0",
							},
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-69f68dd639314edd"),
						PackageDownloadLocation: "NONE",
						PackageName:             "actionpack",
						PackageVersion:          "7.0.1",
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:gem/actionpack@7.0.1",
							},
						},
						Annotations: []spdx.Annotation{
							annotation(t, "LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488"),
							annotation(t, "PkgType: gemspec"),
						},
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						FilesAnalyzed:         true,
						PackageVerificationCode: &spdx.PackageVerificationCode{
							Value: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-da2cda24d2ecbfe6"),
						PackageDownloadLocation: "NONE",
						PackageName:             "actionpack",
						PackageVersion:          "7.0.1",
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:gem/actionpack@7.0.1",
							},
						},
						Annotations: []spdx.Annotation{
							annotation(t, "LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488"),
							annotation(t, "PkgType: gemspec"),
						},
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						FilesAnalyzed:         true,
						PackageVerificationCode: &spdx.PackageVerificationCode{
							Value: "c7526b18eaaeb410e82cb0da9288dd02b38ea171",
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("OperatingSystem-20f7fa3049cc748c"),
						PackageDownloadLocation: "NONE",
						PackageName:             "centos",
						PackageVersion:          "8.3.2011",
						PrimaryPackagePurpose:   tspdx.PackagePurposeOS,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						Annotations: []spdx.Annotation{
							annotation(t, "Class: os-pkgs"),
							annotation(t, "Type: centos"),
						},
					},
				},
				Files: []*spdx.File{
					{
						FileSPDXIdentifier: "File-fa42187221d0d0a8",
						FileName:           "tools/project-doe/specifications/actionpack.gemspec",
						Checksums: []spdx.Checksum{
							{
								Algorithm: spdx.SHA512,
								Value:     "bf690311ee7b95e713ba568322e3533f2dd1cb880b189e99d4edef13592b81764daec43e2c54c61d5c558dc5cfb35ecb85b65519e74026ff17675b6f8f916f4a",
							},
						},
					},
					{
						FileSPDXIdentifier: "File-6a540784b0dc6d55",
						FileName:           "tools/project-john/specifications/actionpack.gemspec",
						Checksums: []spdx.Checksum{
							{
								Algorithm: spdx.SHA1,
								Value:     "d2f9f9aed5161f6e4116a3f9573f41cd832f137c",
							},
						},
					},
				},
				Relationships: []*spdx.Relationship{
					{
						RefA:         spdx.DocElementID{ElementRefID: "ContainerImage-413bfede37ad01fc"},
						RefB:         spdx.DocElementID{ElementRefID: "OperatingSystem-20f7fa3049cc748c"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "ContainerImage-413bfede37ad01fc"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-69f68dd639314edd"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "ContainerImage-413bfede37ad01fc"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-da2cda24d2ecbfe6"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
						RefB:         spdx.DocElementID{ElementRefID: "ContainerImage-413bfede37ad01fc"},
						Relationship: "DESCRIBES",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "OperatingSystem-20f7fa3049cc748c"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-40c4059fe08523bf"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Package-69f68dd639314edd"},
						RefB:         spdx.DocElementID{ElementRefID: "File-fa42187221d0d0a8"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Package-da2cda24d2ecbfe6"},
						RefB:         spdx.DocElementID{ElementRefID: "File-6a540784b0dc6d55"},
						Relationship: "CONTAINS",
					},
				},
				OtherLicenses: nil,
				Annotations:   nil,
				Reviews:       nil,
			},
		},
		{
			name: "happy path for fs scan",
			inputReport: types.Report{
				SchemaVersion: report.SchemaVersion,
				ArtifactName:  "masahiro331/CVE-2021-41098",
				ArtifactType:  ftypes.TypeFilesystem,
				Results: types.Results{
					{
						Target: "Gemfile.lock",
						Class:  types.ClassLangPkg,
						Type:   ftypes.Bundler,
						Packages: []ftypes.Package{
							{
								Name:    "actioncable",
								Version: "6.1.4.1",
								Identifier: ftypes.PkgIdentifier{
									PURL: &packageurl.PackageURL{
										Type:    packageurl.TypeGem,
										Name:    "actioncable",
										Version: "6.1.4.1",
									},
								},
							},
						},
					},
					{
						Target: "pom.xml",
						Class:  types.ClassLangPkg,
						Type:   ftypes.Pom,
						Packages: []ftypes.Package{
							{
								ID:      "com.example:example:1.0.0::abcdef1234567001",
								Name:    "com.example:example",
								Version: "1.0.0",
								Identifier: ftypes.PkgIdentifier{
									PURL: &packageurl.PackageURL{
										Type:      packageurl.TypeMaven,
										Namespace: "com.example",
										Name:      "example",
										Version:   "1.0.0",
									},
								},
							},
						},
					},
				},
			},
			wantSBOM: &spdx.Document{
				SPDXVersion:       spdx.Version,
				DataLicense:       spdx.DataLicense,
				SPDXIdentifier:    "DOCUMENT",
				DocumentName:      "masahiro331/CVE-2021-41098",
				DocumentNamespace: "http://trivy.dev/filesystem/masahiro331/CVE-2021-41098-3ff14136-e09f-4df9-80ea-000000000006",
				CreationInfo: &spdx.CreationInfo{
					Creators: []common.Creator{
						{
							Creator:     "aquasecurity",
							CreatorType: "Organization",
						},
						{
							Creator:     "trivy-0.56.2",
							CreatorType: "Tool",
						},
					},
					Created: "2021-08-25T12:20:30Z",
				},
				Packages: []*spdx.Package{
					{
						PackageSPDXIdentifier:   spdx.ElementID("Application-ed046c4a6b4da30f"),
						PackageDownloadLocation: "NONE",
						PackageName:             "Gemfile.lock",
						PrimaryPackagePurpose:   tspdx.PackagePurposeApplication,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						Annotations: []spdx.Annotation{
							annotation(t, "Class: lang-pkgs"),
							annotation(t, "Type: bundler"),
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Application-800d9e6e0f88ab3a"),
						PackageDownloadLocation: "NONE",
						PackageName:             "pom.xml",
						PrimaryPackagePurpose:   tspdx.PackagePurposeApplication,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						Annotations: []spdx.Annotation{
							annotation(t, "Class: lang-pkgs"),
							annotation(t, "Type: pom"),
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-e78eaf94802a53dc"),
						PackageDownloadLocation: "NONE",
						PackageName:             "actioncable",
						PackageVersion:          "6.1.4.1",
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:gem/actioncable@6.1.4.1",
							},
						},
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						PackageSourceInfo:     "package found in: Gemfile.lock",
						Annotations: []spdx.Annotation{
							annotation(t, "PkgType: bundler"),
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-69cd7625c68537c7"),
						PackageDownloadLocation: "NONE",
						PackageName:             "com.example:example",
						PackageVersion:          "1.0.0",
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:maven/com.example/example@1.0.0",
							},
						},
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						PackageSourceInfo:     "package found in: pom.xml",
						Annotations: []spdx.Annotation{
							annotation(t, "PkgID: com.example:example:1.0.0::abcdef1234567001"),
							annotation(t, "PkgType: pom"),
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Filesystem-5af0f1f08c20909a"),
						PackageDownloadLocation: "NONE",
						PackageName:             "masahiro331/CVE-2021-41098",
						Annotations: []spdx.Annotation{
							annotation(t, "SchemaVersion: 2"),
						},
						PrimaryPackagePurpose:   tspdx.PackagePurposeSource,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
					},
				},
				Relationships: []*spdx.Relationship{
					{
						RefA:         spdx.DocElementID{ElementRefID: "Application-800d9e6e0f88ab3a"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-69cd7625c68537c7"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Application-ed046c4a6b4da30f"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-e78eaf94802a53dc"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
						RefB:         spdx.DocElementID{ElementRefID: "Filesystem-5af0f1f08c20909a"},
						Relationship: "DESCRIBES",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Filesystem-5af0f1f08c20909a"},
						RefB:         spdx.DocElementID{ElementRefID: "Application-800d9e6e0f88ab3a"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Filesystem-5af0f1f08c20909a"},
						RefB:         spdx.DocElementID{ElementRefID: "Application-ed046c4a6b4da30f"},
						Relationship: "CONTAINS",
					},
				},
			},
		},
		{
			name: "happy path with various licenses",
			inputReport: types.Report{
				SchemaVersion: report.SchemaVersion,
				ArtifactName:  "pom.xml",
				ArtifactType:  ftypes.TypeFilesystem,
				Results: types.Results{
					{
						Target: "pom.xml",
						Class:  types.ClassLangPkg,
						Type:   ftypes.Pom,
						Packages: []ftypes.Package{
							{
								ID:      "com.example:example:1.0.0::abcdef1234567001",
								Name:    "com.example:example",
								Version: "1.0.0",
								Identifier: ftypes.PkgIdentifier{
									PURL: &packageurl.PackageURL{
										Type:      packageurl.TypeMaven,
										Namespace: "com.example",
										Name:      "example",
										Version:   "1.0.0",
									},
								},
								Licenses: []string{
									"text://BSD-4-clause",
									"BSD-4-clause or LGPL-2.0-only",
									"AFL 3.0 with wrong-exceptions",
									"AFL 3.0 with Autoconf-exception-3.0",
									"text://UNKNOWN",
									"UNKNOWN",
								},
							},
						},
					},
				},
			},
			wantSBOM: &spdx.Document{
				SPDXVersion:       spdx.Version,
				DataLicense:       spdx.DataLicense,
				SPDXIdentifier:    "DOCUMENT",
				DocumentName:      "pom.xml",
				DocumentNamespace: "http://trivy.dev/filesystem/pom.xml-3ff14136-e09f-4df9-80ea-000000000004",
				CreationInfo: &spdx.CreationInfo{
					Creators: []common.Creator{
						{
							Creator:     "aquasecurity",
							CreatorType: "Organization",
						},
						{
							Creator:     "trivy-0.56.2",
							CreatorType: "Tool",
						},
					},
					Created: "2021-08-25T12:20:30Z",
				},
				Packages: []*spdx.Package{
					{
						PackageSPDXIdentifier:   spdx.ElementID("Application-800d9e6e0f88ab3a"),
						PackageDownloadLocation: "NONE",
						PackageName:             "pom.xml",
						PrimaryPackagePurpose:   tspdx.PackagePurposeApplication,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						Annotations: []spdx.Annotation{
							annotation(t, "Class: lang-pkgs"),
							annotation(t, "Type: pom"),
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-69cd7625c68537c7"),
						PackageDownloadLocation: "NONE",
						PackageName:             "com.example:example",
						PackageVersion:          "1.0.0",
						PackageLicenseConcluded: "LicenseRef-14b1606fb243e2b6 AND (BSD-4-Clause OR LGPL-2.0-only) AND LicenseRef-77bdf77d8292ce5b AND AFL-3.0 WITH Autoconf-exception-3.0 AND LicenseRef-229659393343e160 AND LicenseRef-a8d01765900624d3",
						PackageLicenseDeclared:  "LicenseRef-14b1606fb243e2b6 AND (BSD-4-Clause OR LGPL-2.0-only) AND LicenseRef-77bdf77d8292ce5b AND AFL-3.0 WITH Autoconf-exception-3.0 AND LicenseRef-229659393343e160 AND LicenseRef-a8d01765900624d3",
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:maven/com.example/example@1.0.0",
							},
						},
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						PackageSourceInfo:     "package found in: pom.xml",
						Annotations: []spdx.Annotation{
							annotation(t, "PkgID: com.example:example:1.0.0::abcdef1234567001"),
							annotation(t, "PkgType: pom"),
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Filesystem-340a6f62df359d6a"),
						PackageDownloadLocation: "NONE",
						PackageName:             "pom.xml",
						Annotations: []spdx.Annotation{
							annotation(t, "SchemaVersion: 2"),
						},
						PrimaryPackagePurpose:   tspdx.PackagePurposeSource,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
					},
				},
				Relationships: []*spdx.Relationship{
					{
						RefA:         spdx.DocElementID{ElementRefID: "Application-800d9e6e0f88ab3a"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-69cd7625c68537c7"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
						RefB:         spdx.DocElementID{ElementRefID: "Filesystem-340a6f62df359d6a"},
						Relationship: "DESCRIBES",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Filesystem-340a6f62df359d6a"},
						RefB:         spdx.DocElementID{ElementRefID: "Application-800d9e6e0f88ab3a"},
						Relationship: "CONTAINS",
					},
				},
				OtherLicenses: []*spdx.OtherLicense{
					{
						LicenseIdentifier: "LicenseRef-14b1606fb243e2b6",
						LicenseName:       "NOASSERTION",
						ExtractedText:     "BSD-4-clause",
						LicenseComment:    "The license text represents text found in package metadata and may not represent the full text of the license",
					},
					{
						LicenseIdentifier: "LicenseRef-229659393343e160",
						LicenseName:       "NOASSERTION",
						ExtractedText:     "UNKNOWN",
						LicenseComment:    "The license text represents text found in package metadata and may not represent the full text of the license",
					},
					{
						LicenseIdentifier: "LicenseRef-77bdf77d8292ce5b",
						LicenseName:       "AFL-3.0 WITH wrong-exceptions",
						ExtractedText:     `This component is licensed under "AFL-3.0 WITH wrong-exceptions"`,
					},
					{
						LicenseIdentifier: "LicenseRef-a8d01765900624d3",
						LicenseName:       "UNKNOWN",
						ExtractedText:     `This component is licensed under "UNKNOWN"`,
					},
				},
			},
		},
		{
			name: "happy path with vulnerability",
			inputReport: types.Report{
				SchemaVersion: report.SchemaVersion,
				ArtifactName:  "log4j-core-2.17.0.jar",
				ArtifactType:  ftypes.TypeFilesystem,
				Results: types.Results{
					{
						Target: "Java",
						Class:  types.ClassLangPkg,
						Type:   ftypes.Jar,
						Packages: []ftypes.Package{
							{
								Name:    "org.apache.logging.log4j:log4j-core",
								Version: "2.17.0",
								Identifier: ftypes.PkgIdentifier{
									PURL: &packageurl.PackageURL{
										Type:      packageurl.TypeMaven,
										Namespace: "org.apache.logging.log4j",
										Name:      "log4j-core",
										Version:   "2.17.0",
									},
								},
							},
						},
						Vulnerabilities: []types.DetectedVulnerability{
							{
								VulnerabilityID:  "CVE-2021-44832",
								PkgName:          "org.apache.logging.log4j:log4j-core",
								InstalledVersion: "2.17.0",
								FixedVersion:     "2.3.2, 2.12.4, 2.17.1",
								PrimaryURL:       "https://avd.aquasec.com/nvd/cve-2021-44832",
							},
						},
					},
				},
			},
			wantSBOM: &spdx.Document{
				SPDXVersion:       spdx.Version,
				DataLicense:       spdx.DataLicense,
				SPDXIdentifier:    "DOCUMENT",
				DocumentName:      "log4j-core-2.17.0.jar",
				DocumentNamespace: "http://trivy.dev/filesystem/log4j-core-2.17.0.jar-3ff14136-e09f-4df9-80ea-000000000003",
				CreationInfo: &spdx.CreationInfo{
					Creators: []common.Creator{
						{
							Creator:     "aquasecurity",
							CreatorType: "Organization",
						},
						{
							Creator:     "trivy-0.56.2",
							CreatorType: "Tool",
						},
					},
					Created: "2021-08-25T12:20:30Z",
				},
				Packages: []*spdx.Package{
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-4ee6f197f4811213"),
						PackageDownloadLocation: "NONE",
						PackageName:             "org.apache.logging.log4j:log4j-core",
						PackageVersion:          "2.17.0",
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:maven/org.apache.logging.log4j/log4j-core@2.17.0",
							},
							{
								Category: "SECURITY",
								RefType:  "advisory",
								Locator:  "https://avd.aquasec.com/nvd/cve-2021-44832",
							},
						},
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						Annotations: []spdx.Annotation{
							annotation(t, "PkgType: jar"),
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Filesystem-121e7e7a43f02ab"),
						PackageDownloadLocation: "NONE",
						PackageName:             "log4j-core-2.17.0.jar",
						Annotations: []spdx.Annotation{
							annotation(t, "SchemaVersion: 2"),
						},
						PrimaryPackagePurpose:   tspdx.PackagePurposeSource,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
					},
				},
				Relationships: []*spdx.Relationship{
					{
						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
						RefB:         spdx.DocElementID{ElementRefID: "Filesystem-121e7e7a43f02ab"},
						Relationship: "DESCRIBES",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Filesystem-121e7e7a43f02ab"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-4ee6f197f4811213"},
						Relationship: "CONTAINS",
					},
				},
			},
		},
		{
			name: "happy path aggregate results",
			inputReport: types.Report{
				SchemaVersion: report.SchemaVersion,
				ArtifactName:  "http://test-aggregate",
				ArtifactType:  ftypes.TypeRepository,
				Results: types.Results{
					{
						Target: "Node.js",
						Class:  types.ClassLangPkg,
						Type:   ftypes.NodePkg,
						Packages: []ftypes.Package{
							{
								Name:    "ruby-typeprof",
								Version: "0.20.1",
								Identifier: ftypes.PkgIdentifier{
									PURL: &packageurl.PackageURL{
										Type:    packageurl.TypeNPM,
										Name:    "ruby-typeprof",
										Version: "0.20.1",
									},
								},
								Licenses: []string{"MIT"},
								Layer: ftypes.Layer{
									DiffID: "sha256:661c3fd3cc16b34c070f3620ca6b03b6adac150f9a7e5d0e3c707a159990f88e",
								},
								Digest:   "sha256:a5efa82f08774597165e8c1a102d45d0406913b74c184883ac91f409ae26009d",
								FilePath: "usr/local/lib/ruby/gems/3.1.0/gems/typeprof-0.21.1/vscode/package.json",
							},
						},
					},
				},
			},
			wantSBOM: &spdx.Document{
				SPDXVersion:       spdx.Version,
				DataLicense:       spdx.DataLicense,
				SPDXIdentifier:    "DOCUMENT",
				DocumentName:      "http://test-aggregate",
				DocumentNamespace: "http://trivy.dev/repository/test-aggregate-3ff14136-e09f-4df9-80ea-000000000003",
				CreationInfo: &spdx.CreationInfo{
					Creators: []common.Creator{
						{
							Creator:     "aquasecurity",
							CreatorType: "Organization",
						},
						{
							Creator:     "trivy-0.56.2",
							CreatorType: "Tool",
						},
					},
					Created: "2021-08-25T12:20:30Z",
				},
				Packages: []*spdx.Package{
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-52b8e939bac2d133"),
						PackageDownloadLocation: "git+http://test-aggregate",
						PackageName:             "ruby-typeprof",
						PackageVersion:          "0.20.1",
						PackageLicenseConcluded: "MIT",
						PackageLicenseDeclared:  "MIT",
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:npm/ruby-typeprof@0.20.1",
							},
						},
						Annotations: []spdx.Annotation{
							annotation(t, "LayerDiffID: sha256:661c3fd3cc16b34c070f3620ca6b03b6adac150f9a7e5d0e3c707a159990f88e"),
							annotation(t, "PkgType: node-pkg"),
						},
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						FilesAnalyzed:         true,
						PackageVerificationCode: &spdx.PackageVerificationCode{
							Value: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
						},
					},
					{
						PackageSPDXIdentifier:   "Repository-1a78857c1a6a759e",
						PackageName:             "http://test-aggregate",
						PackageDownloadLocation: "git+http://test-aggregate",
						Annotations: []spdx.Annotation{
							annotation(t, "SchemaVersion: 2"),
						},
						PrimaryPackagePurpose:   tspdx.PackagePurposeSource,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
					},
				},
				Files: []*spdx.File{
					{
						FileName:           "usr/local/lib/ruby/gems/3.1.0/gems/typeprof-0.21.1/vscode/package.json",
						FileSPDXIdentifier: "File-a52825a3e5bc6dfe",
						Checksums: []common.Checksum{
							{
								Algorithm: common.SHA256,
								Value:     "a5efa82f08774597165e8c1a102d45d0406913b74c184883ac91f409ae26009d",
							},
						},
					},
				},
				Relationships: []*spdx.Relationship{
					{
						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
						RefB:         spdx.DocElementID{ElementRefID: "Repository-1a78857c1a6a759e"},
						Relationship: "DESCRIBES",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Package-52b8e939bac2d133"},
						RefB:         spdx.DocElementID{ElementRefID: "File-a52825a3e5bc6dfe"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Repository-1a78857c1a6a759e"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-52b8e939bac2d133"},
						Relationship: "CONTAINS",
					},
				},
			},
		},
		{
			name: "happy path empty",
			inputReport: types.Report{
				SchemaVersion: report.SchemaVersion,
				ArtifactName:  "empty/path",
				ArtifactType:  ftypes.TypeFilesystem,
				Results:       types.Results{},
			},
			wantSBOM: &spdx.Document{
				SPDXVersion:       spdx.Version,
				DataLicense:       spdx.DataLicense,
				SPDXIdentifier:    "DOCUMENT",
				DocumentName:      "empty/path",
				DocumentNamespace: "http://trivy.dev/filesystem/empty/path-3ff14136-e09f-4df9-80ea-000000000002",

				CreationInfo: &spdx.CreationInfo{
					Creators: []common.Creator{
						{
							Creator:     "aquasecurity",
							CreatorType: "Organization",
						},
						{
							Creator:     "trivy-0.56.2",
							CreatorType: "Tool",
						},
					},
					Created: "2021-08-25T12:20:30Z",
				},
				Packages: []*spdx.Package{
					{
						PackageName:             "empty/path",
						PackageSPDXIdentifier:   "Filesystem-70f34983067dba86",
						PackageDownloadLocation: "NONE",
						Annotations: []spdx.Annotation{
							annotation(t, "SchemaVersion: 2"),
						},
						PrimaryPackagePurpose:   tspdx.PackagePurposeSource,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
					},
				},
				Relationships: []*spdx.Relationship{
					{
						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
						RefB:         spdx.DocElementID{ElementRefID: "Filesystem-70f34983067dba86"},
						Relationship: "DESCRIBES",
					},
				},
			},
		},
		{
			name: "happy path secret",
			inputReport: types.Report{
				SchemaVersion: report.SchemaVersion,
				ArtifactName:  "secret",
				ArtifactType:  ftypes.TypeFilesystem,
				Results: types.Results{
					{
						Target: "key.pem",
						Class:  types.ClassSecret,
						Secrets: []types.DetectedSecret{
							{
								RuleID:    "private-key",
								Category:  "AsymmetricPrivateKey",
								Severity:  "HIGH",
								Title:     "Asymmetric Private Key",
								StartLine: 1,
								EndLine:   1,
							},
						},
					},
				},
			},
			wantSBOM: &spdx.Document{
				SPDXVersion:       spdx.Version,
				DataLicense:       spdx.DataLicense,
				SPDXIdentifier:    "DOCUMENT",
				DocumentName:      "secret",
				DocumentNamespace: "http://trivy.dev/filesystem/secret-3ff14136-e09f-4df9-80ea-000000000002",
				CreationInfo: &spdx.CreationInfo{
					Creators: []common.Creator{
						{
							Creator:     "aquasecurity",
							CreatorType: "Organization",
						},
						{
							Creator:     "trivy-0.56.2",
							CreatorType: "Tool",
						},
					},
					Created: "2021-08-25T12:20:30Z",
				},
				Packages: []*spdx.Package{
					{
						PackageName:             "secret",
						PackageSPDXIdentifier:   "Filesystem-5c08d34162a2c5d3",
						PackageDownloadLocation: "NONE",
						Annotations: []spdx.Annotation{
							annotation(t, "SchemaVersion: 2"),
						},
						PrimaryPackagePurpose:   tspdx.PackagePurposeSource,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
					},
				},
				Relationships: []*spdx.Relationship{
					{
						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
						RefB:         spdx.DocElementID{ElementRefID: "Filesystem-5c08d34162a2c5d3"},
						Relationship: "DESCRIBES",
					},
				},
			},
		},
		{
			name: "go library local",
			inputReport: types.Report{
				SchemaVersion: report.SchemaVersion,
				ArtifactName:  "go-artifact",
				ArtifactType:  ftypes.TypeFilesystem,
				Results: types.Results{
					{
						Target: "/usr/local/bin/test",
						Class:  types.ClassLangPkg,
						Type:   ftypes.GoBinary,
						Packages: []ftypes.Package{
							{
								Name:    "./private_repos/cnrm.googlesource.com/cnrm/",
								Version: "",
							},
							{
								Name:    "golang.org/x/crypto",
								Version: "v0.0.1",
								Identifier: ftypes.PkgIdentifier{
									UID: "161541A259EF014B",
									PURL: &packageurl.PackageURL{
										Type:      packageurl.TypeGolang,
										Namespace: "golang.org/x",
										Name:      "crypto",
										Version:   "v0.0.1",
									},
								},
							},
						},
					},
				},
			},
			wantSBOM: &spdx.Document{
				SPDXVersion:       spdx.Version,
				DataLicense:       spdx.DataLicense,
				SPDXIdentifier:    "DOCUMENT",
				DocumentName:      "go-artifact",
				DocumentNamespace: "http://trivy.dev/filesystem/go-artifact-3ff14136-e09f-4df9-80ea-000000000005",
				CreationInfo: &spdx.CreationInfo{
					Creators: []common.Creator{
						{
							Creator:     "aquasecurity",
							CreatorType: "Organization",
						},
						{
							Creator:     "trivy-0.56.2",
							CreatorType: "Tool",
						},
					},
					Created: "2021-08-25T12:20:30Z",
				},
				Packages: []*spdx.Package{
					{
						PackageSPDXIdentifier:   spdx.ElementID("Application-aab0f4e8cf174c67"),
						PackageDownloadLocation: "NONE",
						PackageName:             "/usr/local/bin/test",
						PrimaryPackagePurpose:   tspdx.PackagePurposeApplication,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						Annotations: []spdx.Annotation{
							annotation(t, "Class: lang-pkgs"),
							annotation(t, "Type: gobinary"),
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-b1c3b9e2363f5ff7"),
						PackageDownloadLocation: "NONE",
						PackageName:             "./private_repos/cnrm.googlesource.com/cnrm/",
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						PrimaryPackagePurpose:   tspdx.PackagePurposeLibrary,
						PackageSupplier:         &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						PackageSourceInfo:       "package found in: /usr/local/bin/test",
						Annotations: []spdx.Annotation{
							annotation(t, "PkgType: gobinary"),
						},
					},
					{
						PackageSPDXIdentifier:   spdx.ElementID("Package-b9b7ae633941e083"),
						PackageDownloadLocation: "NONE",
						PackageName:             "golang.org/x/crypto",
						PackageVersion:          "v0.0.1",
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
						PackageExternalReferences: []*spdx.PackageExternalReference{
							{
								Category: tspdx.CategoryPackageManager,
								RefType:  tspdx.RefTypePurl,
								Locator:  "pkg:golang/golang.org/x/crypto@v0.0.1",
							},
						},
						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
						PackageSourceInfo:     "package found in: /usr/local/bin/test",
						Annotations: []spdx.Annotation{
							annotation(t, "PkgType: gobinary"),
						},
					},
					{
						PackageName:             "go-artifact",
						PackageSPDXIdentifier:   "Filesystem-e340f27468b382be",
						PackageDownloadLocation: "NONE",
						Annotations: []spdx.Annotation{
							annotation(t, "SchemaVersion: 2"),
						},
						PrimaryPackagePurpose:   tspdx.PackagePurposeSource,
						PackageLicenseConcluded: "NOASSERTION",
						PackageLicenseDeclared:  "NOASSERTION",
					},
				},
				Relationships: []*spdx.Relationship{
					{
						RefA:         spdx.DocElementID{ElementRefID: "Application-aab0f4e8cf174c67"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-b1c3b9e2363f5ff7"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Application-aab0f4e8cf174c67"},
						RefB:         spdx.DocElementID{ElementRefID: "Package-b9b7ae633941e083"},
						Relationship: "CONTAINS",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
						RefB:         spdx.DocElementID{ElementRefID: "Filesystem-e340f27468b382be"},
						Relationship: "DESCRIBES",
					},
					{
						RefA:         spdx.DocElementID{ElementRefID: "Filesystem-e340f27468b382be"},
						RefB:         spdx.DocElementID{ElementRefID: "Application-aab0f4e8cf174c67"},
						Relationship: "CONTAINS",
					},
				},
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Fake function calculating the hash value
			h := fnv.New64()
			hasher := func(v any, _ hashstructure.Format, _ *hashstructure.HashOptions) (uint64, error) {
				h.Reset()

				var str string
				switch vv := v.(type) {
				case *core.Component:
					str = vv.Name + vv.Version + vv.SrcFile
					for _, f := range vv.Files {
						str += f.Path
					}
				case spdx.OtherLicense:
					str = vv.ExtractedText + vv.LicenseName
				case string:
					str = vv
				default:
					require.Failf(t, "unknown type", "%T", v)
				}

				if _, err := h.Write([]byte(str)); err != nil {
					return 0, err
				}

				return h.Sum64(), nil
			}

			ctx := clock.With(t.Context(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC))
			uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d")

			marshaler := tspdx.NewMarshaler("0.56.2", tspdx.WithHasher(hasher))
			spdxDoc, err := marshaler.MarshalReport(ctx, tc.inputReport)
			require.NoError(t, err)

			assert.NoError(t, spdxlib.ValidateDocument(spdxDoc))
			assert.Equal(t, tc.wantSBOM, spdxDoc)
		})
	}
}

func TestMarshaler_normalizeLicenses(t *testing.T) {
	tests := []struct {
		name              string
		input             []string
		wantLicenseName   string
		wantOtherLicenses []*spdx.OtherLicense
	}{
		{
			name: "happy path",
			input: []string{
				"GPLv2+",
			},
			wantLicenseName: "GPL-2.0-or-later",
		},
		{
			name: "happy path with multi license",
			input: []string{
				"GPLv2+",
				"GPLv3+",
				"BSD-4-Clause",
			},
			wantLicenseName: "GPL-2.0-or-later AND GPL-3.0-or-later AND BSD-4-Clause",
		},
		{
			name: "happy path with OR operator",
			input: []string{
				"GPLv2+",
				"LGPL 2.0 or GNU LESSER",
			},
			wantLicenseName: "GPL-2.0-or-later AND (LGPL-2.0-only OR LGPL-2.1-only)",
		},
		{
			name: "happy path with OR operator with non-SPDX license",
			input: []string{
				"GPLv2+",
				"wrong-license or unknown-license",
			},
			wantLicenseName: "GPL-2.0-or-later AND (LicenseRef-c581e42fe705aa48 OR LicenseRef-a0bb0951a6dfbdbe)",
			wantOtherLicenses: []*spdx.OtherLicense{
				{
					LicenseIdentifier: "LicenseRef-a0bb0951a6dfbdbe",
					LicenseName:       "unknown-license",
					ExtractedText:     `This component is licensed under "unknown-license"`,
				},
				{
					LicenseIdentifier: "LicenseRef-c581e42fe705aa48",
					LicenseName:       "wrong-license",
					ExtractedText:     `This component is licensed under "wrong-license"`,
				},
			},
		},
		{
			name: "happy path with AND operator",
			input: []string{
				"GPLv2+",
				"LGPL 2.0 and GNU LESSER",
			},
			wantLicenseName: "GPL-2.0-or-later AND LGPL-2.0-only AND LGPL-2.1-only",
		},
		{
			name: "happy path with WITH operator",
			input: []string{
				"GPLv2",
				"AFL 3.0 with wrong-exceptions",
				"LGPL 2.0 and unknown-license and GNU LESSER",
				"AFL 3.0 with Autoconf-exception-3.0",
			},
			wantLicenseName: "GPL-2.0-only AND LicenseRef-51373b28fab165e9 AND LGPL-2.0-only AND LicenseRef-a0bb0951a6dfbdbe AND LGPL-2.1-only AND AFL-3.0 WITH Autoconf-exception-3.0",
			wantOtherLicenses: []*spdx.OtherLicense{
				{
					LicenseIdentifier: "LicenseRef-51373b28fab165e9",
					LicenseName:       "AFL-3.0 WITH wrong-exceptions",
					ExtractedText:     `This component is licensed under "AFL-3.0 WITH wrong-exceptions"`,
				},
				{
					LicenseIdentifier: "LicenseRef-a0bb0951a6dfbdbe",
					LicenseName:       "unknown-license",
					ExtractedText:     `This component is licensed under "unknown-license"`,
				},
			},
		},
		{
			name: "happy path with non-SPDX exception",
			input: []string{
				"AFL 2.0",
				"AFL 3.0 with wrong-exceptions",
			},
			wantLicenseName: "AFL-2.0 AND LicenseRef-51373b28fab165e9",
			wantOtherLicenses: []*spdx.OtherLicense{
				{
					LicenseIdentifier: "LicenseRef-51373b28fab165e9",
					LicenseName:       "AFL-3.0 WITH wrong-exceptions",
					ExtractedText:     `This component is licensed under "AFL-3.0 WITH wrong-exceptions"`,
				},
			},
		},
		{
			name: "happy path with incorrect cases for license and exception",
			input: []string{
				"afl 3.0 with autoCONF-exception-3.0",
			},
			wantLicenseName: "AFL-3.0 WITH Autoconf-exception-3.0",
		},
		{
			name: "happy path with text of license",
			input: []string{
				"text://Redistribution and use in source and binary forms, with or without",
				"AFL 2.0",
				"unknown-license",
			},
			wantLicenseName: "LicenseRef-b5b4cc09bc5f0e16 AND AFL-2.0 AND LicenseRef-a0bb0951a6dfbdbe",
			wantOtherLicenses: []*spdx.OtherLicense{
				{
					LicenseIdentifier: "LicenseRef-a0bb0951a6dfbdbe",
					LicenseName:       "unknown-license",
					ExtractedText:     `This component is licensed under "unknown-license"`,
				},
				{
					LicenseIdentifier: "LicenseRef-b5b4cc09bc5f0e16",
					LicenseName:       "NOASSERTION",
					ExtractedText:     "Redistribution and use in source and binary forms, with or without",
					LicenseComment:    "The license text represents text found in package metadata and may not represent the full text of the license",
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			m := tspdx.NewMarshaler("")
			gotLicenseName, gotOtherLicenses := m.NormalizeLicenses(tt.input)
			// We will sort all OtherLicenses for SPDX document
			// So we need to sort OtherLicenses for this test
			sort.Slice(gotOtherLicenses, func(i, j int) bool {
				return gotOtherLicenses[i].LicenseIdentifier < gotOtherLicenses[j].LicenseIdentifier
			})
			assert.Equal(t, tt.wantLicenseName, gotLicenseName)
			assert.Equal(t, tt.wantOtherLicenses, gotOtherLicenses)
		})
	}
}
