github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/javascript/packagejson/metadata/metadata_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 metadata_test
    16  
    17  import (
    18  	"testing"
    19  
    20  	"github.com/google/go-cmp/cmp"
    21  	"github.com/google/go-cmp/cmp/cmpopts"
    22  	"github.com/google/osv-scalibr/extractor/filesystem/language/javascript/packagejson/metadata"
    23  	"google.golang.org/protobuf/proto"
    24  	"google.golang.org/protobuf/testing/protocmp"
    25  
    26  	pb "github.com/google/osv-scalibr/binary/proto/scan_result_go_proto"
    27  )
    28  
    29  func TestUnmarshalJSON_Person(t *testing.T) {
    30  	testCases := []struct {
    31  		desc    string
    32  		input   string
    33  		want    *metadata.Person
    34  		wantErr error
    35  	}{
    36  		{
    37  			desc:  "empty person",
    38  			input: `""`,
    39  			want:  &metadata.Person{},
    40  		},
    41  		{
    42  			desc:  "full person string",
    43  			input: `"Developer <dev@corp.com> (http://dev.blog.com)"`,
    44  			want: &metadata.Person{
    45  				Name:  "Developer",
    46  				Email: "dev@corp.com",
    47  				URL:   "http://dev.blog.com",
    48  			},
    49  		},
    50  		{
    51  			desc:  "person string no name",
    52  			input: `"<dev@corp.com> (http://dev.blog.com)"`,
    53  			want:  &metadata.Person{},
    54  		},
    55  		{
    56  			desc:  "person string no email",
    57  			input: `"Developer (http://dev.blog.com)"`,
    58  			want: &metadata.Person{
    59  				Name: "Developer",
    60  				URL:  "http://dev.blog.com",
    61  			},
    62  		},
    63  		{
    64  			desc:  "person string no url",
    65  			input: `"Developer <dev@corp.com>"`,
    66  			want: &metadata.Person{
    67  				Name:  "Developer",
    68  				Email: "dev@corp.com",
    69  			},
    70  		},
    71  		{
    72  			desc:  "empty person object",
    73  			input: `{}`,
    74  			want:  &metadata.Person{},
    75  		},
    76  		{
    77  			desc:    "invalid json object",
    78  			input:   `:"Developer"`,
    79  			want:    &metadata.Person{},
    80  			wantErr: cmpopts.AnyError,
    81  		},
    82  		{
    83  			desc:  "full person object",
    84  			input: `{"name":"Developer","email":"dev@corp.com","url":"http://dev.blog.com"}`,
    85  			want: &metadata.Person{
    86  				Name:  "Developer",
    87  				Email: "dev@corp.com",
    88  				URL:   "http://dev.blog.com",
    89  			},
    90  		},
    91  		{
    92  			desc:  "person object no name",
    93  			input: `{"email":"dev@corp.com","url":"http://dev.blog.com"}`,
    94  			want:  &metadata.Person{},
    95  		},
    96  		{
    97  			desc:  "person object no email",
    98  			input: `{"name":"Developer","url":"http://dev.blog.com"}`,
    99  			want: &metadata.Person{
   100  				Name: "Developer",
   101  				URL:  "http://dev.blog.com",
   102  			},
   103  		},
   104  		{
   105  			desc:  "person object no url",
   106  			input: `{"name":"Developer","email":"dev@corp.com"}`,
   107  			want: &metadata.Person{
   108  				Name:  "Developer",
   109  				Email: "dev@corp.com",
   110  			},
   111  		},
   112  	}
   113  
   114  	for _, tc := range testCases {
   115  		t.Run(tc.desc, func(t *testing.T) {
   116  			p := &metadata.Person{}
   117  			if err := p.UnmarshalJSON([]byte(tc.input)); !cmp.Equal(err, tc.wantErr, cmpopts.EquateErrors()) {
   118  				t.Fatalf("UnmarshalJSON(%+v) error: got %v, want %v\n", tc.input, err, tc.wantErr)
   119  			}
   120  			if diff := cmp.Diff(tc.want, p); diff != "" {
   121  				t.Errorf("UnmarshalJSON(%+v) diff (-want +got):\n%s", tc.input, diff)
   122  			}
   123  		})
   124  	}
   125  }
   126  
   127  func TestPersonString(t *testing.T) {
   128  	testCases := []struct {
   129  		desc  string
   130  		input *metadata.Person
   131  		want  string
   132  	}{
   133  		{
   134  			desc:  "nil input",
   135  			input: nil,
   136  			want:  "",
   137  		},
   138  		{
   139  			desc: "person_with_no_name",
   140  			input: &metadata.Person{
   141  				Email: "dev@corp.com",
   142  				URL:   "http://dev.blog.com",
   143  			},
   144  			want: "",
   145  		},
   146  		{
   147  			desc: "person_with_no_email",
   148  			input: &metadata.Person{
   149  				Name: "Developer",
   150  				URL:  "http://dev.blog.com",
   151  			},
   152  			want: "Developer (http://dev.blog.com)",
   153  		},
   154  		{
   155  			desc: "person_with_no_url",
   156  			input: &metadata.Person{
   157  				Name:  "Developer",
   158  				Email: "dev@corp.com",
   159  			},
   160  			want: "Developer <dev@corp.com>",
   161  		},
   162  		{
   163  			desc: "person_object",
   164  			input: &metadata.Person{
   165  				Name:  "Developer",
   166  				Email: "dev@corp.com",
   167  				URL:   "http://dev.blog.com",
   168  			},
   169  			want: "Developer <dev@corp.com> (http://dev.blog.com)",
   170  		},
   171  	}
   172  
   173  	for _, tc := range testCases {
   174  		t.Run(tc.desc, func(t *testing.T) {
   175  			got := tc.input.PersonString()
   176  			if diff := cmp.Diff(tc.want, got); diff != "" {
   177  				t.Errorf("metadata.PersonString(%+v) diff (-want +got):\n%s", tc.input, diff)
   178  			}
   179  		})
   180  	}
   181  }
   182  
   183  func TestPersonFromString(t *testing.T) {
   184  	testCases := []struct {
   185  		desc  string
   186  		input string
   187  		want  *metadata.Person
   188  	}{
   189  		{
   190  			desc:  "empty input",
   191  			input: "",
   192  			want:  nil,
   193  		},
   194  		{
   195  			desc:  "name, email, and url",
   196  			input: "Developer <dev@corp.com> (http://dev.blog.com)",
   197  			want: &metadata.Person{
   198  				Name:  "Developer",
   199  				Email: "dev@corp.com",
   200  				URL:   "http://dev.blog.com",
   201  			},
   202  		},
   203  		{
   204  			desc:  "name, and url",
   205  			input: "Developer (http://dev.blog.com)",
   206  			want: &metadata.Person{
   207  				Name: "Developer",
   208  				URL:  "http://dev.blog.com",
   209  			},
   210  		},
   211  		{
   212  			desc:  "name, email",
   213  			input: "Developer <dev@corp.com>",
   214  			want: &metadata.Person{
   215  				Name:  "Developer",
   216  				Email: "dev@corp.com",
   217  			},
   218  		},
   219  		{
   220  			desc:  "name only",
   221  			input: "Developer",
   222  			want: &metadata.Person{
   223  				Name: "Developer",
   224  			},
   225  		},
   226  	}
   227  
   228  	for _, tc := range testCases {
   229  		t.Run(tc.desc, func(t *testing.T) {
   230  			got := metadata.PersonFromString(tc.input)
   231  			if diff := cmp.Diff(tc.want, got); diff != "" {
   232  				t.Errorf("metadata.PersonFromString(%+v) diff (-want +got):\n%s", tc.input, diff)
   233  			}
   234  		})
   235  	}
   236  }
   237  
   238  func TestSetProto(t *testing.T) {
   239  	testCases := []struct {
   240  		desc string
   241  		m    *metadata.JavascriptPackageJSONMetadata
   242  		p    *pb.Package
   243  		want *pb.Package
   244  	}{
   245  		{
   246  			desc: "nil_metadata",
   247  			m:    nil,
   248  			p:    &pb.Package{Name: "some-package"},
   249  			want: &pb.Package{Name: "some-package"},
   250  		},
   251  		{
   252  			desc: "nil_package",
   253  			m: &metadata.JavascriptPackageJSONMetadata{
   254  				Author: &metadata.Person{
   255  					Name:  "some-author",
   256  					Email: "some-author@google.com",
   257  				},
   258  			},
   259  			p:    nil,
   260  			want: nil,
   261  		},
   262  		{
   263  			desc: "set_metadata",
   264  			m: &metadata.JavascriptPackageJSONMetadata{
   265  				Author: &metadata.Person{
   266  					Name:  "some-author",
   267  					Email: "some-author@google.com",
   268  				},
   269  				Source: metadata.Unknown,
   270  			},
   271  			p: &pb.Package{Name: "some-package"},
   272  			want: &pb.Package{
   273  				Name: "some-package",
   274  				Metadata: &pb.Package_JavascriptMetadata{
   275  					JavascriptMetadata: &pb.JavascriptPackageJSONMetadata{
   276  						Author: "some-author <some-author@google.com>",
   277  						Source: pb.PackageSource_UNKNOWN,
   278  					},
   279  				},
   280  			},
   281  		},
   282  		{
   283  			desc: "override_metadata",
   284  			m: &metadata.JavascriptPackageJSONMetadata{
   285  				Author: &metadata.Person{
   286  					Name:  "some-other-author",
   287  					Email: "some-other-author@google.com",
   288  				},
   289  				Source: metadata.Unknown,
   290  			},
   291  			p: &pb.Package{
   292  				Name: "some-package",
   293  				Metadata: &pb.Package_JavascriptMetadata{
   294  					JavascriptMetadata: &pb.JavascriptPackageJSONMetadata{
   295  						Author: "some-author <some-author@google.com>",
   296  					},
   297  				},
   298  			},
   299  			want: &pb.Package{
   300  				Name: "some-package",
   301  				Metadata: &pb.Package_JavascriptMetadata{
   302  					JavascriptMetadata: &pb.JavascriptPackageJSONMetadata{
   303  						Author: "some-other-author <some-other-author@google.com>",
   304  						Source: pb.PackageSource_UNKNOWN,
   305  					},
   306  				},
   307  			},
   308  		},
   309  		{
   310  			desc: "set_all_fields",
   311  			m: &metadata.JavascriptPackageJSONMetadata{
   312  				Author: &metadata.Person{
   313  					Name:  "some-author",
   314  					Email: "some-author@google.com",
   315  				},
   316  				Maintainers: []*metadata.Person{
   317  					{
   318  						Name:  "first-maintainer",
   319  						Email: "first-maintainer@google.com",
   320  					},
   321  					{
   322  						Name:  "second-maintainer",
   323  						Email: "second-maintainer@google.com",
   324  					},
   325  				},
   326  				Contributors: []*metadata.Person{
   327  					{
   328  						Name:  "first-contributor",
   329  						Email: "first-contributor@google.com",
   330  					},
   331  					{
   332  						Name:  "second-contributor",
   333  						Email: "second-contributor@google.com",
   334  					},
   335  				},
   336  				Source: metadata.PublicRegistry,
   337  			},
   338  			p: &pb.Package{Name: "some-package"},
   339  			want: &pb.Package{
   340  				Name: "some-package",
   341  				Metadata: &pb.Package_JavascriptMetadata{
   342  					JavascriptMetadata: &pb.JavascriptPackageJSONMetadata{
   343  						Author: "some-author <some-author@google.com>",
   344  						Maintainers: []string{
   345  							"first-maintainer <first-maintainer@google.com>",
   346  							"second-maintainer <second-maintainer@google.com>",
   347  						},
   348  						Contributors: []string{
   349  							"first-contributor <first-contributor@google.com>",
   350  							"second-contributor <second-contributor@google.com>",
   351  						},
   352  						Source: pb.PackageSource_PUBLIC_REGISTRY,
   353  					},
   354  				},
   355  			},
   356  		},
   357  		{
   358  			desc: "set_public_registry_NPMResolutionSource",
   359  			m: &metadata.JavascriptPackageJSONMetadata{
   360  				Author: &metadata.Person{
   361  					Name:  "some-author",
   362  					Email: "some-author@google.com",
   363  				},
   364  				Source: metadata.PublicRegistry,
   365  			},
   366  			p: &pb.Package{Name: "some-package"},
   367  			want: &pb.Package{
   368  				Name: "some-package",
   369  				Metadata: &pb.Package_JavascriptMetadata{
   370  					JavascriptMetadata: &pb.JavascriptPackageJSONMetadata{
   371  						Author: "some-author <some-author@google.com>",
   372  						Source: pb.PackageSource_PUBLIC_REGISTRY,
   373  					},
   374  				},
   375  			},
   376  		},
   377  		{
   378  			desc: "set_other_NPMResolutionSource",
   379  			m: &metadata.JavascriptPackageJSONMetadata{
   380  				Author: &metadata.Person{
   381  					Name:  "some-author",
   382  					Email: "some-author@google.com",
   383  				},
   384  				Source: metadata.Other,
   385  			},
   386  			p: &pb.Package{Name: "some-package"},
   387  			want: &pb.Package{
   388  				Name: "some-package",
   389  				Metadata: &pb.Package_JavascriptMetadata{
   390  					JavascriptMetadata: &pb.JavascriptPackageJSONMetadata{
   391  						Author: "some-author <some-author@google.com>",
   392  						Source: pb.PackageSource_OTHER,
   393  					},
   394  				},
   395  			},
   396  		},
   397  		{
   398  			desc: "set_local_NPMResolutionSource",
   399  			m: &metadata.JavascriptPackageJSONMetadata{
   400  				Author: &metadata.Person{
   401  					Name:  "some-author",
   402  					Email: "some-author@google.com",
   403  				},
   404  				Source: metadata.Local,
   405  			},
   406  			p: &pb.Package{Name: "some-package"},
   407  			want: &pb.Package{
   408  				Name: "some-package",
   409  				Metadata: &pb.Package_JavascriptMetadata{
   410  					JavascriptMetadata: &pb.JavascriptPackageJSONMetadata{
   411  						Author: "some-author <some-author@google.com>",
   412  						Source: pb.PackageSource_LOCAL,
   413  					},
   414  				},
   415  			},
   416  		},
   417  	}
   418  
   419  	for _, tc := range testCases {
   420  		t.Run(tc.desc, func(t *testing.T) {
   421  			p := proto.Clone(tc.p).(*pb.Package)
   422  			tc.m.SetProto(p)
   423  			opts := []cmp.Option{
   424  				protocmp.Transform(),
   425  			}
   426  			if diff := cmp.Diff(tc.want, p, opts...); diff != "" {
   427  				t.Errorf("Metatadata{%+v}.SetProto(%+v): (-want +got):\n%s", tc.m, tc.p, diff)
   428  			}
   429  
   430  			// Test the reverse conversion for completeness.
   431  
   432  			if tc.p == nil && tc.want == nil {
   433  				return
   434  			}
   435  
   436  			got := metadata.ToStruct(p.GetJavascriptMetadata())
   437  			if diff := cmp.Diff(tc.m, got); diff != "" {
   438  				t.Errorf("ToStruct(%+v): (-want +got):\n%s", p.GetJavascriptMetadata(), diff)
   439  			}
   440  		})
   441  	}
   442  }
   443  
   444  func TestToStruct(t *testing.T) {
   445  	testCases := []struct {
   446  		desc string
   447  		m    *pb.JavascriptPackageJSONMetadata
   448  		want *metadata.JavascriptPackageJSONMetadata
   449  	}{
   450  		{
   451  			desc: "nil",
   452  			m:    nil,
   453  			want: nil,
   454  		},
   455  		{
   456  			desc: "some_fields",
   457  			m: &pb.JavascriptPackageJSONMetadata{
   458  				Author: "some-author",
   459  			},
   460  			want: &metadata.JavascriptPackageJSONMetadata{
   461  				Author: &metadata.Person{
   462  					Name: "some-author",
   463  				},
   464  				Source: metadata.Unknown,
   465  			},
   466  		},
   467  		{
   468  			desc: "all_fields",
   469  			m: &pb.JavascriptPackageJSONMetadata{
   470  				Author: "some-author <some-author@google.com>",
   471  				Maintainers: []string{
   472  					"first-maintainer <first-maintainer@google.com>",
   473  					"second-maintainer <second-maintainer@google.com>",
   474  				},
   475  				Contributors: []string{
   476  					"first-contributor <first-contributor@google.com>",
   477  					"second-contributor <second-contributor@google.com>",
   478  				},
   479  				Source: pb.PackageSource_PUBLIC_REGISTRY,
   480  			},
   481  			want: &metadata.JavascriptPackageJSONMetadata{
   482  				Author: &metadata.Person{
   483  					Name:  "some-author",
   484  					Email: "some-author@google.com",
   485  				},
   486  				Contributors: []*metadata.Person{
   487  					{
   488  						Name:  "first-contributor",
   489  						Email: "first-contributor@google.com",
   490  					},
   491  					{
   492  						Name:  "second-contributor",
   493  						Email: "second-contributor@google.com",
   494  					},
   495  				},
   496  				Maintainers: []*metadata.Person{
   497  					{
   498  						Name:  "first-maintainer",
   499  						Email: "first-maintainer@google.com",
   500  					},
   501  					{
   502  						Name:  "second-maintainer",
   503  						Email: "second-maintainer@google.com",
   504  					},
   505  				},
   506  				Source: metadata.PublicRegistry,
   507  			},
   508  		},
   509  		{
   510  			desc: "set_public_registry_NPMResolutionSource",
   511  			m: &pb.JavascriptPackageJSONMetadata{
   512  				Author: "some-author <some-author@google.com>",
   513  				Source: pb.PackageSource_PUBLIC_REGISTRY,
   514  			},
   515  			want: &metadata.JavascriptPackageJSONMetadata{
   516  				Author: &metadata.Person{
   517  					Name:  "some-author",
   518  					Email: "some-author@google.com",
   519  				},
   520  				Source: metadata.PublicRegistry,
   521  			},
   522  		},
   523  		{
   524  			desc: "set_other_NPMResolutionSource",
   525  			m: &pb.JavascriptPackageJSONMetadata{
   526  				Author: "some-author <some-author@google.com>",
   527  				Source: pb.PackageSource_OTHER,
   528  			},
   529  			want: &metadata.JavascriptPackageJSONMetadata{
   530  				Author: &metadata.Person{
   531  					Name:  "some-author",
   532  					Email: "some-author@google.com",
   533  				},
   534  				Source: metadata.Other,
   535  			},
   536  		},
   537  		{
   538  			desc: "set_local_NPMResolutionSource",
   539  			m: &pb.JavascriptPackageJSONMetadata{
   540  				Author: "some-author <some-author@google.com>",
   541  				Source: pb.PackageSource_LOCAL,
   542  			},
   543  			want: &metadata.JavascriptPackageJSONMetadata{
   544  				Author: &metadata.Person{
   545  					Name:  "some-author",
   546  					Email: "some-author@google.com",
   547  				},
   548  				Source: metadata.Local,
   549  			},
   550  		},
   551  	}
   552  
   553  	for _, tc := range testCases {
   554  		t.Run(tc.desc, func(t *testing.T) {
   555  			got := metadata.ToStruct(tc.m)
   556  			if diff := cmp.Diff(tc.want, got); diff != "" {
   557  				t.Errorf("ToStruct(%+v): (-want +got):\n%s", tc.m, diff)
   558  			}
   559  
   560  			if tc.m == nil {
   561  				return
   562  			}
   563  
   564  			// Test the reverse conversion for completeness.
   565  
   566  			gotP := &pb.Package{}
   567  			wantP := &pb.Package{
   568  				Metadata: &pb.Package_JavascriptMetadata{
   569  					JavascriptMetadata: tc.m,
   570  				},
   571  			}
   572  			got.SetProto(gotP)
   573  			opts := []cmp.Option{
   574  				protocmp.Transform(),
   575  			}
   576  			if diff := cmp.Diff(wantP, gotP, opts...); diff != "" {
   577  				t.Errorf("Metatadata{%+v}.SetProto(%+v): (-want +got):\n%s", got, wantP, diff)
   578  			}
   579  		})
   580  	}
   581  }