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 }