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 }