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  }