github.com/grpc-ecosystem/grpc-gateway/v2@v2.19.1/protoc-gen-openapiv2/internal/genopenapi/generator_test.go (about) 1 package genopenapi_test 2 3 import ( 4 "os" 5 "reflect" 6 "sort" 7 "strings" 8 "testing" 9 10 "github.com/google/go-cmp/cmp" 11 "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor" 12 "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/internal/genopenapi" 13 "gopkg.in/yaml.v3" 14 15 "google.golang.org/protobuf/encoding/prototext" 16 "google.golang.org/protobuf/proto" 17 "google.golang.org/protobuf/types/descriptorpb" 18 "google.golang.org/protobuf/types/pluginpb" 19 ) 20 21 func TestGenerate_YAML(t *testing.T) { 22 t.Parallel() 23 24 req := &pluginpb.CodeGeneratorRequest{ 25 ProtoFile: []*descriptorpb.FileDescriptorProto{{ 26 Name: proto.String("file.proto"), 27 Package: proto.String("example"), 28 Options: &descriptorpb.FileOptions{ 29 GoPackage: proto.String("goexample/v1;goexample"), 30 }, 31 }}, 32 FileToGenerate: []string{ 33 "file.proto", 34 }, 35 } 36 37 resp := requireGenerate(t, req, genopenapi.FormatYAML, false, false) 38 if len(resp) != 1 { 39 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 40 } 41 42 var p map[string]interface{} 43 err := yaml.Unmarshal([]byte(resp[0].GetContent()), &p) 44 if err != nil { 45 t.Fatalf("failed to unmarshall yaml: %s", err) 46 } 47 } 48 49 func TestGenerateExtension(t *testing.T) { 50 t.Parallel() 51 52 const in = ` 53 file_to_generate: "exampleproto/v1/example.proto" 54 parameter: "output_format=yaml,allow_delete_body=true" 55 proto_file: { 56 name: "exampleproto/v1/example.proto" 57 package: "example.v1" 58 message_type: { 59 name: "Foo" 60 field: { 61 name: "bar" 62 number: 1 63 label: LABEL_OPTIONAL 64 type: TYPE_STRING 65 json_name: "bar" 66 options: { 67 [grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field]: { 68 description: "This is bar" 69 extensions: { 70 key: "x-go-default" 71 value: { 72 string_value: "0.5s" 73 } 74 } 75 } 76 } 77 } 78 } 79 service: { 80 name: "TestService" 81 method: { 82 name: "Test" 83 input_type: ".example.v1.Foo" 84 output_type: ".example.v1.Foo" 85 options: {} 86 } 87 } 88 options: { 89 go_package: "exampleproto/v1;exampleproto" 90 } 91 }` 92 93 var req pluginpb.CodeGeneratorRequest 94 if err := prototext.Unmarshal([]byte(in), &req); err != nil { 95 t.Fatalf("failed to marshall yaml: %s", err) 96 } 97 98 formats := [...]genopenapi.Format{ 99 genopenapi.FormatJSON, 100 genopenapi.FormatYAML, 101 } 102 103 for _, format := range formats { 104 format := format 105 106 t.Run(string(format), func(t *testing.T) { 107 t.Parallel() 108 109 resp := requireGenerate(t, &req, format, false, false) 110 if len(resp) != 1 { 111 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 112 } 113 114 content := resp[0].GetContent() 115 116 t.Log(content) 117 118 if !strings.Contains(content, "x-go-default") { 119 t.Fatal("x-go-default not found in content message") 120 } 121 }) 122 } 123 } 124 125 func TestGenerateYAML(t *testing.T) { 126 t.Parallel() 127 128 tests := []struct { 129 name string 130 inputProtoText string 131 wantYAML string 132 }{ 133 { 134 // It tests https://github.com/grpc-ecosystem/grpc-gateway/issues/3557. 135 name: "path item object", 136 inputProtoText: "testdata/generator/path_item_object.prototext", 137 wantYAML: "testdata/generator/path_item_object.swagger.yaml", 138 }, 139 } 140 141 for _, tt := range tests { 142 tt := tt 143 t.Run(tt.name, func(t *testing.T) { 144 t.Parallel() 145 146 b, err := os.ReadFile(tt.inputProtoText) 147 if err != nil { 148 t.Fatal(err) 149 } 150 var req pluginpb.CodeGeneratorRequest 151 if err := prototext.Unmarshal(b, &req); err != nil { 152 t.Fatal(err) 153 } 154 155 resp := requireGenerate(t, &req, genopenapi.FormatYAML, false, true) 156 if len(resp) != 1 { 157 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 158 } 159 got := resp[0].GetContent() 160 161 want, err := os.ReadFile(tt.wantYAML) 162 if err != nil { 163 t.Fatal(err) 164 } 165 diff := cmp.Diff(string(want), got) 166 if diff != "" { 167 t.Fatalf("content not match\n%s", diff) 168 } 169 }) 170 } 171 } 172 173 func requireGenerate( 174 tb testing.TB, 175 req *pluginpb.CodeGeneratorRequest, 176 format genopenapi.Format, 177 preserveRPCOrder bool, 178 allowMerge bool, 179 ) []*descriptor.ResponseFile { 180 tb.Helper() 181 182 reg := descriptor.NewRegistry() 183 reg.SetPreserveRPCOrder(preserveRPCOrder) 184 reg.SetAllowMerge(allowMerge) 185 186 if err := reg.Load(req); err != nil { 187 tb.Fatalf("failed to load request: %s", err) 188 } 189 190 var targets []*descriptor.File 191 for _, target := range req.FileToGenerate { 192 f, err := reg.LookupFile(target) 193 if err != nil { 194 tb.Fatalf("failed to lookup file: %s", err) 195 } 196 197 targets = append(targets, f) 198 } 199 200 g := genopenapi.New(reg, format) 201 202 resp, err := g.Generate(targets) 203 switch { 204 case err != nil: 205 tb.Fatalf("failed to generate targets: %s", err) 206 case len(resp) != len(targets) && !allowMerge: 207 tb.Fatalf("invalid count, expected: %d, actual: %d", len(targets), len(resp)) 208 } 209 210 return resp 211 } 212 213 func TestGeneratedYAMLIndent(t *testing.T) { 214 // It tests https://github.com/grpc-ecosystem/grpc-gateway/issues/2745. 215 const in = ` 216 file_to_generate: "exampleproto/v1/exampleproto.proto" 217 parameter: "output_format=yaml,allow_delete_body=true" 218 proto_file: { 219 name: "exampleproto/v1/exampleproto.proto" 220 package: "repro" 221 message_type: { 222 name: "RollupRequest" 223 field: { 224 name: "type" 225 number: 1 226 label: LABEL_OPTIONAL 227 type: TYPE_ENUM 228 type_name: ".repro.RollupType" 229 json_name: "type" 230 } 231 } 232 message_type: { 233 name: "RollupResponse" 234 } 235 enum_type: { 236 name: "RollupType" 237 value: { 238 name: "UNKNOWN_ROLLUP" 239 number: 0 240 } 241 value: { 242 name: "APPLE" 243 number: 1 244 } 245 value: { 246 name: "BANANA" 247 number: 2 248 } 249 value: { 250 name: "CARROT" 251 number: 3 252 } 253 } 254 service: { 255 name: "Repro" 256 method: { 257 name: "GetRollup" 258 input_type: ".repro.RollupRequest" 259 output_type: ".repro.RollupResponse" 260 options: { 261 [google.api.http]: { 262 get: "/rollup" 263 } 264 } 265 } 266 } 267 options: { 268 go_package: "repro/foobar" 269 } 270 source_code_info: { 271 location: { 272 path: 5 273 path: 0 274 path: 2 275 path: 1 276 span: 24 277 span: 4 278 span: 14 279 leading_comments: " Apples are good\n" 280 } 281 location: { 282 path: 5 283 path: 0 284 path: 2 285 path: 3 286 span: 28 287 span: 4 288 span: 15 289 leading_comments: " Carrots are mediocre\n" 290 } 291 } 292 syntax: "proto3" 293 } 294 ` 295 296 var req pluginpb.CodeGeneratorRequest 297 if err := prototext.Unmarshal([]byte(in), &req); err != nil { 298 t.Fatalf("failed to marshall yaml: %s", err) 299 } 300 301 resp := requireGenerate(t, &req, genopenapi.FormatYAML, false, false) 302 if len(resp) != 1 { 303 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 304 } 305 306 content := resp[0].GetContent() 307 308 err := yaml.Unmarshal([]byte(content), map[string]interface{}{}) 309 if err != nil { 310 t.Log(content) 311 t.Fatalf("got invalid yaml: %s", err) 312 } 313 } 314 315 func TestGenerateRPCOrderPreserved(t *testing.T) { 316 t.Parallel() 317 318 const in = ` 319 file_to_generate: "exampleproto/v1/example.proto" 320 parameter: "output_format=yaml,allow_delete_body=true" 321 proto_file: { 322 name: "exampleproto/v1/example.proto" 323 package: "example.v1" 324 message_type: { 325 name: "Foo" 326 field: { 327 name: "bar" 328 number: 1 329 label: LABEL_OPTIONAL 330 type: TYPE_STRING 331 json_name: "bar" 332 } 333 } 334 service: { 335 name: "TestService" 336 method: { 337 name: "Test1" 338 input_type: ".example.v1.Foo" 339 output_type: ".example.v1.Foo" 340 options: { 341 [google.api.http]: { 342 get: "/b/first" 343 } 344 } 345 } 346 method: { 347 name: "Test2" 348 input_type: ".example.v1.Foo" 349 output_type: ".example.v1.Foo" 350 options: { 351 [google.api.http]: { 352 get: "/a/second" 353 } 354 } 355 } 356 method: { 357 name: "Test3" 358 input_type: ".example.v1.Foo" 359 output_type: ".example.v1.Foo" 360 options: { 361 [google.api.http]: { 362 get: "/c/third" 363 } 364 } 365 } 366 } 367 options: { 368 go_package: "exampleproto/v1;exampleproto" 369 } 370 }` 371 372 var req pluginpb.CodeGeneratorRequest 373 if err := prototext.Unmarshal([]byte(in), &req); err != nil { 374 t.Fatalf("failed to marshall yaml: %s", err) 375 } 376 377 formats := [...]genopenapi.Format{ 378 genopenapi.FormatJSON, 379 genopenapi.FormatYAML, 380 } 381 382 for _, format := range formats { 383 format := format 384 t.Run(string(format), func(t *testing.T) { 385 t.Parallel() 386 387 resp := requireGenerate(t, &req, format, true, false) 388 if len(resp) != 1 { 389 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 390 } 391 392 content := resp[0].GetContent() 393 394 t.Log(content) 395 396 contentsSlice := strings.Fields(content) 397 expectedPaths := []string{"/b/first", "/a/second", "/c/third"} 398 399 foundPaths := []string{} 400 for _, contentValue := range contentsSlice { 401 findExpectedPaths(&foundPaths, expectedPaths, contentValue) 402 } 403 404 if allPresent := reflect.DeepEqual(foundPaths, expectedPaths); !allPresent { 405 t.Fatalf("Found paths differed from expected paths. Got: %#v, want %#v", foundPaths, expectedPaths) 406 } 407 }) 408 } 409 410 } 411 412 func TestGenerateRPCOrderNotPreserved(t *testing.T) { 413 t.Parallel() 414 415 const in = ` 416 file_to_generate: "exampleproto/v1/example.proto" 417 parameter: "output_format=yaml,allow_delete_body=true" 418 proto_file: { 419 name: "exampleproto/v1/example.proto" 420 package: "example.v1" 421 message_type: { 422 name: "Foo" 423 field: { 424 name: "bar" 425 number: 1 426 label: LABEL_OPTIONAL 427 type: TYPE_STRING 428 json_name: "bar" 429 } 430 } 431 service: { 432 name: "TestService" 433 method: { 434 name: "Test1" 435 input_type: ".example.v1.Foo" 436 output_type: ".example.v1.Foo" 437 options: { 438 [google.api.http]: { 439 get: "/b/first" 440 } 441 } 442 } 443 method: { 444 name: "Test2" 445 input_type: ".example.v1.Foo" 446 output_type: ".example.v1.Foo" 447 options: { 448 [google.api.http]: { 449 get: "/a/second" 450 } 451 } 452 } 453 method: { 454 name: "Test3" 455 input_type: ".example.v1.Foo" 456 output_type: ".example.v1.Foo" 457 options: { 458 [google.api.http]: { 459 get: "/c/third" 460 } 461 } 462 } 463 } 464 options: { 465 go_package: "exampleproto/v1;exampleproto" 466 } 467 }` 468 469 var req pluginpb.CodeGeneratorRequest 470 if err := prototext.Unmarshal([]byte(in), &req); err != nil { 471 t.Fatalf("failed to marshall yaml: %s", err) 472 } 473 474 formats := [...]genopenapi.Format{ 475 genopenapi.FormatJSON, 476 genopenapi.FormatYAML, 477 } 478 479 for _, format := range formats { 480 format := format 481 t.Run(string(format), func(t *testing.T) { 482 t.Parallel() 483 484 resp := requireGenerate(t, &req, format, false, false) 485 if len(resp) != 1 { 486 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 487 } 488 489 content := resp[0].GetContent() 490 491 t.Log(content) 492 contentsSlice := strings.Fields(content) 493 expectedPaths := []string{"/a/second", "/b/first", "/c/third"} 494 495 foundPaths := []string{} 496 for _, contentValue := range contentsSlice { 497 findExpectedPaths(&foundPaths, expectedPaths, contentValue) 498 } 499 500 if allPresent := reflect.DeepEqual(foundPaths, expectedPaths); !allPresent { 501 t.Fatalf("Found paths differed from expected paths. Got: %#v, want %#v", foundPaths, expectedPaths) 502 } 503 }) 504 } 505 506 } 507 508 func TestGenerateRPCOrderPreservedMultipleServices(t *testing.T) { 509 t.Parallel() 510 511 const in = ` 512 file_to_generate: "exampleproto/v1/example.proto" 513 parameter: "output_format=yaml,allow_delete_body=true" 514 proto_file: { 515 name: "exampleproto/v1/example.proto" 516 package: "example.v1" 517 message_type: { 518 name: "Foo" 519 field: { 520 name: "bar" 521 number: 1 522 label: LABEL_OPTIONAL 523 type: TYPE_STRING 524 json_name: "bar" 525 } 526 } 527 service: { 528 name: "TestServiceOne" 529 method: { 530 name: "Test1" 531 input_type: ".example.v1.Foo" 532 output_type: ".example.v1.Foo" 533 options: { 534 [google.api.http]: { 535 get: "/d/first" 536 } 537 } 538 } 539 method: { 540 name: "Test2" 541 input_type: ".example.v1.Foo" 542 output_type: ".example.v1.Foo" 543 options: { 544 [google.api.http]: { 545 get: "/e/second" 546 } 547 } 548 } 549 method: { 550 name: "Test3" 551 input_type: ".example.v1.Foo" 552 output_type: ".example.v1.Foo" 553 options: { 554 [google.api.http]: { 555 get: "/c/third" 556 } 557 } 558 } 559 } 560 service: { 561 name: "TestServiceTwo" 562 method: { 563 name: "Test1" 564 input_type: ".example.v1.Foo" 565 output_type: ".example.v1.Foo" 566 options: { 567 [google.api.http]: { 568 get: "/b/first" 569 } 570 } 571 } 572 method: { 573 name: "Test2" 574 input_type: ".example.v1.Foo" 575 output_type: ".example.v1.Foo" 576 options: { 577 [google.api.http]: { 578 get: "/a/second" 579 } 580 } 581 } 582 method: { 583 name: "Test3" 584 input_type: ".example.v1.Foo" 585 output_type: ".example.v1.Foo" 586 options: { 587 [google.api.http]: { 588 get: "/g/third" 589 } 590 } 591 } 592 } 593 options: { 594 go_package: "exampleproto/v1;exampleproto" 595 } 596 }` 597 598 var req pluginpb.CodeGeneratorRequest 599 if err := prototext.Unmarshal([]byte(in), &req); err != nil { 600 t.Fatalf("failed to marshall yaml: %s", err) 601 } 602 603 formats := [...]genopenapi.Format{ 604 genopenapi.FormatJSON, 605 genopenapi.FormatYAML, 606 } 607 608 for _, format := range formats { 609 format := format 610 t.Run(string(format), func(t *testing.T) { 611 t.Parallel() 612 613 resp := requireGenerate(t, &req, format, true, false) 614 if len(resp) != 1 { 615 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 616 } 617 618 content := resp[0].GetContent() 619 620 t.Log(content) 621 622 contentsSlice := strings.Fields(content) 623 expectedPaths := []string{"/d/first", "/e/second", "/c/third", "/b/first", "/a/second", "/g/third"} 624 625 foundPaths := []string{} 626 for _, contentValue := range contentsSlice { 627 findExpectedPaths(&foundPaths, expectedPaths, contentValue) 628 } 629 630 if allPresent := reflect.DeepEqual(foundPaths, expectedPaths); !allPresent { 631 t.Fatalf("Found paths differed from expected paths. Got: %#v, want %#v", foundPaths, expectedPaths) 632 } 633 }) 634 } 635 } 636 637 func TestGenerateRPCOrderNotPreservedMultipleServices(t *testing.T) { 638 t.Parallel() 639 640 const in = ` 641 file_to_generate: "exampleproto/v1/example.proto" 642 parameter: "output_format=yaml,allow_delete_body=true" 643 proto_file: { 644 name: "exampleproto/v1/example.proto" 645 package: "example.v1" 646 message_type: { 647 name: "Foo" 648 field: { 649 name: "bar" 650 number: 1 651 label: LABEL_OPTIONAL 652 type: TYPE_STRING 653 json_name: "bar" 654 } 655 } 656 service: { 657 name: "TestServiceOne" 658 method: { 659 name: "Test1" 660 input_type: ".example.v1.Foo" 661 output_type: ".example.v1.Foo" 662 options: { 663 [google.api.http]: { 664 get: "/d/first" 665 } 666 } 667 } 668 method: { 669 name: "Test2" 670 input_type: ".example.v1.Foo" 671 output_type: ".example.v1.Foo" 672 options: { 673 [google.api.http]: { 674 get: "/e/second" 675 } 676 } 677 } 678 method: { 679 name: "Test3" 680 input_type: ".example.v1.Foo" 681 output_type: ".example.v1.Foo" 682 options: { 683 [google.api.http]: { 684 get: "/c/third" 685 } 686 } 687 } 688 } 689 service: { 690 name: "TestServiceTwo" 691 method: { 692 name: "Test1" 693 input_type: ".example.v1.Foo" 694 output_type: ".example.v1.Foo" 695 options: { 696 [google.api.http]: { 697 get: "/b/first" 698 } 699 } 700 } 701 method: { 702 name: "Test2" 703 input_type: ".example.v1.Foo" 704 output_type: ".example.v1.Foo" 705 options: { 706 [google.api.http]: { 707 get: "/a/second" 708 } 709 } 710 } 711 method: { 712 name: "Test3" 713 input_type: ".example.v1.Foo" 714 output_type: ".example.v1.Foo" 715 options: { 716 [google.api.http]: { 717 get: "/g/third" 718 } 719 } 720 } 721 } 722 options: { 723 go_package: "exampleproto/v1;exampleproto" 724 } 725 }` 726 727 var req pluginpb.CodeGeneratorRequest 728 if err := prototext.Unmarshal([]byte(in), &req); err != nil { 729 t.Fatalf("failed to marshall yaml: %s", err) 730 } 731 732 formats := [...]genopenapi.Format{ 733 genopenapi.FormatJSON, 734 genopenapi.FormatYAML, 735 } 736 737 for _, format := range formats { 738 format := format 739 t.Run(string(format), func(t *testing.T) { 740 t.Parallel() 741 742 resp := requireGenerate(t, &req, format, false, false) 743 if len(resp) != 1 { 744 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 745 } 746 747 content := resp[0].GetContent() 748 749 t.Log(content) 750 751 contentsSlice := strings.Fields(content) 752 expectedPaths := []string{"/d/first", "/e/second", "/c/third", "/b/first", "/a/second", "/g/third"} 753 sort.Strings(expectedPaths) 754 755 foundPaths := []string{} 756 for _, contentValue := range contentsSlice { 757 findExpectedPaths(&foundPaths, expectedPaths, contentValue) 758 } 759 760 if allPresent := reflect.DeepEqual(foundPaths, expectedPaths); !allPresent { 761 t.Fatalf("Found paths differed from expected paths. Got: %#v, want %#v", foundPaths, expectedPaths) 762 } 763 }) 764 } 765 } 766 767 func TestGenerateRPCOrderPreservedMergeFiles(t *testing.T) { 768 t.Parallel() 769 770 const in1 = ` 771 file_to_generate: "exampleproto/v1/example.proto" 772 parameter: "output_format=yaml,allow_delete_body=true" 773 proto_file: { 774 name: "exampleproto/v1/example.proto" 775 package: "example.v1" 776 message_type: { 777 name: "Foo" 778 field: { 779 name: "bar" 780 number: 1 781 label: LABEL_OPTIONAL 782 type: TYPE_STRING 783 json_name: "bar" 784 } 785 } 786 service: { 787 name: "TestService" 788 method: { 789 name: "Test1" 790 input_type: ".example.v1.Foo" 791 output_type: ".example.v1.Foo" 792 options: { 793 [google.api.http]: { 794 get: "/c/cpath" 795 } 796 } 797 } 798 method: { 799 name: "Test2" 800 input_type: ".example.v1.Foo" 801 output_type: ".example.v1.Foo" 802 options: { 803 [google.api.http]: { 804 get: "/b/bpath" 805 } 806 } 807 } 808 method: { 809 name: "Test3" 810 input_type: ".example.v1.Foo" 811 output_type: ".example.v1.Foo" 812 options: { 813 [google.api.http]: { 814 get: "/a/apath" 815 } 816 } 817 } 818 } 819 options: { 820 go_package: "exampleproto/v1;exampleproto" 821 } 822 }` 823 824 const in2 = ` 825 file_to_generate: "exampleproto/v2/example.proto" 826 parameter: "output_format=yaml,allow_delete_body=true" 827 proto_file: { 828 name: "exampleproto/v2/example.proto" 829 package: "example.v2" 830 message_type: { 831 name: "Foo" 832 field: { 833 name: "bar" 834 number: 1 835 label: LABEL_OPTIONAL 836 type: TYPE_STRING 837 json_name: "bar" 838 } 839 } 840 service: { 841 name: "TestService" 842 method: { 843 name: "Test1" 844 input_type: ".example.v2.Foo" 845 output_type: ".example.v2.Foo" 846 options: { 847 [google.api.http]: { 848 get: "/f/fpath" 849 } 850 } 851 } 852 method: { 853 name: "Test2" 854 input_type: ".example.v2.Foo" 855 output_type: ".example.v2.Foo" 856 options: { 857 [google.api.http]: { 858 get: "/e/epath" 859 } 860 } 861 } 862 method: { 863 name: "Test3" 864 input_type: ".example.v2.Foo" 865 output_type: ".example.v2.Foo" 866 options: { 867 [google.api.http]: { 868 get: "/d/dpath" 869 } 870 } 871 } 872 } 873 options: { 874 go_package: "exampleproto/v2;exampleproto" 875 } 876 }` 877 878 var req1, req2 pluginpb.CodeGeneratorRequest 879 880 if err := prototext.Unmarshal([]byte(in1), &req1); err != nil { 881 t.Fatalf("failed to marshall yaml: %s", err) 882 } 883 if err := prototext.Unmarshal([]byte(in2), &req2); err != nil { 884 t.Fatalf("failed to marshall yaml: %s", err) 885 } 886 887 req1.ProtoFile = append(req1.ProtoFile, req2.ProtoFile...) 888 req1.FileToGenerate = append(req1.FileToGenerate, req2.FileToGenerate...) 889 formats := [...]genopenapi.Format{ 890 genopenapi.FormatJSON, 891 genopenapi.FormatYAML, 892 } 893 894 for _, format := range formats { 895 format := format 896 t.Run(string(format), func(t *testing.T) { 897 t.Parallel() 898 899 resp := requireGenerate(t, &req1, format, true, true) 900 if len(resp) != 1 { 901 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 902 } 903 904 content := resp[0].GetContent() 905 906 t.Log(content) 907 908 contentsSlice := strings.Fields(content) 909 expectedPaths := []string{"/c/cpath", "/b/bpath", "/a/apath", "/f/fpath", "/e/epath", "/d/dpath"} 910 911 foundPaths := []string{} 912 for _, contentValue := range contentsSlice { 913 findExpectedPaths(&foundPaths, expectedPaths, contentValue) 914 } 915 916 if allPresent := reflect.DeepEqual(foundPaths, expectedPaths); !allPresent { 917 t.Fatalf("Found paths differed from expected paths. Got: %#v, want %#v", foundPaths, expectedPaths) 918 } 919 }) 920 } 921 } 922 923 func TestGenerateRPCOrderNotPreservedMergeFiles(t *testing.T) { 924 t.Parallel() 925 926 const in1 = ` 927 file_to_generate: "exampleproto/v1/example.proto" 928 parameter: "output_format=yaml,allow_delete_body=true" 929 proto_file: { 930 name: "exampleproto/v1/example.proto" 931 package: "example.v1" 932 message_type: { 933 name: "Foo" 934 field: { 935 name: "bar" 936 number: 1 937 label: LABEL_OPTIONAL 938 type: TYPE_STRING 939 json_name: "bar" 940 } 941 } 942 service: { 943 name: "TestService" 944 method: { 945 name: "Test1" 946 input_type: ".example.v1.Foo" 947 output_type: ".example.v1.Foo" 948 options: { 949 [google.api.http]: { 950 get: "/c/cpath" 951 } 952 } 953 } 954 method: { 955 name: "Test2" 956 input_type: ".example.v1.Foo" 957 output_type: ".example.v1.Foo" 958 options: { 959 [google.api.http]: { 960 get: "/b/bpath" 961 } 962 } 963 } 964 method: { 965 name: "Test3" 966 input_type: ".example.v1.Foo" 967 output_type: ".example.v1.Foo" 968 options: { 969 [google.api.http]: { 970 get: "/a/apath" 971 } 972 } 973 } 974 } 975 options: { 976 go_package: "exampleproto/v1;exampleproto" 977 } 978 }` 979 980 const in2 = ` 981 file_to_generate: "exampleproto/v2/example.proto" 982 parameter: "output_format=yaml,allow_delete_body=true" 983 proto_file: { 984 name: "exampleproto/v2/example.proto" 985 package: "example.v2" 986 message_type: { 987 name: "Foo" 988 field: { 989 name: "bar" 990 number: 1 991 label: LABEL_OPTIONAL 992 type: TYPE_STRING 993 json_name: "bar" 994 } 995 } 996 service: { 997 name: "TestService" 998 method: { 999 name: "Test1" 1000 input_type: ".example.v2.Foo" 1001 output_type: ".example.v2.Foo" 1002 options: { 1003 [google.api.http]: { 1004 get: "/f/fpath" 1005 } 1006 } 1007 } 1008 method: { 1009 name: "Test2" 1010 input_type: ".example.v2.Foo" 1011 output_type: ".example.v2.Foo" 1012 options: { 1013 [google.api.http]: { 1014 get: "/e/epath" 1015 } 1016 } 1017 } 1018 method: { 1019 name: "Test3" 1020 input_type: ".example.v2.Foo" 1021 output_type: ".example.v2.Foo" 1022 options: { 1023 [google.api.http]: { 1024 get: "/d/dpath" 1025 } 1026 } 1027 } 1028 } 1029 options: { 1030 go_package: "exampleproto/v2;exampleproto" 1031 } 1032 }` 1033 1034 var req1, req2 pluginpb.CodeGeneratorRequest 1035 1036 if err := prototext.Unmarshal([]byte(in1), &req1); err != nil { 1037 t.Fatalf("failed to marshall yaml: %s", err) 1038 } 1039 if err := prototext.Unmarshal([]byte(in2), &req2); err != nil { 1040 t.Fatalf("failed to marshall yaml: %s", err) 1041 } 1042 1043 req1.ProtoFile = append(req1.ProtoFile, req2.ProtoFile...) 1044 req1.FileToGenerate = append(req1.FileToGenerate, req2.FileToGenerate...) 1045 formats := [...]genopenapi.Format{ 1046 genopenapi.FormatJSON, 1047 genopenapi.FormatYAML, 1048 } 1049 1050 for _, format := range formats { 1051 format := format 1052 t.Run(string(format), func(t *testing.T) { 1053 t.Parallel() 1054 1055 resp := requireGenerate(t, &req1, format, false, true) 1056 if len(resp) != 1 { 1057 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 1058 } 1059 1060 content := resp[0].GetContent() 1061 1062 t.Log(content) 1063 1064 contentsSlice := strings.Fields(content) 1065 expectedPaths := []string{"/c/cpath", "/b/bpath", "/a/apath", "/f/fpath", "/e/epath", "/d/dpath"} 1066 sort.Strings(expectedPaths) 1067 1068 foundPaths := []string{} 1069 for _, contentValue := range contentsSlice { 1070 findExpectedPaths(&foundPaths, expectedPaths, contentValue) 1071 } 1072 1073 if allPresent := reflect.DeepEqual(foundPaths, expectedPaths); !allPresent { 1074 t.Fatalf("Found paths differed from expected paths. Got: %#v, want %#v", foundPaths, expectedPaths) 1075 } 1076 }) 1077 } 1078 } 1079 1080 func TestGenerateRPCOrderPreservedAdditionalBindings(t *testing.T) { 1081 t.Parallel() 1082 1083 const in = ` 1084 file_to_generate: "exampleproto/v1/example.proto" 1085 parameter: "output_format=yaml,allow_delete_body=true" 1086 proto_file: { 1087 name: "exampleproto/v1/example.proto" 1088 package: "example.v1" 1089 message_type: { 1090 name: "Foo" 1091 field: { 1092 name: "bar" 1093 number: 1 1094 label: LABEL_OPTIONAL 1095 type: TYPE_STRING 1096 json_name: "bar" 1097 } 1098 } 1099 service: { 1100 name: "TestService" 1101 method: { 1102 name: "Test1" 1103 input_type: ".example.v1.Foo" 1104 output_type: ".example.v1.Foo" 1105 options: { 1106 [google.api.http]: { 1107 get: "/b/first" 1108 additional_bindings { 1109 get: "/a/additional" 1110 } 1111 } 1112 } 1113 } 1114 method: { 1115 name: "Test2" 1116 input_type: ".example.v1.Foo" 1117 output_type: ".example.v1.Foo" 1118 options: { 1119 [google.api.http]: { 1120 get: "/a/second" 1121 additional_bindings { 1122 get: "/z/zAdditional" 1123 } 1124 } 1125 } 1126 } 1127 method: { 1128 name: "Test3" 1129 input_type: ".example.v1.Foo" 1130 output_type: ".example.v1.Foo" 1131 options: { 1132 [google.api.http]: { 1133 get: "/c/third" 1134 additional_bindings { 1135 get: "/b/bAdditional" 1136 } 1137 } 1138 } 1139 } 1140 } 1141 options: { 1142 go_package: "exampleproto/v1;exampleproto" 1143 } 1144 }` 1145 1146 var req pluginpb.CodeGeneratorRequest 1147 if err := prototext.Unmarshal([]byte(in), &req); err != nil { 1148 t.Fatalf("failed to marshall yaml: %s", err) 1149 } 1150 1151 formats := [...]genopenapi.Format{ 1152 genopenapi.FormatJSON, 1153 genopenapi.FormatYAML, 1154 } 1155 1156 for _, format := range formats { 1157 format := format 1158 t.Run(string(format), func(t *testing.T) { 1159 t.Parallel() 1160 1161 resp := requireGenerate(t, &req, format, true, false) 1162 if len(resp) != 1 { 1163 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 1164 } 1165 1166 content := resp[0].GetContent() 1167 1168 t.Log(content) 1169 1170 contentsSlice := strings.Fields(content) 1171 expectedPaths := []string{"/b/first", "/a/additional", "/a/second", "/z/zAdditional", "/c/third", "/b/bAdditional"} 1172 1173 foundPaths := []string{} 1174 for _, contentValue := range contentsSlice { 1175 findExpectedPaths(&foundPaths, expectedPaths, contentValue) 1176 } 1177 1178 if allPresent := reflect.DeepEqual(foundPaths, expectedPaths); !allPresent { 1179 t.Fatalf("Found paths differed from expected paths. Got: %#v, want %#v", foundPaths, expectedPaths) 1180 } 1181 }) 1182 } 1183 } 1184 1185 func TestGenerateRPCOneOfFieldBodyAdditionalBindings(t *testing.T) { 1186 t.Parallel() 1187 1188 const in = ` 1189 file_to_generate: "exampleproto/v1/example.proto" 1190 parameter: "output_format=yaml,allow_delete_body=true" 1191 proto_file: { 1192 name: "exampleproto/v1/example.proto" 1193 package: "example.v1" 1194 message_type: { 1195 name: "Foo" 1196 oneof_decl: { 1197 name: "foo" 1198 } 1199 field: { 1200 name: "bar" 1201 number: 1 1202 label: LABEL_OPTIONAL 1203 type: TYPE_STRING 1204 json_name: "bar" 1205 oneof_index: 0 1206 } 1207 field: { 1208 name: "baz" 1209 number: 2 1210 label: LABEL_OPTIONAL 1211 type: TYPE_STRING 1212 json_name: "bar" 1213 oneof_index: 0 1214 } 1215 } 1216 service: { 1217 name: "TestService" 1218 method: { 1219 name: "Test1" 1220 input_type: ".example.v1.Foo" 1221 output_type: ".example.v1.Foo" 1222 options: { 1223 [google.api.http]: { 1224 post: "/b/foo" 1225 body: "*" 1226 additional_bindings { 1227 post: "/b/foo/bar" 1228 body: "bar" 1229 } 1230 additional_bindings { 1231 post: "/b/foo/baz" 1232 body: "baz" 1233 } 1234 } 1235 } 1236 } 1237 } 1238 options: { 1239 go_package: "exampleproto/v1;exampleproto" 1240 } 1241 }` 1242 1243 var req pluginpb.CodeGeneratorRequest 1244 if err := prototext.Unmarshal([]byte(in), &req); err != nil { 1245 t.Fatalf("failed to marshall yaml: %s", err) 1246 } 1247 1248 formats := [...]genopenapi.Format{ 1249 genopenapi.FormatJSON, 1250 genopenapi.FormatYAML, 1251 } 1252 1253 for _, format := range formats { 1254 format := format 1255 t.Run(string(format), func(t *testing.T) { 1256 t.Parallel() 1257 1258 resp := requireGenerate(t, &req, format, true, false) 1259 if len(resp) != 1 { 1260 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 1261 } 1262 1263 content := resp[0].GetContent() 1264 1265 t.Log(content) 1266 1267 contentsSlice := strings.Fields(content) 1268 expectedPaths := []string{"/b/foo", "/b/foo/bar", "/b/foo/baz"} 1269 1270 foundPaths := []string{} 1271 for _, contentValue := range contentsSlice { 1272 findExpectedPaths(&foundPaths, expectedPaths, contentValue) 1273 } 1274 1275 if allPresent := reflect.DeepEqual(foundPaths, expectedPaths); !allPresent { 1276 t.Fatalf("Found paths differed from expected paths. Got: %#v, want %#v", foundPaths, expectedPaths) 1277 } 1278 1279 // The input message only contains oneof fields, so no other fields should be mapped to the query. 1280 if strings.Contains(content, "query") { 1281 t.Fatalf("Found query in content, expected not to find any") 1282 } 1283 }) 1284 } 1285 } 1286 1287 func TestGenerateRPCOrderNotPreservedAdditionalBindings(t *testing.T) { 1288 t.Parallel() 1289 1290 const in = ` 1291 file_to_generate: "exampleproto/v1/example.proto" 1292 parameter: "output_format=yaml,allow_delete_body=true" 1293 proto_file: { 1294 name: "exampleproto/v1/example.proto" 1295 package: "example.v1" 1296 message_type: { 1297 name: "Foo" 1298 field: { 1299 name: "bar" 1300 number: 1 1301 label: LABEL_OPTIONAL 1302 type: TYPE_STRING 1303 json_name: "bar" 1304 } 1305 } 1306 service: { 1307 name: "TestService" 1308 method: { 1309 name: "Test1" 1310 input_type: ".example.v1.Foo" 1311 output_type: ".example.v1.Foo" 1312 options: { 1313 [google.api.http]: { 1314 get: "/b/first" 1315 additional_bindings { 1316 get: "/a/additional" 1317 } 1318 } 1319 } 1320 } 1321 method: { 1322 name: "Test2" 1323 input_type: ".example.v1.Foo" 1324 output_type: ".example.v1.Foo" 1325 options: { 1326 [google.api.http]: { 1327 get: "/a/second" 1328 additional_bindings { 1329 get: "/z/zAdditional" 1330 } 1331 } 1332 } 1333 } 1334 method: { 1335 name: "Test3" 1336 input_type: ".example.v1.Foo" 1337 output_type: ".example.v1.Foo" 1338 options: { 1339 [google.api.http]: { 1340 get: "/c/third" 1341 additional_bindings { 1342 get: "/b/bAdditional" 1343 } 1344 } 1345 } 1346 } 1347 } 1348 options: { 1349 go_package: "exampleproto/v1;exampleproto" 1350 } 1351 }` 1352 1353 var req pluginpb.CodeGeneratorRequest 1354 if err := prototext.Unmarshal([]byte(in), &req); err != nil { 1355 t.Fatalf("failed to marshall yaml: %s", err) 1356 } 1357 1358 formats := [...]genopenapi.Format{ 1359 genopenapi.FormatJSON, 1360 genopenapi.FormatYAML, 1361 } 1362 1363 for _, format := range formats { 1364 format := format 1365 t.Run(string(format), func(t *testing.T) { 1366 t.Parallel() 1367 1368 resp := requireGenerate(t, &req, format, false, false) 1369 if len(resp) != 1 { 1370 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 1371 } 1372 1373 content := resp[0].GetContent() 1374 1375 t.Log(content) 1376 1377 contentsSlice := strings.Fields(content) 1378 expectedPaths := []string{"/b/first", "/a/additional", "/a/second", "/z/zAdditional", "/c/third", "/b/bAdditional"} 1379 sort.Strings(expectedPaths) 1380 1381 foundPaths := []string{} 1382 for _, contentValue := range contentsSlice { 1383 findExpectedPaths(&foundPaths, expectedPaths, contentValue) 1384 } 1385 1386 if allPresent := reflect.DeepEqual(foundPaths, expectedPaths); !allPresent { 1387 t.Fatalf("Found paths differed from expected paths. Got: %#v, want %#v", foundPaths, expectedPaths) 1388 } 1389 }) 1390 } 1391 } 1392 1393 func TestGenerateRPCOrderPreservedMergeFilesAdditionalBindingsMultipleServices(t *testing.T) { 1394 t.Parallel() 1395 1396 const in1 = ` 1397 file_to_generate: "exampleproto/v1/example.proto" 1398 parameter: "output_format=yaml,allow_delete_body=true" 1399 proto_file: { 1400 name: "exampleproto/v1/example.proto" 1401 package: "example.v1" 1402 message_type: { 1403 name: "Foo" 1404 field: { 1405 name: "bar" 1406 number: 1 1407 label: LABEL_OPTIONAL 1408 type: TYPE_STRING 1409 json_name: "bar" 1410 } 1411 } 1412 service: { 1413 name: "TestServiceOne" 1414 method: { 1415 name: "Test1" 1416 input_type: ".example.v1.Foo" 1417 output_type: ".example.v1.Foo" 1418 options: { 1419 [google.api.http]: { 1420 get: "/d/first" 1421 } 1422 } 1423 } 1424 method: { 1425 name: "Test2" 1426 input_type: ".example.v1.Foo" 1427 output_type: ".example.v1.Foo" 1428 options: { 1429 [google.api.http]: { 1430 get: "/e/second" 1431 } 1432 } 1433 } 1434 method: { 1435 name: "Test3" 1436 input_type: ".example.v1.Foo" 1437 output_type: ".example.v1.Foo" 1438 options: { 1439 [google.api.http]: { 1440 get: "/c/third" 1441 } 1442 } 1443 } 1444 } 1445 service: { 1446 name: "TestServiceTwo" 1447 method: { 1448 name: "Test1" 1449 input_type: ".example.v1.Foo" 1450 output_type: ".example.v1.Foo" 1451 options: { 1452 [google.api.http]: { 1453 get: "/b/first" 1454 } 1455 } 1456 } 1457 method: { 1458 name: "Test2" 1459 input_type: ".example.v1.Foo" 1460 output_type: ".example.v1.Foo" 1461 options: { 1462 [google.api.http]: { 1463 get: "/a/second" 1464 } 1465 } 1466 } 1467 method: { 1468 name: "Test3" 1469 input_type: ".example.v1.Foo" 1470 output_type: ".example.v1.Foo" 1471 options: { 1472 [google.api.http]: { 1473 get: "/g/third" 1474 } 1475 } 1476 } 1477 } 1478 options: { 1479 go_package: "exampleproto/v1;exampleproto" 1480 } 1481 }` 1482 1483 const in2 = ` 1484 file_to_generate: "exampleproto/v2/example.proto" 1485 parameter: "output_format=yaml,allow_delete_body=true" 1486 proto_file: { 1487 name: "exampleproto/v2/example.proto" 1488 package: "example.v2" 1489 message_type: { 1490 name: "Foo" 1491 field: { 1492 name: "bar" 1493 number: 1 1494 label: LABEL_OPTIONAL 1495 type: TYPE_STRING 1496 json_name: "bar" 1497 } 1498 } 1499 service: { 1500 name: "TestService" 1501 method: { 1502 name: "Test1" 1503 input_type: ".example.v2.Foo" 1504 output_type: ".example.v2.Foo" 1505 options: { 1506 [google.api.http]: { 1507 get: "/b/bpath" 1508 additional_bindings { 1509 get: "/a/additional" 1510 } 1511 } 1512 } 1513 } 1514 method: { 1515 name: "Test2" 1516 input_type: ".example.v2.Foo" 1517 output_type: ".example.v2.Foo" 1518 options: { 1519 [google.api.http]: { 1520 get: "/a/apath" 1521 additional_bindings { 1522 get: "/z/zAdditional" 1523 } 1524 } 1525 } 1526 } 1527 method: { 1528 name: "Test3" 1529 input_type: ".example.v2.Foo" 1530 output_type: ".example.v2.Foo" 1531 options: { 1532 [google.api.http]: { 1533 get: "/c/cpath" 1534 additional_bindings { 1535 get: "/b/bAdditional" 1536 } 1537 } 1538 } 1539 } 1540 } 1541 options: { 1542 go_package: "exampleproto/v2;exampleproto" 1543 } 1544 }` 1545 1546 var req1, req2 pluginpb.CodeGeneratorRequest 1547 1548 if err := prototext.Unmarshal([]byte(in1), &req1); err != nil { 1549 t.Fatalf("failed to marshall yaml: %s", err) 1550 } 1551 if err := prototext.Unmarshal([]byte(in2), &req2); err != nil { 1552 t.Fatalf("failed to marshall yaml: %s", err) 1553 } 1554 1555 req1.ProtoFile = append(req1.ProtoFile, req2.ProtoFile...) 1556 req1.FileToGenerate = append(req1.FileToGenerate, req2.FileToGenerate...) 1557 formats := [...]genopenapi.Format{ 1558 genopenapi.FormatJSON, 1559 genopenapi.FormatYAML, 1560 } 1561 1562 for _, format := range formats { 1563 format := format 1564 t.Run(string(format), func(t *testing.T) { 1565 t.Parallel() 1566 1567 resp := requireGenerate(t, &req1, format, true, true) 1568 if len(resp) != 1 { 1569 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 1570 } 1571 1572 content := resp[0].GetContent() 1573 1574 t.Log(content) 1575 1576 contentsSlice := strings.Fields(content) 1577 expectedPaths := []string{"/d/first", "/e/second", "/c/third", 1578 "/b/first", "/a/second", "/g/third", "/b/bpath", "/a/additional", 1579 "/a/apath", "/z/zAdditional", "/c/cpath", "/b/bAdditional"} 1580 1581 foundPaths := []string{} 1582 for _, contentValue := range contentsSlice { 1583 findExpectedPaths(&foundPaths, expectedPaths, contentValue) 1584 } 1585 1586 if allPresent := reflect.DeepEqual(foundPaths, expectedPaths); !allPresent { 1587 t.Fatalf("Found paths differed from expected paths. Got: %#v, want %#v", foundPaths, expectedPaths) 1588 } 1589 }) 1590 } 1591 } 1592 1593 func TestGenerateRPCOrderNotPreservedMergeFilesAdditionalBindingsMultipleServices(t *testing.T) { 1594 t.Parallel() 1595 1596 const in1 = ` 1597 file_to_generate: "exampleproto/v1/example.proto" 1598 parameter: "output_format=yaml,allow_delete_body=true" 1599 proto_file: { 1600 name: "exampleproto/v1/example.proto" 1601 package: "example.v1" 1602 message_type: { 1603 name: "Foo" 1604 field: { 1605 name: "bar" 1606 number: 1 1607 label: LABEL_OPTIONAL 1608 type: TYPE_STRING 1609 json_name: "bar" 1610 } 1611 } 1612 service: { 1613 name: "TestServiceOne" 1614 method: { 1615 name: "Test1" 1616 input_type: ".example.v1.Foo" 1617 output_type: ".example.v1.Foo" 1618 options: { 1619 [google.api.http]: { 1620 get: "/d/first" 1621 } 1622 } 1623 } 1624 method: { 1625 name: "Test2" 1626 input_type: ".example.v1.Foo" 1627 output_type: ".example.v1.Foo" 1628 options: { 1629 [google.api.http]: { 1630 get: "/e/second" 1631 } 1632 } 1633 } 1634 method: { 1635 name: "Test3" 1636 input_type: ".example.v1.Foo" 1637 output_type: ".example.v1.Foo" 1638 options: { 1639 [google.api.http]: { 1640 get: "/c/third" 1641 } 1642 } 1643 } 1644 } 1645 service: { 1646 name: "TestServiceTwo" 1647 method: { 1648 name: "Test1" 1649 input_type: ".example.v1.Foo" 1650 output_type: ".example.v1.Foo" 1651 options: { 1652 [google.api.http]: { 1653 get: "/b/first" 1654 } 1655 } 1656 } 1657 method: { 1658 name: "Test2" 1659 input_type: ".example.v1.Foo" 1660 output_type: ".example.v1.Foo" 1661 options: { 1662 [google.api.http]: { 1663 get: "/a/second" 1664 } 1665 } 1666 } 1667 method: { 1668 name: "Test3" 1669 input_type: ".example.v1.Foo" 1670 output_type: ".example.v1.Foo" 1671 options: { 1672 [google.api.http]: { 1673 get: "/g/third" 1674 } 1675 } 1676 } 1677 } 1678 options: { 1679 go_package: "exampleproto/v1;exampleproto" 1680 } 1681 }` 1682 1683 const in2 = ` 1684 file_to_generate: "exampleproto/v2/example.proto" 1685 parameter: "output_format=yaml,allow_delete_body=true" 1686 proto_file: { 1687 name: "exampleproto/v2/example.proto" 1688 package: "example.v2" 1689 message_type: { 1690 name: "Foo" 1691 field: { 1692 name: "bar" 1693 number: 1 1694 label: LABEL_OPTIONAL 1695 type: TYPE_STRING 1696 json_name: "bar" 1697 } 1698 } 1699 service: { 1700 name: "TestService" 1701 method: { 1702 name: "Test1" 1703 input_type: ".example.v2.Foo" 1704 output_type: ".example.v2.Foo" 1705 options: { 1706 [google.api.http]: { 1707 get: "/b/bpath" 1708 additional_bindings { 1709 get: "/a/additional" 1710 } 1711 } 1712 } 1713 } 1714 method: { 1715 name: "Test2" 1716 input_type: ".example.v2.Foo" 1717 output_type: ".example.v2.Foo" 1718 options: { 1719 [google.api.http]: { 1720 get: "/a/apath" 1721 additional_bindings { 1722 get: "/z/zAdditional" 1723 } 1724 } 1725 } 1726 } 1727 method: { 1728 name: "Test3" 1729 input_type: ".example.v2.Foo" 1730 output_type: ".example.v2.Foo" 1731 options: { 1732 [google.api.http]: { 1733 get: "/c/cpath" 1734 additional_bindings { 1735 get: "/b/bAdditional" 1736 } 1737 } 1738 } 1739 } 1740 } 1741 options: { 1742 go_package: "exampleproto/v2;exampleproto" 1743 } 1744 }` 1745 1746 var req1, req2 pluginpb.CodeGeneratorRequest 1747 1748 if err := prototext.Unmarshal([]byte(in1), &req1); err != nil { 1749 t.Fatalf("failed to marshall yaml: %s", err) 1750 } 1751 if err := prototext.Unmarshal([]byte(in2), &req2); err != nil { 1752 t.Fatalf("failed to marshall yaml: %s", err) 1753 } 1754 1755 req1.ProtoFile = append(req1.ProtoFile, req2.ProtoFile...) 1756 req1.FileToGenerate = append(req1.FileToGenerate, req2.FileToGenerate...) 1757 formats := [...]genopenapi.Format{ 1758 genopenapi.FormatJSON, 1759 genopenapi.FormatYAML, 1760 } 1761 1762 for _, format := range formats { 1763 format := format 1764 t.Run(string(format), func(t *testing.T) { 1765 t.Parallel() 1766 1767 resp := requireGenerate(t, &req1, format, false, true) 1768 if len(resp) != 1 { 1769 t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) 1770 } 1771 1772 content := resp[0].GetContent() 1773 1774 t.Log(content) 1775 1776 contentsSlice := strings.Fields(content) 1777 expectedPaths := []string{"/d/first", "/e/second", "/c/third", 1778 "/b/first", "/a/second", "/g/third", "/b/bpath", "/a/additional", 1779 "/a/apath", "/z/zAdditional", "/c/cpath", "/b/bAdditional"} 1780 sort.Strings(expectedPaths) 1781 1782 foundPaths := []string{} 1783 for _, contentValue := range contentsSlice { 1784 findExpectedPaths(&foundPaths, expectedPaths, contentValue) 1785 } 1786 1787 if allPresent := reflect.DeepEqual(foundPaths, expectedPaths); !allPresent { 1788 t.Fatalf("Found paths differed from expected paths. Got: %#v, want %#v", foundPaths, expectedPaths) 1789 } 1790 }) 1791 } 1792 } 1793 1794 // Tries to find expected paths from a provided substring and store them in the foundPaths 1795 // slice. 1796 func findExpectedPaths(foundPaths *[]string, expectedPaths []string, potentialPath string) { 1797 seenPaths := map[string]struct{}{} 1798 1799 // foundPaths may not be empty when this function is called multiple times, 1800 // so we add them to seenPaths map to avoid duplicates. 1801 for _, path := range *foundPaths { 1802 seenPaths[path] = struct{}{} 1803 } 1804 1805 for _, path := range expectedPaths { 1806 _, pathAlreadySeen := seenPaths[path] 1807 if strings.Contains(potentialPath, path) && !pathAlreadySeen { 1808 *foundPaths = append(*foundPaths, path) 1809 seenPaths[path] = struct{}{} 1810 } 1811 } 1812 } 1813 1814 func TestFindExpectedPaths(t *testing.T) { 1815 t.Parallel() 1816 1817 testCases := [...]struct { 1818 testName string 1819 requiredPaths []string 1820 potentialPath string 1821 expectedPathsFound []string 1822 }{ 1823 { 1824 testName: "One potential path present", 1825 requiredPaths: []string{"/d/first", "/e/second", "/c/third", "/b/first"}, 1826 potentialPath: "[{\"path: \"/d/first\"", 1827 expectedPathsFound: []string{"/d/first"}, 1828 }, 1829 { 1830 testName: "No potential Paths present", 1831 requiredPaths: []string{"/d/first", "/e/second", "/c/third", "/b/first"}, 1832 potentialPath: "[{\"path: \"/z/zpath\"", 1833 expectedPathsFound: []string{}, 1834 }, 1835 { 1836 testName: "Multiple potential paths present", 1837 requiredPaths: []string{"/d/first", "/e/second", "/c/third", "/b/first", "/d/first"}, 1838 potentialPath: "[{\"path: \"/d/first\"someData\"/c/third\"someData\"/b/third\"", 1839 expectedPathsFound: []string{"/d/first", "/c/third"}, 1840 }, 1841 } 1842 1843 for _, tc := range testCases { 1844 tc := tc 1845 1846 t.Run(tc.testName, func(t *testing.T) { 1847 t.Parallel() 1848 1849 foundPaths := []string{} 1850 findExpectedPaths(&foundPaths, tc.requiredPaths, tc.potentialPath) 1851 if correctPathsFound := reflect.DeepEqual(foundPaths, tc.expectedPathsFound); !correctPathsFound { 1852 t.Fatalf("Found paths differed from expected paths. Got: %#v, want %#v", foundPaths, tc.expectedPathsFound) 1853 } 1854 }) 1855 } 1856 }