github.com/google/osv-scalibr@v0.4.1/purl/purl_test.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package purl_test 16 17 import ( 18 "testing" 19 20 "github.com/google/go-cmp/cmp" 21 "github.com/google/osv-scalibr/purl" 22 "github.com/package-url/packageurl-go" 23 ) 24 25 func TestFromString(t *testing.T) { 26 tests := []struct { 27 name string 28 purl string 29 want purl.PackageURL 30 }{ 31 // Tests should be ordered by type as they appear in 32 // https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#cargo 33 { 34 name: "bitbucket", 35 purl: "pkg:bitbucket/birkenfeld/pygments-main@244fd47e07d1014f0aed9c", 36 want: purl.PackageURL{ 37 Type: "bitbucket", 38 Namespace: "birkenfeld", 39 Name: "pygments-main", 40 Version: "244fd47e07d1014f0aed9c", 41 }, 42 }, { 43 name: "cargo", 44 purl: "pkg:cargo/rand@0.7.2", 45 want: purl.PackageURL{ 46 Type: "cargo", 47 Name: "rand", 48 Version: "0.7.2", 49 }, 50 }, { 51 name: "composer", 52 purl: "pkg:composer/laravel/laravel@5.5.0", 53 want: purl.PackageURL{ 54 Type: "composer", 55 Namespace: "laravel", 56 Name: "laravel", 57 Version: "5.5.0", 58 }, 59 }, { 60 name: "deb", 61 purl: "pkg:deb/debian/curl@7.50.3-1?arch=i386&distro=jessie", 62 want: purl.PackageURL{ 63 Type: "deb", 64 Namespace: "debian", 65 Name: "curl", 66 Version: "7.50.3-1", 67 Qualifiers: purl.QualifiersFromMap(map[string]string{"arch": "i386", "distro": "jessie"}), 68 }, 69 }, { 70 name: "gem", 71 purl: "pkg:gem/jruby-launcher@1.1.2?platform=java", 72 want: purl.PackageURL{ 73 Type: "gem", 74 Name: "jruby-launcher", 75 Version: "1.1.2", 76 Qualifiers: purl.QualifiersFromMap(map[string]string{"platform": "java"}), 77 }, 78 }, { 79 name: "github", 80 purl: "pkg:github/package-url/purl-spec@244fd47e07d1004#everybody/loves/dogs", 81 want: purl.PackageURL{ 82 Type: "github", 83 Namespace: "package-url", 84 Name: "purl-spec", 85 Version: "244fd47e07d1004", 86 Subpath: "everybody/loves/dogs", 87 }, 88 }, { 89 name: "golang", 90 purl: "pkg:golang/package-name@1.2.3", 91 want: purl.PackageURL{ 92 Type: "golang", 93 Name: "package-name", 94 Version: "1.2.3", 95 }, 96 }, { 97 name: "maven", 98 purl: "pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?classifier=dist&type=zip", 99 want: purl.PackageURL{ 100 Type: "maven", 101 Namespace: "org.apache.xmlgraphics", 102 Name: "batik-anim", 103 Version: "1.9.1", 104 Qualifiers: purl.QualifiersFromMap(map[string]string{"classifier": "dist", "type": "zip"}), 105 }, 106 }, { 107 name: "npm", 108 purl: "pkg:npm/foobar@12.3.1", 109 want: purl.PackageURL{ 110 Type: "npm", 111 Name: "foobar", 112 Version: "12.3.1", 113 }, 114 }, { 115 name: "rpm", 116 purl: "pkg:rpm/fedora/curl@7.50.3-1.fc25?arch=i386&distro=fedora-25&sourcerpm=curl-7.50.3-1.fc25.src.rpm", 117 want: purl.PackageURL{ 118 Type: "rpm", 119 Namespace: "fedora", 120 Name: "curl", 121 Version: "7.50.3-1.fc25", 122 Qualifiers: purl.QualifiersFromMap(map[string]string{"arch": "i386", "distro": "fedora-25", "sourcerpm": "curl-7.50.3-1.fc25.src.rpm"}), 123 }, 124 }, { 125 name: "cos", 126 purl: "pkg:cos/python-exec@17162.336.16?distro=cos-101", 127 want: purl.PackageURL{ 128 Type: purl.TypeCOS, 129 Name: "python-exec", 130 Version: "17162.336.16", 131 Qualifiers: purl.QualifiersFromMap(map[string]string{"distro": "cos-101"}), 132 }, 133 }, 134 } 135 136 for _, test := range tests { 137 t.Run(test.name, func(t *testing.T) { 138 got, err := purl.FromString(test.purl) 139 if err != nil { 140 t.Fatalf("FromString(%+v) error: %v", test.purl, err) 141 } 142 if diff := cmp.Diff(test.want.String(), got.String()); diff != "" { 143 t.Fatalf("FromString(%+v) returned unexpected result; diff (-want +got):\n%s", test.purl, diff) 144 } 145 }) 146 } 147 } 148 149 func TestFromStringInvalidPURL(t *testing.T) { 150 tests := []struct { 151 name string 152 purl string 153 }{ 154 { 155 name: "missing_type", 156 purl: "pkg:/package-name@1.2.3", 157 }, { 158 name: "invalid_type", 159 purl: "pkg:unknown/package-name@1.2.3", 160 }, 161 } 162 163 for _, test := range tests { 164 t.Run(test.name, func(t *testing.T) { 165 if _, err := purl.FromString(test.purl); err == nil { 166 t.Fatalf("FromString(%+v) got no error, expected one", test.purl) 167 } 168 }) 169 } 170 } 171 172 func TestQualifiersFromMap(t *testing.T) { 173 tests := []struct { 174 name string 175 qualifierMap map[string]string 176 wantQualifiers purl.Qualifiers 177 }{ 178 { 179 name: "normal_transcription", 180 qualifierMap: map[string]string{ 181 "qual": "ifier", 182 "other": "qualifier", 183 }, 184 wantQualifiers: []packageurl.Qualifier{ 185 {Key: "other", Value: "qualifier"}, 186 {Key: "qual", Value: "ifier"}, 187 }, 188 }, { 189 name: "filters_only_empty_value", 190 qualifierMap: map[string]string{ 191 "empty": "", 192 "other": "qualifier", 193 }, 194 wantQualifiers: []packageurl.Qualifier{ 195 {Key: "other", Value: "qualifier"}, 196 }, 197 }, { 198 name: "empty_qualifiers_if_all_empty", 199 qualifierMap: map[string]string{ 200 "empty": "", 201 }, 202 wantQualifiers: []packageurl.Qualifier{}, 203 }, 204 } 205 206 for _, test := range tests { 207 t.Run(test.name, func(t *testing.T) { 208 got := purl.QualifiersFromMap(test.qualifierMap) 209 210 if diff := cmp.Diff(test.wantQualifiers, got); diff != "" { 211 t.Fatalf("QualifiersFromMap(%+v) returned unexpected result; diff (-want +got):\n%s", test.qualifierMap, diff) 212 } 213 }) 214 } 215 }