github.com/anchore/syft@v1.38.2/syft/format/purls/decoder_test.go (about)

     1  package purls
     2  
     3  import (
     4  	"bytes"
     5  	"regexp"
     6  	"slices"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/anchore/packageurl-go"
    14  	"github.com/anchore/syft/internal/cmptest"
    15  	"github.com/anchore/syft/syft/pkg"
    16  )
    17  
    18  func TestDecoder_Decode(t *testing.T) {
    19  	tests := []struct {
    20  		purl     string
    21  		expected []pkg.Package
    22  	}{
    23  		{
    24  			purl: "pkg:generic/some-package@1.2.3",
    25  			expected: []pkg.Package{
    26  				{
    27  					Name:    "some-package",
    28  					Type:    pkg.UnknownPkg,
    29  					Version: "1.2.3",
    30  					PURL:    "pkg:generic/some-package@1.2.3",
    31  				},
    32  			},
    33  		},
    34  		{
    35  			purl: "pkg:npm/some-package@1.2.3",
    36  			expected: []pkg.Package{
    37  				{
    38  					Name:     "some-package",
    39  					Type:     pkg.NpmPkg,
    40  					Language: pkg.JavaScript,
    41  					Version:  "1.2.3",
    42  					PURL:     "pkg:npm/some-package@1.2.3",
    43  				},
    44  			},
    45  		},
    46  		{
    47  			purl: "pkg:apk/curl@7.61.1",
    48  			expected: []pkg.Package{
    49  				{
    50  					Name:    "curl",
    51  					Version: "7.61.1",
    52  					Type:    pkg.ApkPkg,
    53  					PURL:    "pkg:apk/curl@7.61.1",
    54  				},
    55  			},
    56  		},
    57  		{
    58  			purl: "pkg:deb/debian/sysv-rc@2.88dsf-59?arch=all&distro=debian-jessie&upstream=sysvinit",
    59  			expected: []pkg.Package{
    60  				{
    61  					Name:    "sysv-rc",
    62  					Version: "2.88dsf-59",
    63  					Type:    pkg.DebPkg,
    64  					PURL:    "pkg:deb/debian/sysv-rc@2.88dsf-59?arch=all&distro=debian-jessie&upstream=sysvinit",
    65  				},
    66  			},
    67  		},
    68  		{
    69  			purl: "pkg:apk/libcrypto3@3.3.2?upstream=openssl",
    70  			expected: []pkg.Package{
    71  				{
    72  					Name:    "libcrypto3",
    73  					Version: "3.3.2",
    74  					Type:    pkg.ApkPkg,
    75  					PURL:    "pkg:apk/libcrypto3@3.3.2?upstream=openssl",
    76  				},
    77  			},
    78  		},
    79  		{
    80  			purl: "pkg:apk/libcrypto3@3.3.2?upstream=openssl%403.2.1", // %40 is @
    81  			expected: []pkg.Package{
    82  				{
    83  					Name:    "libcrypto3",
    84  					Version: "3.3.2",
    85  					Type:    pkg.ApkPkg,
    86  					PURL:    "pkg:apk/libcrypto3@3.3.2?upstream=openssl%403.2.1",
    87  				},
    88  			},
    89  		},
    90  		{
    91  			purl: "pkg:rpm/redhat/systemd-x@239-82.el8_10.2?arch=aarch64&distro=rhel-8.10&upstream=systemd-239-82.el8_10.2.src.rpm",
    92  			expected: []pkg.Package{
    93  				{
    94  					Name:    "systemd-x",
    95  					Version: "239-82.el8_10.2",
    96  					Type:    pkg.RpmPkg,
    97  					PURL:    "pkg:rpm/redhat/systemd-x@239-82.el8_10.2?arch=aarch64&distro=rhel-8.10&upstream=systemd-239-82.el8_10.2.src.rpm",
    98  				},
    99  			},
   100  		},
   101  		{
   102  			purl: "pkg:rpm/redhat/dbus-common@1.12.8-26.el8?arch=noarch&distro=rhel-8.10&epoch=1&upstream=dbus-1.12.8-26.el8.src.rpm",
   103  			expected: []pkg.Package{
   104  				{
   105  					Name:    "dbus-common",
   106  					Version: "1:1.12.8-26.el8",
   107  					Type:    pkg.RpmPkg,
   108  					PURL:    "pkg:rpm/redhat/dbus-common@1.12.8-26.el8?arch=noarch&distro=rhel-8.10&epoch=1&upstream=dbus-1.12.8-26.el8.src.rpm",
   109  				},
   110  			},
   111  		},
   112  		{
   113  			purl: "pkg:apk/curl@7.61.1?arch=aarch64&distro=alpine-3.20.3",
   114  			expected: []pkg.Package{
   115  				{
   116  					Name:    "curl",
   117  					Version: "7.61.1",
   118  					Type:    pkg.ApkPkg,
   119  					PURL:    "pkg:apk/curl@7.61.1?arch=aarch64&distro=alpine-3.20.3",
   120  				},
   121  			},
   122  		},
   123  		{
   124  			purl: "pkg:golang/k8s.io/ingress-nginx@v1.11.2",
   125  			expected: []pkg.Package{
   126  				{
   127  					Name:     "k8s.io/ingress-nginx",
   128  					Version:  "v1.11.2",
   129  					Type:     pkg.GoModulePkg,
   130  					Language: pkg.Go,
   131  					PURL:     "pkg:golang/k8s.io/ingress-nginx@v1.11.2",
   132  				},
   133  			},
   134  		},
   135  		{
   136  			purl: "pkg:golang/github.com/wazuh/wazuh@v4.5.0",
   137  			expected: []pkg.Package{
   138  				{
   139  					Name:     "github.com/wazuh/wazuh",
   140  					Version:  "v4.5.0",
   141  					Type:     pkg.GoModulePkg,
   142  					PURL:     "pkg:golang/github.com/wazuh/wazuh@v4.5.0",
   143  					Language: pkg.Go,
   144  				},
   145  			},
   146  		},
   147  		{
   148  			purl: "pkg:golang/wazuh@v4.5.0",
   149  			expected: []pkg.Package{
   150  				{
   151  					Name:     "wazuh",
   152  					Version:  "v4.5.0",
   153  					Type:     pkg.GoModulePkg,
   154  					PURL:     "pkg:golang/wazuh@v4.5.0",
   155  					Language: pkg.Go,
   156  				},
   157  			},
   158  		},
   159  		{
   160  			purl: "pkg:maven/org.apache/some-pkg@4.11.3",
   161  			expected: []pkg.Package{
   162  				{
   163  					Name:     "some-pkg",
   164  					Version:  "4.11.3",
   165  					Type:     pkg.JavaPkg,
   166  					PURL:     "pkg:maven/org.apache/some-pkg@4.11.3",
   167  					Language: pkg.Java,
   168  					// we intentionally do not claim we found a pom properties file (don't derive this from the purl).
   169  					// but we need a metadata allocated since all Java packages have a this metadata type (a consistency point)
   170  					Metadata: pkg.JavaArchive{},
   171  				},
   172  			},
   173  		},
   174  	}
   175  
   176  	for _, test := range tests {
   177  		t.Run(test.purl, func(t *testing.T) {
   178  			dec := NewFormatDecoder()
   179  			got, _, _, err := dec.Decode(strings.NewReader(test.purl))
   180  			require.NoError(t, err)
   181  
   182  			if diff := cmp.Diff(test.expected, got.Artifacts.Packages.Sorted(), cmptest.DefaultOptions()...); diff != "" {
   183  				t.Errorf("expected packages (-want +got):\n%s", diff)
   184  			}
   185  		})
   186  	}
   187  }
   188  
   189  func Test_DecodeEncodeCycle(t *testing.T) {
   190  	tests := []struct {
   191  		name  string
   192  		input string
   193  	}{
   194  		{
   195  			name:  "basic",
   196  			input: "pkg:generic/some-package@1.2.3",
   197  		},
   198  		{
   199  			name:  "multiple",
   200  			input: "pkg:generic/pkg1\npkg:generic/pkg2\n\npkg:npm/@vercel/ncc@2.9.5",
   201  		},
   202  		{
   203  			name:  "java",
   204  			input: "pkg:maven/org.apache/some-thing@4.11.3",
   205  		},
   206  		{
   207  			name:  "leading whitespace",
   208  			input: "     \n \t  pkg:maven/org.apache/some-thing@4.11.3",
   209  		},
   210  	}
   211  
   212  	for _, tt := range tests {
   213  		t.Run(tt.name, func(t *testing.T) {
   214  			dec := NewFormatDecoder()
   215  			decodedSBOM, _, _, err := dec.Decode(strings.NewReader(tt.input))
   216  			require.NoError(t, err)
   217  
   218  			var buf bytes.Buffer
   219  			enc := NewFormatEncoder()
   220  			require.NoError(t, enc.Encode(&buf, *decodedSBOM))
   221  
   222  			in := strings.TrimSpace(regexp.MustCompile(`\s+`).ReplaceAllString(strings.TrimSpace(tt.input), "\n"))
   223  			expected := strings.Split(in, "\n")
   224  			slices.Sort(expected)
   225  
   226  			got := strings.Split(strings.TrimSpace(buf.String()), "\n")
   227  			slices.Sort(got)
   228  			require.EqualValues(t, expected, got)
   229  
   230  			for _, item := range got {
   231  				// require every result is a valid PURL -- no whitespace lines, etc.
   232  				_, err = packageurl.FromString(item)
   233  				require.NoError(t, err)
   234  			}
   235  		})
   236  	}
   237  }