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  }