Files
trivy/pkg/purl/purl_test.go
2025-09-22 06:46:45 +00:00

1040 lines
23 KiB
Go

package purl_test
import (
"testing"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/package-url/packageurl-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/purl"
"github.com/aquasecurity/trivy/pkg/types"
)
func TestNewPackageURL(t *testing.T) {
testCases := []struct {
name string
typ ftypes.TargetType
pkg ftypes.Package
metadata types.Metadata
want *purl.PackageURL
wantErr string
}{
{
name: "maven package",
typ: ftypes.Jar,
pkg: ftypes.Package{
Name: "org.springframework:spring-core",
Version: "5.3.14",
},
want: &purl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.springframework",
Name: "spring-core",
Version: "5.3.14",
},
},
{
name: "gradle package",
typ: ftypes.Gradle,
pkg: ftypes.Package{
Name: "org.springframework:spring-core",
Version: "5.3.14",
},
want: &purl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.springframework",
Name: "spring-core",
Version: "5.3.14",
},
},
{
name: "sbt package",
typ: ftypes.Sbt,
pkg: ftypes.Package{
Name: "org.typelevel:cats-core_2.12",
Version: "2.9.0",
},
want: &purl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.typelevel",
Name: "cats-core_2.12",
Version: "2.9.0",
},
},
{
name: "bun package",
typ: ftypes.Bun,
pkg: ftypes.Package{
Name: "bun-types@1.2.14",
Version: "1.2.14",
},
want: &purl.PackageURL{
Type: packageurl.TypeNPM,
Name: "bun-types@1.2.14",
Version: "1.2.14",
},
},
{
name: "yarn package",
typ: ftypes.Yarn,
pkg: ftypes.Package{
Name: "@xtuc/ieee754",
Version: "1.2.0",
},
want: &purl.PackageURL{
Type: packageurl.TypeNPM,
Namespace: "@xtuc",
Name: "ieee754",
Version: "1.2.0",
},
},
{
name: "yarn package with non-namespace",
typ: ftypes.Yarn,
pkg: ftypes.Package{
Name: "lodash",
Version: "4.17.21",
},
want: &purl.PackageURL{
Type: packageurl.TypeNPM,
Name: "lodash",
Version: "4.17.21",
},
},
{
name: "pnpm package",
typ: ftypes.Pnpm,
pkg: ftypes.Package{
Name: "@xtuc/ieee754",
Version: "1.2.0",
},
want: &purl.PackageURL{
Type: packageurl.TypeNPM,
Namespace: "@xtuc",
Name: "ieee754",
Version: "1.2.0",
},
},
{
name: "pnpm package with non-namespace",
typ: ftypes.Pnpm,
pkg: ftypes.Package{
Name: "lodash",
Version: "4.17.21",
},
want: &purl.PackageURL{
Type: packageurl.TypeNPM,
Name: "lodash",
Version: "4.17.21",
},
},
{
name: "pypi package",
typ: ftypes.PythonPkg,
pkg: ftypes.Package{
Name: "Django_test",
Version: "1.2.0",
},
want: &purl.PackageURL{
Type: packageurl.TypePyPi,
Name: "django-test",
Version: "1.2.0",
},
},
{
name: "conda package",
typ: ftypes.CondaPkg,
pkg: ftypes.Package{
Name: "absl-py",
Version: "0.4.1",
},
want: &purl.PackageURL{
Type: packageurl.TypeConda,
Name: "absl-py",
Version: "0.4.1",
},
},
{
name: "conda environment.yaml",
typ: ftypes.CondaEnv,
pkg: ftypes.Package{
Name: "blas",
Version: "1.0",
},
want: &purl.PackageURL{
Type: packageurl.TypeConda,
Name: "blas",
Version: "1.0",
},
},
{
name: "composer package",
typ: ftypes.Composer,
pkg: ftypes.Package{
Name: "symfony/contracts",
Version: "v1.0.2",
},
want: &purl.PackageURL{
Type: packageurl.TypeComposer,
Namespace: "symfony",
Name: "contracts",
Version: "v1.0.2",
},
},
{
name: "golang package",
typ: ftypes.GoModule,
pkg: ftypes.Package{
Name: "github.com/go-sql-driver/Mysql",
Version: "v1.5.0",
},
want: &purl.PackageURL{
Type: packageurl.TypeGolang,
Namespace: "github.com/go-sql-driver",
Name: "mysql",
Version: "v1.5.0",
},
},
{
name: "golang package with a local path",
typ: ftypes.GoModule,
pkg: ftypes.Package{
Name: "./private_repos/cnrm.googlesource.com/cnrm/",
Version: "",
},
want: nil,
},
{
name: "hex package",
typ: ftypes.Hex,
pkg: ftypes.Package{
ID: "bunt@0.2.0",
Name: "bunt",
Version: "0.2.0",
Locations: []ftypes.Location{
{
StartLine: 2,
EndLine: 2,
},
},
},
want: &purl.PackageURL{
Type: packageurl.TypeHex,
Name: "bunt",
Version: "0.2.0",
},
},
{
name: "dart package",
typ: ftypes.Pub,
pkg: ftypes.Package{
Name: "http",
Version: "0.13.2",
},
want: &purl.PackageURL{
Type: packageurl.TypePub,
Name: "http",
Version: "0.13.2",
},
},
{
name: "swift package",
typ: ftypes.Swift,
pkg: ftypes.Package{
ID: "github.com/apple/swift-atomics@1.1.0",
Name: "github.com/apple/swift-atomics",
Version: "1.1.0",
},
want: &purl.PackageURL{
Type: packageurl.TypeSwift,
Namespace: "github.com/apple",
Name: "swift-atomics",
Version: "1.1.0",
},
},
{
name: "cocoapods package",
typ: ftypes.Cocoapods,
pkg: ftypes.Package{
ID: "GoogleUtilities/NSData+zlib@7.5.2",
Name: "GoogleUtilities/NSData+zlib",
Version: "7.5.2",
},
want: &purl.PackageURL{
Type: packageurl.TypeCocoapods,
Name: "GoogleUtilities",
Version: "7.5.2",
Subpath: "NSData+zlib",
},
},
{
name: "rust binary",
typ: ftypes.RustBinary,
pkg: ftypes.Package{
ID: "abomination@0.7.3",
Name: "abomination",
Version: "0.7.3",
},
want: &purl.PackageURL{
Type: packageurl.TypeCargo,
Name: "abomination",
Version: "0.7.3",
},
},
{
name: "dotnet Packages.props",
typ: ftypes.PackagesProps,
pkg: ftypes.Package{
ID: "Newtonsoft.Json@9.0.1",
Name: "Newtonsoft.Json",
Version: "9.0.1",
},
want: &purl.PackageURL{
Type: packageurl.TypeNuget,
Name: "Newtonsoft.Json",
Version: "9.0.1",
},
},
{
name: "os package",
typ: ftypes.RedHat,
pkg: ftypes.Package{
Name: "acl",
Version: "2.2.53",
Release: "1.el8",
Epoch: 1,
Arch: "aarch64",
SrcName: "acl",
SrcVersion: "2.2.53",
SrcRelease: "1.el8",
SrcEpoch: 1,
Modularitylabel: "",
},
metadata: types.Metadata{
OS: &ftypes.OS{
Family: ftypes.RedHat,
Name: "8",
},
},
want: &purl.PackageURL{
Type: packageurl.TypeRPM,
Namespace: "redhat",
Name: "acl",
Version: "2.2.53-1.el8",
Qualifiers: packageurl.Qualifiers{
{
Key: "arch",
Value: "aarch64",
},
{
Key: "epoch",
Value: "1",
},
{
Key: "distro",
Value: "redhat-8",
},
},
},
},
{
name: "container",
typ: purl.TypeOCI,
metadata: types.Metadata{
RepoTags: []string{
"cblmariner2preview.azurecr.io/base/core:2.0.20220124-amd64",
},
RepoDigests: []string{
"cblmariner2preview.azurecr.io/base/core@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260",
"cblmariner2preview.azurecr.io/base/core@sha256:016bb1f5735e43b2738cd3fd1979b62608fe1727132b2506c17ba0e1f6a6ed8a",
},
ImageConfig: v1.ConfigFile{
Architecture: "amd64",
},
},
want: &purl.PackageURL{
Type: packageurl.TypeOCI,
Namespace: "",
Name: "core",
Version: "sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260",
Qualifiers: packageurl.Qualifiers{
{
Key: "repository_url",
Value: "cblmariner2preview.azurecr.io/base/core",
},
{
Key: "arch",
Value: "amd64",
},
},
},
},
{
name: "container local",
typ: purl.TypeOCI,
metadata: types.Metadata{
RepoTags: []string{},
RepoDigests: []string{},
ImageConfig: v1.ConfigFile{
Architecture: "amd64",
},
ImageID: "sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260",
},
want: nil,
},
{
name: "container with implicit registry",
typ: purl.TypeOCI,
metadata: types.Metadata{
RepoTags: []string{
"alpine:3.14",
"alpine:latest",
},
RepoDigests: []string{
"alpine:3.14@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260",
"alpine:latest@sha256:016bb1f5735e43b2738cd3fd1979b62608fe1727132b2506c17ba0e1f6a6ed8a",
},
ImageConfig: v1.ConfigFile{
Architecture: "amd64",
},
},
want: &purl.PackageURL{
Type: packageurl.TypeOCI,
Namespace: "",
Name: "alpine",
Version: "sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260",
Qualifiers: packageurl.Qualifiers{
{
Key: "repository_url",
Value: "index.docker.io/library/alpine",
},
{
Key: "arch",
Value: "amd64",
},
},
},
},
{
name: "sad path",
typ: purl.TypeOCI,
metadata: types.Metadata{
RepoTags: []string{
"cblmariner2preview.azurecr.io/base/core:2.0.20220124-amd64",
},
RepoDigests: []string{
"sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260",
},
},
wantErr: "failed to parse digest",
},
{
name: "julia project",
typ: ftypes.Julia,
pkg: ftypes.Package{
ID: "ade2ca70-3891-5945-98fb-dc099432e06a",
Name: "Dates",
Version: "1.9.0",
},
want: &purl.PackageURL{
Type: packageurl.TypeJulia,
Name: "Dates",
Version: "1.9.0",
Qualifiers: packageurl.Qualifiers{
{
Key: "uuid",
Value: "ade2ca70-3891-5945-98fb-dc099432e06a",
},
},
},
},
{
name: "bottlerocket package",
typ: ftypes.Bottlerocket,
metadata: types.Metadata{
OS: &ftypes.OS{
Family: ftypes.Bottlerocket,
Name: "1.34.0",
},
},
pkg: ftypes.Package{
ID: "glibc@2.40",
Name: "glibc",
Version: "2.40",
Epoch: 1,
Arch: "x86_64",
},
want: &purl.PackageURL{
Type: "bottlerocket",
Name: "glibc",
Version: "2.40",
Qualifiers: packageurl.Qualifiers{
{
Key: "arch",
Value: "x86_64",
},
{
Key: "epoch",
Value: "1",
},
{
Key: "distro",
Value: "bottlerocket-1.34.0",
},
},
},
},
{
name: "coreos package",
typ: ftypes.CoreOS,
metadata: types.Metadata{
OS: &ftypes.OS{
Family: ftypes.CoreOS,
Name: "1.34.0",
},
},
pkg: ftypes.Package{
ID: "glibc@2.40",
Name: "glibc",
Version: "2.40",
Epoch: 1,
Arch: "x86_64",
},
want: &purl.PackageURL{
Type: "rpm",
Namespace: "coreos",
Name: "glibc",
Version: "2.40",
Qualifiers: packageurl.Qualifiers{
{
Key: "arch",
Value: "x86_64",
},
{
Key: "epoch",
Value: "1",
},
{
Key: "distro",
Value: "coreos-1.34.0",
},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
packageURL, err := purl.New(tc.typ, tc.metadata, tc.pkg)
if tc.wantErr != "" {
require.ErrorContains(t, err, tc.wantErr)
return
}
require.NoError(t, err)
assert.Equal(t, tc.want, packageURL, tc.name)
})
}
}
func TestFromString(t *testing.T) {
testCases := []struct {
name string
purl string
want purl.PackageURL
wantErr string
}{
{
name: "happy path for maven",
purl: "pkg:maven/org.springframework/spring-core@5.0.4.RELEASE",
want: purl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.springframework",
Version: "5.0.4.RELEASE",
Name: "spring-core",
},
},
{
name: "happy path for npm",
purl: "pkg:npm/bootstrap@5.0.2",
want: purl.PackageURL{
Type: packageurl.TypeNPM,
Name: "bootstrap",
Version: "5.0.2",
},
},
{
name: "happy path for coocapods",
purl: "pkg:cocoapods/GoogleUtilities@7.5.2#NSData+zlib",
want: purl.PackageURL{
Type: packageurl.TypeCocoapods,
Name: "GoogleUtilities",
Version: "7.5.2",
Subpath: "NSData+zlib",
},
},
{
name: "happy path for hex",
purl: "pkg:hex/plug@1.14.0",
want: purl.PackageURL{
Type: packageurl.TypeHex,
Name: "plug",
Version: "1.14.0",
},
},
{
name: "happy path for dart",
purl: "pkg:pub/http@0.13.2",
want: purl.PackageURL{
Type: packageurl.TypePub,
Name: "http",
Version: "0.13.2",
},
},
{
name: "happy path for apk",
purl: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?distro=3.14.2&epoch=1",
want: purl.PackageURL{
Type: string(analyzer.TypeApk),
Namespace: "alpine",
Name: "alpine-baselayout",
Version: "3.2.0-r16",
Qualifiers: packageurl.Qualifiers{
{
Key: "distro",
Value: "3.14.2",
},
{
Key: "epoch",
Value: "1",
},
},
},
},
{
name: "happy path for rpm",
purl: "pkg:rpm/redhat/containers-common@0.1.14",
want: purl.PackageURL{
Type: packageurl.TypeRPM,
Namespace: "redhat",
Name: "containers-common",
Version: "0.1.14",
},
},
{
name: "happy path for conda",
purl: "pkg:conda/absl-py@0.4.1",
want: purl.PackageURL{
Type: packageurl.TypeConda,
Name: "absl-py",
Version: "0.4.1",
},
},
{
name: "bad rpm",
purl: "pkg:rpm/redhat/a--@1.0.0",
want: purl.PackageURL{
Type: packageurl.TypeRPM,
Namespace: "redhat",
Name: "a--",
Version: "1.0.0",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
pkg, err := purl.FromString(tc.purl)
if tc.wantErr != "" {
assert.ErrorContains(t, err, tc.wantErr)
return
}
require.NoError(t, err)
assert.Equal(t, tc.want, *pkg, tc.name)
})
}
}
func TestPackageURL_Package(t *testing.T) {
tests := []struct {
name string
pkgURL *purl.PackageURL
wantPkg *ftypes.Package
}{
{
name: "rpm + Qualifiers",
pkgURL: &purl.PackageURL{
Type: packageurl.TypeRPM,
Namespace: "redhat",
Name: "nodejs-full-i18n",
Version: "10.21.0-3.module_el8.2.0+391+8da3adc6",
Qualifiers: packageurl.Qualifiers{
{
Key: "arch",
Value: "x86_64",
},
{
Key: "epoch",
Value: "1",
},
{
Key: "modularitylabel",
Value: "nodejs:10:8020020200707141642:6a468ee4",
},
{
Key: "distro",
Value: "redhat-8",
},
},
},
wantPkg: &ftypes.Package{
ID: "nodejs-full-i18n@10.21.0-3.module_el8.2.0+391+8da3adc6",
Name: "nodejs-full-i18n",
Version: "10.21.0",
Release: "3.module_el8.2.0+391+8da3adc6",
Arch: "x86_64",
Epoch: 1,
Modularitylabel: "nodejs:10:8020020200707141642:6a468ee4",
Identifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeRPM,
Namespace: "redhat",
Name: "nodejs-full-i18n",
Version: "10.21.0-3.module_el8.2.0+391+8da3adc6",
Qualifiers: packageurl.Qualifiers{
{
Key: "arch",
Value: "x86_64",
},
{
Key: "epoch",
Value: "1",
},
{
Key: "modularitylabel",
Value: "nodejs:10:8020020200707141642:6a468ee4",
},
{
Key: "distro",
Value: "redhat-8",
},
},
},
},
},
},
{
name: "composer with namespace",
pkgURL: &purl.PackageURL{
Type: packageurl.TypeComposer,
Namespace: "symfony",
Name: "contracts",
Version: "1.0.2",
},
wantPkg: &ftypes.Package{
ID: "symfony/contracts@1.0.2",
Name: "symfony/contracts",
Version: "1.0.2",
Identifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeComposer,
Namespace: "symfony",
Name: "contracts",
Version: "1.0.2",
},
},
},
},
{
name: "maven with namespace",
pkgURL: &purl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.springframework",
Name: "spring-core",
Version: "5.0.4.RELEASE",
},
wantPkg: &ftypes.Package{
ID: "org.springframework:spring-core:5.0.4.RELEASE",
Name: "org.springframework:spring-core",
Version: "5.0.4.RELEASE",
Identifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.springframework",
Name: "spring-core",
Version: "5.0.4.RELEASE",
},
},
},
},
{
name: "cocoapods with subpath",
pkgURL: &purl.PackageURL{
Type: packageurl.TypeCocoapods,
Version: "4.2.0",
Name: "AppCenter",
Subpath: "Analytics",
},
wantPkg: &ftypes.Package{
ID: "AppCenter/Analytics@4.2.0",
Name: "AppCenter/Analytics",
Version: "4.2.0",
Identifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeCocoapods,
Version: "4.2.0",
Name: "AppCenter",
Subpath: "Analytics",
},
},
},
},
{
name: "bottlerocket with epoch",
pkgURL: &purl.PackageURL{
Type: "bottlerocket",
Name: "glibc",
Version: "2.40",
Qualifiers: packageurl.Qualifiers{
{
Key: "epoch",
Value: "1",
},
{
Key: "distro",
Value: "bottlerocket-1.34.0",
},
},
},
wantPkg: &ftypes.Package{
ID: "glibc@2.40",
Name: "glibc",
Version: "2.40",
Epoch: 1,
Identifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: "bottlerocket",
Name: "glibc",
Version: "2.40",
Qualifiers: packageurl.Qualifiers{
{
Key: "epoch",
Value: "1",
},
{
Key: "distro",
Value: "bottlerocket-1.34.0",
},
},
},
},
},
},
{
name: "coreos with epoch",
pkgURL: &purl.PackageURL{
Type: "rpm",
Namespace: "coreos",
Name: "glibc",
Version: "2.40",
Qualifiers: packageurl.Qualifiers{
{
Key: "epoch",
Value: "1",
},
{
Key: "distro",
Value: "coreos-1.34.0",
},
},
},
wantPkg: &ftypes.Package{
ID: "glibc@2.40",
Name: "glibc",
Version: "2.40",
Epoch: 1,
Identifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: "rpm",
Namespace: "coreos",
Name: "glibc",
Version: "2.40",
Qualifiers: packageurl.Qualifiers{
{
Key: "epoch",
Value: "1",
},
{
Key: "distro",
Value: "coreos-1.34.0",
},
},
},
},
},
},
{
name: "wrong epoch",
pkgURL: &purl.PackageURL{
Type: packageurl.TypeRPM,
Namespace: "redhat",
Name: "acl",
Version: "2.2.53-1.el8",
Qualifiers: packageurl.Qualifiers{
{
Key: "epoch",
Value: "wrong",
},
},
},
wantPkg: &ftypes.Package{
ID: "acl@2.2.53-1.el8",
Name: "acl",
Version: "2.2.53",
Release: "1.el8",
Identifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeRPM,
Namespace: "redhat",
Name: "acl",
Version: "2.2.53-1.el8",
Qualifiers: packageurl.Qualifiers{
{
Key: "epoch",
Value: "wrong",
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.pkgURL.Package()
assert.Equal(t, tt.wantPkg, got)
})
}
}
func TestPackageURL_LangType(t *testing.T) {
tests := []struct {
name string
purl packageurl.PackageURL
want ftypes.LangType
}{
{
name: "maven",
purl: packageurl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.springframework",
Name: "spring-core",
Version: "5.0.4.RELEASE",
},
want: ftypes.Jar,
},
{
name: "k8s",
purl: packageurl.PackageURL{
Type: purl.TypeK8s,
Name: "kubelet",
Version: "1.21.1",
},
want: ftypes.K8sUpstream,
},
{
name: "eks",
purl: packageurl.PackageURL{
Type: purl.TypeK8s,
Namespace: purl.NamespaceEKS,
Name: "kubelet",
Version: "1.21.1",
},
want: ftypes.EKS,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := purl.PackageURL(tt.purl)
assert.Equalf(t, tt.want, p.LangType(), "LangType()")
})
}
}
func TestPackageURL_Match(t *testing.T) {
tests := []struct {
name string
constraint string
target string
want bool
}{
{
name: "same purl",
constraint: "pkg:golang/github.com/aquasecurity/trivy@v0.49.0",
target: "pkg:golang/github.com/aquasecurity/trivy@v0.49.0",
want: true,
},
{
name: "different type",
constraint: "pkg:golang/github.com/aquasecurity/trivy@v0.49.0",
target: "pkg:maven/github.com/aquasecurity/trivy@0.49.0",
want: false,
},
{
name: "different namespace",
constraint: "pkg:golang/github.com/aquasecurity/trivy@v0.49.0",
target: "pkg:golang/github.com/aquasecurity2/trivy@v.49.0",
want: false,
},
{
name: "different name",
constraint: "pkg:golang/github.com/aquasecurity/trivy@v0.49.0",
target: "pkg:golang/github.com/aquasecurity/tracee@v0.49.0",
want: false,
},
{
name: "different version",
constraint: "pkg:golang/github.com/aquasecurity/trivy@v0.49.0",
target: "pkg:golang/github.com/aquasecurity/trivy@v0.49.1",
want: false,
},
{
name: "version wildcard",
constraint: "pkg:golang/github.com/aquasecurity/trivy",
target: "pkg:golang/github.com/aquasecurity/trivy@v0.50.0",
want: true,
},
{
name: "different qualifier",
constraint: "pkg:bitnami/wordpress@6.2.0?arch=arm64&distro=debian-12",
target: "pkg:bitnami/wordpress@6.2.0?arch=arm64&distro=debian-13",
want: false,
},
{
name: "target more qualifiers",
constraint: "pkg:bitnami/wordpress@6.2.0?arch=arm64",
target: "pkg:bitnami/wordpress@6.2.0?arch=arm64&distro=debian-13",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c, err := purl.FromString(tt.constraint)
require.NoError(t, err)
p, err := purl.FromString(tt.target)
require.NoError(t, err)
assert.Equalf(t, tt.want, c.Match(p.Unwrap()), "Match()")
})
}
}