github.com/googleapis/api-linter@v1.65.2/rules/internal/utils/extension_test.go (about)

     1  // Copyright 2019 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package utils
    16  
    17  import (
    18  	"testing"
    19  
    20  	"bitbucket.org/creachadair/stringset"
    21  	"github.com/google/go-cmp/cmp"
    22  	"github.com/googleapis/api-linter/rules/internal/testutils"
    23  	apb "google.golang.org/genproto/googleapis/api/annotations"
    24  	"google.golang.org/protobuf/proto"
    25  )
    26  
    27  func TestGetFieldBehavior(t *testing.T) {
    28  	fd := testutils.ParseProto3String(t, `
    29  		import "google/api/field_behavior.proto";
    30  
    31  		message Book {
    32  			string name = 1 [
    33  				(google.api.field_behavior) = IMMUTABLE,
    34  				(google.api.field_behavior) = OUTPUT_ONLY];
    35  
    36  			string title = 2 [(google.api.field_behavior) = REQUIRED];
    37  
    38  			string summary = 3;
    39  		}
    40  	`)
    41  	msg := fd.GetMessageTypes()[0]
    42  	tests := []struct {
    43  		fieldName      string
    44  		fieldBehaviors stringset.Set
    45  	}{
    46  		{"name", stringset.New("IMMUTABLE", "OUTPUT_ONLY")},
    47  		{"title", stringset.New("REQUIRED")},
    48  		{"summary", stringset.New()},
    49  	}
    50  	for _, test := range tests {
    51  		t.Run(test.fieldName, func(t *testing.T) {
    52  			f := msg.FindFieldByName(test.fieldName)
    53  			if diff := cmp.Diff(GetFieldBehavior(f), test.fieldBehaviors); diff != "" {
    54  				t.Errorf(diff)
    55  			}
    56  		})
    57  	}
    58  }
    59  
    60  func TestGetMethodSignatures(t *testing.T) {
    61  	for _, test := range []struct {
    62  		name       string
    63  		want       [][]string
    64  		Signatures string
    65  	}{
    66  		{"Zero", [][]string{}, ""},
    67  		{"One", [][]string{{"name"}}, `option (google.api.method_signature) = "name";`},
    68  		{
    69  			"Two",
    70  			[][]string{{"name"}, {"name", "read_mask"}},
    71  			`option (google.api.method_signature) = "name";
    72  			 option (google.api.method_signature) = "name,read_mask";`,
    73  		},
    74  	} {
    75  		t.Run(test.name, func(t *testing.T) {
    76  			f := testutils.ParseProto3Tmpl(t, `
    77  				import "google/api/client.proto";
    78  				service Library {
    79  					rpc GetBook(GetBookRequest) returns (Book) {
    80  						{{.Signatures}}
    81  					}
    82  				}
    83  				message Book {}
    84  				message GetBookRequest {}
    85  			`, test)
    86  			method := f.GetServices()[0].GetMethods()[0]
    87  			if diff := cmp.Diff(GetMethodSignatures(method), test.want); diff != "" {
    88  				t.Errorf(diff)
    89  			}
    90  		})
    91  	}
    92  }
    93  
    94  func TestGetOperationInfo(t *testing.T) {
    95  	fd := testutils.ParseProto3String(t, `
    96  		import "google/longrunning/operations.proto";
    97  		service Library {
    98  			rpc WriteBook(WriteBookRequest) returns (google.longrunning.Operation) {
    99  				option (google.longrunning.operation_info) = {
   100  					response_type: "WriteBookResponse"
   101  					metadata_type: "WriteBookMetadata"
   102  				};
   103  			}
   104  		}
   105  		message WriteBookRequest {}
   106  	`)
   107  	lro := GetOperationInfo(fd.GetServices()[0].GetMethods()[0])
   108  	if got, want := lro.ResponseType, "WriteBookResponse"; got != want {
   109  		t.Errorf("Response type - got %q, want %q.", got, want)
   110  	}
   111  	if got, want := lro.MetadataType, "WriteBookMetadata"; got != want {
   112  		t.Errorf("Metadata type - got %q, want %q.", got, want)
   113  	}
   114  }
   115  
   116  func TestGetOperationInfoNone(t *testing.T) {
   117  	fd := testutils.ParseProto3String(t, `
   118  		service Library {
   119  			rpc GetBook(GetBookRequest) returns (Book);
   120  		}
   121  		message GetBookRequest {}
   122  		message Book {}
   123  	`)
   124  	lro := GetOperationInfo(fd.GetServices()[0].GetMethods()[0])
   125  	if lro != nil {
   126  		t.Errorf("Got %v, expected nil LRO annotation.", lro)
   127  	}
   128  }
   129  
   130  func TestGetOperationInfoResponseType(t *testing.T) {
   131  	// Set up testing permutations.
   132  	tests := []struct {
   133  		testName     string
   134  		ResponseType string
   135  		valid        bool
   136  	}{
   137  		{"Valid", "WriteBookResponse", true},
   138  		{"Invalid", "Foo", false},
   139  	}
   140  	for _, test := range tests {
   141  		t.Run(test.testName, func(t *testing.T) {
   142  			fd := testutils.ParseProto3Tmpl(t, `
   143  				import "google/longrunning/operations.proto";
   144  				service Library {
   145  					rpc WriteBook(WriteBookRequest) returns (google.longrunning.Operation) {
   146  						option (google.longrunning.operation_info) = {
   147  							response_type: "{{ .ResponseType }}"
   148  							metadata_type: "WriteBookMetadata"
   149  						};
   150  					}
   151  				}
   152  				message WriteBookRequest {}
   153  				message WriteBookResponse {}
   154  			`, test)
   155  
   156  			typ := GetOperationResponseType(fd.GetServices()[0].GetMethods()[0])
   157  
   158  			if validType := typ != nil; validType != test.valid {
   159  				t.Fatalf("Expected valid(%v) response_type message", test.valid)
   160  			}
   161  
   162  			if !test.valid {
   163  				return
   164  			}
   165  
   166  			if got, want := typ.GetName(), test.ResponseType; got != want {
   167  				t.Errorf("Response type - got %q, want %q.", got, want)
   168  			}
   169  		})
   170  	}
   171  }
   172  
   173  func TestGetOperationInfoMetadataType(t *testing.T) {
   174  	// Set up testing permutations.
   175  	tests := []struct {
   176  		testName     string
   177  		MetadataType string
   178  		valid        bool
   179  	}{
   180  		{"Valid", "WriteBookMetadata", true},
   181  		{"Invalid", "Foo", false},
   182  	}
   183  	for _, test := range tests {
   184  		t.Run(test.testName, func(t *testing.T) {
   185  			fd := testutils.ParseProto3Tmpl(t, `
   186  				import "google/longrunning/operations.proto";
   187  				service Library {
   188  					rpc WriteBook(WriteBookRequest) returns (google.longrunning.Operation) {
   189  						option (google.longrunning.operation_info) = {
   190  							response_type: "WriteBookResponse"
   191  							metadata_type: "{{ .MetadataType }}"
   192  						};
   193  					}
   194  				}
   195  				message WriteBookRequest {}
   196  				message WriteBookMetadata {}
   197  			`, test)
   198  
   199  			typ := GetMetadataType(fd.GetServices()[0].GetMethods()[0])
   200  
   201  			if validType := typ != nil; validType != test.valid {
   202  				t.Fatalf("Expected valid(%v) metadata_type message", test.valid)
   203  			}
   204  
   205  			if !test.valid {
   206  				return
   207  			}
   208  
   209  			if got, want := typ.GetName(), test.MetadataType; got != want {
   210  				t.Errorf("Metadata type - got %q, want %q.", got, want)
   211  			}
   212  		})
   213  	}
   214  }
   215  
   216  func TestGetResource(t *testing.T) {
   217  	t.Run("Present", func(t *testing.T) {
   218  		f := testutils.ParseProto3String(t, `
   219  			import "google/api/resource.proto";
   220  			message Book {
   221  				option (google.api.resource) = {
   222  					type: "library.googleapis.com/Book"
   223  					pattern: "publishers/{publisher}/books/{book}"
   224  				};
   225  			}
   226  		`)
   227  		resource := GetResource(f.GetMessageTypes()[0])
   228  		if got, want := resource.GetType(), "library.googleapis.com/Book"; got != want {
   229  			t.Errorf("Got %q, expected %q.", got, want)
   230  		}
   231  		if got, want := resource.GetPattern()[0], "publishers/{publisher}/books/{book}"; got != want {
   232  			t.Errorf("Got %q, expected %q.", got, want)
   233  		}
   234  	})
   235  	t.Run("Absent", func(t *testing.T) {
   236  		f := testutils.ParseProto3String(t, "message Book {}")
   237  		if got := GetResource(f.GetMessageTypes()[0]); got != nil {
   238  			t.Errorf(`Got "%v", expected nil.`, got)
   239  		}
   240  	})
   241  	t.Run("Nil", func(t *testing.T) {
   242  		if got := GetResource(nil); got != nil {
   243  			t.Errorf(`Got "%v", expected nil.`, got)
   244  		}
   245  	})
   246  }
   247  
   248  func TestGetResourceDefinition(t *testing.T) {
   249  	t.Run("Zero", func(t *testing.T) {
   250  		f := testutils.ParseProto3String(t, `
   251  			import "google/api/resource.proto";
   252  		`)
   253  		if got := GetResourceDefinitions(f); got != nil {
   254  			t.Errorf("Got %v, expected nil.", got)
   255  		}
   256  	})
   257  	t.Run("One", func(t *testing.T) {
   258  		f := testutils.ParseProto3String(t, `
   259  			import "google/api/resource.proto";
   260  			option (google.api.resource_definition) = {
   261  				type: "library.googleapis.com/Book"
   262  			};
   263  		`)
   264  		defs := GetResourceDefinitions(f)
   265  		if got, want := len(defs), 1; got != want {
   266  			t.Errorf("Got %d definitions, expected %d.", got, want)
   267  		}
   268  		if got, want := defs[0].GetType(), "library.googleapis.com/Book"; got != want {
   269  			t.Errorf("Got %s for type, expected %s.", got, want)
   270  		}
   271  	})
   272  	t.Run("Two", func(t *testing.T) {
   273  		f := testutils.ParseProto3String(t, `
   274  			import "google/api/resource.proto";
   275  			option (google.api.resource_definition) = {
   276  				type: "library.googleapis.com/Book"
   277  			};
   278  			option (google.api.resource_definition) = {
   279  				type: "library.googleapis.com/Author"
   280  			};
   281  		`)
   282  		defs := GetResourceDefinitions(f)
   283  		if got, want := len(defs), 2; got != want {
   284  			t.Errorf("Got %d definitions, expected %d.", got, want)
   285  		}
   286  		if got, want := defs[0].GetType(), "library.googleapis.com/Book"; got != want {
   287  			t.Errorf("Got %s for type, expected %s.", got, want)
   288  		}
   289  		if got, want := defs[1].GetType(), "library.googleapis.com/Author"; got != want {
   290  			t.Errorf("Got %s for type, expected %s.", got, want)
   291  		}
   292  	})
   293  }
   294  
   295  func TestGetResourceReference(t *testing.T) {
   296  	t.Run("Present", func(t *testing.T) {
   297  		f := testutils.ParseProto3String(t, `
   298  			import "google/api/resource.proto";
   299  			message GetBookRequest {
   300  				string name = 1 [(google.api.resource_reference) = {
   301  					type: "library.googleapis.com/Book"
   302  				}];
   303  			}
   304  		`)
   305  		ref := GetResourceReference(f.GetMessageTypes()[0].GetFields()[0])
   306  		if got, want := ref.GetType(), "library.googleapis.com/Book"; got != want {
   307  			t.Errorf("Got %q, expected %q.", got, want)
   308  		}
   309  	})
   310  	t.Run("Absent", func(t *testing.T) {
   311  		f := testutils.ParseProto3String(t, "message GetBookRequest { string name = 1; }")
   312  		if got := GetResourceReference(f.GetMessageTypes()[0].GetFields()[0]); got != nil {
   313  			t.Errorf(`Got "%v", expected nil`, got)
   314  		}
   315  	})
   316  }
   317  
   318  func TestFindResource(t *testing.T) {
   319  	files := testutils.ParseProtoStrings(t, map[string]string{
   320  		"book.proto": `
   321  			syntax = "proto3";
   322  			package test;
   323  
   324  			import "google/api/resource.proto";
   325  
   326  			message Book {
   327  				option (google.api.resource) = {
   328  					type: "library.googleapis.com/Book"
   329  					pattern: "publishers/{publisher}/books/{book}"
   330  				};
   331  
   332  				string name = 1;
   333  			}
   334  		`,
   335  		"shelf.proto": `
   336  			syntax = "proto3";
   337  			package test;
   338  
   339  			import "book.proto";
   340  			import "google/api/resource.proto";
   341  
   342  			message Shelf {
   343  				option (google.api.resource) = {
   344  					type: "library.googleapis.com/Shelf"
   345  					pattern: "shelves/{shelf}"
   346  				};
   347  
   348  				string name = 1;
   349  
   350  				repeated Book books = 2;
   351  			}
   352  		`,
   353  	})
   354  
   355  	for _, tst := range []struct {
   356  		name, reference string
   357  		notFound        bool
   358  	}{
   359  		{"local_reference", "library.googleapis.com/Shelf", false},
   360  		{"imported_reference", "library.googleapis.com/Book", false},
   361  		{"unresolvable", "foo.googleapis.com/Bar", true},
   362  	} {
   363  		t.Run(tst.name, func(t *testing.T) {
   364  			got := FindResource(tst.reference, files["shelf.proto"])
   365  
   366  			if tst.notFound && got != nil {
   367  				t.Fatalf("Expected to not find the resource, but found %q", got.GetType())
   368  			}
   369  
   370  			if !tst.notFound && got == nil {
   371  				t.Errorf("Got nil, expected %q", tst.reference)
   372  			} else if !tst.notFound && got.GetType() != tst.reference {
   373  				t.Errorf("Got %q, expected %q", got.GetType(), tst.reference)
   374  			}
   375  		})
   376  	}
   377  }
   378  
   379  func TestFindResourceMessage(t *testing.T) {
   380  	files := testutils.ParseProtoStrings(t, map[string]string{
   381  		"book.proto": `
   382  			syntax = "proto3";
   383  			package test;
   384  
   385  			import "google/api/resource.proto";
   386  
   387  			message Book {
   388  				option (google.api.resource) = {
   389  					type: "library.googleapis.com/Book"
   390  					pattern: "publishers/{publisher}/books/{book}"
   391  				};
   392  
   393  				string name = 1;
   394  			}
   395  		`,
   396  		"shelf.proto": `
   397  			syntax = "proto3";
   398  			package test;
   399  
   400  			import "book.proto";
   401  			import "google/api/resource.proto";
   402  
   403  			message Shelf {
   404  				option (google.api.resource) = {
   405  					type: "library.googleapis.com/Shelf"
   406  					pattern: "shelves/{shelf}"
   407  				};
   408  
   409  				string name = 1;
   410  
   411  				repeated Book books = 2;
   412  			}
   413  		`,
   414  	})
   415  
   416  	for _, tst := range []struct {
   417  		name, reference, wantMsg string
   418  		notFound                 bool
   419  	}{
   420  		{"local_reference", "library.googleapis.com/Shelf", "Shelf", false},
   421  		{"imported_reference", "library.googleapis.com/Book", "Book", false},
   422  		{"unresolvable", "foo.googleapis.com/Bar", "", true},
   423  	} {
   424  		t.Run(tst.name, func(t *testing.T) {
   425  			got := FindResourceMessage(tst.reference, files["shelf.proto"])
   426  
   427  			if tst.notFound && got != nil {
   428  				t.Fatalf("Expected to not find the message, but found %q", got.GetName())
   429  			}
   430  
   431  			if !tst.notFound && got == nil {
   432  				t.Errorf("Got nil, expected %q", tst.wantMsg)
   433  			} else if !tst.notFound && got.GetName() != tst.wantMsg {
   434  				t.Errorf("Got %q, expected %q", got.GetName(), tst.wantMsg)
   435  			}
   436  		})
   437  	}
   438  }
   439  
   440  func TestSplitResourceTypeName(t *testing.T) {
   441  	for _, tst := range []struct {
   442  		name, input, service, typeName string
   443  		ok                             bool
   444  	}{
   445  		{"Valid", "foo.googleapis.com/Foo", "foo.googleapis.com", "Foo", true},
   446  		{"InvalidExtraSlashes", "foo.googleapis.com/Foo/Bar", "", "", false},
   447  		{"InvalidNoService", "/Foo", "", "", false},
   448  		{"InvalidNoTypeName", "foo.googleapis.com/", "", "", false},
   449  	} {
   450  		t.Run(tst.name, func(t *testing.T) {
   451  			s, typ, ok := SplitResourceTypeName(tst.input)
   452  			if ok != tst.ok {
   453  				t.Fatalf("Expected %v for ok, but got %v", tst.ok, ok)
   454  			}
   455  			if diff := cmp.Diff(s, tst.service); diff != "" {
   456  				t.Errorf("service: got(-),want(+):\n%s", diff)
   457  			}
   458  			if diff := cmp.Diff(typ, tst.typeName); diff != "" {
   459  				t.Errorf("type name: got(-),want(+):\n%s", diff)
   460  			}
   461  		})
   462  	}
   463  }
   464  
   465  func TestGetOutputOrLROResponseMessage(t *testing.T) {
   466  	for _, test := range []struct {
   467  		name string
   468  		RPCs string
   469  		want string
   470  	}{
   471  		{"BookOutputType", `
   472  			rpc CreateBook(CreateBookRequest) returns (Book) {};
   473  		`, "Book"},
   474  		{"BespokeOperationResource", `
   475  			rpc CreateBook(CreateBookRequest) returns (Operation) {};
   476  		`, "Operation"},
   477  		{"LROBookResponse", `
   478  			rpc CreateBook(CreateBookRequest) returns (google.longrunning.Operation) {
   479  				option (google.longrunning.operation_info) = {
   480  					response_type: "Book"
   481  				};
   482  		};
   483  		`, "Book"},
   484  		{"LROMissingResponse", `
   485  			rpc CreateBook(CreateBookRequest) returns (google.longrunning.Operation) {
   486  		};
   487  		`, ""},
   488  	} {
   489  		t.Run(test.name, func(t *testing.T) {
   490  			file := testutils.ParseProto3Tmpl(t, `
   491  				import "google/api/resource.proto";
   492  				import "google/longrunning/operations.proto";
   493  				import "google/protobuf/field_mask.proto";
   494  				service Foo {
   495  					{{.RPCs}}
   496  				}
   497  
   498  				// This is at the top to make it retrievable
   499  				// by the test code.
   500  				message Book {
   501  					option (google.api.resource) = {
   502  						type: "library.googleapis.com/Book"
   503  						pattern: "books/{book}"
   504  						singular: "book"
   505  						plural: "books"
   506  					};
   507  				}
   508  
   509  				message CreateBookRequest {
   510  					// The parent resource where this book will be created.
   511  					// Format: publishers/{publisher}
   512  					string parent = 1;
   513  
   514  					// The book to create.
   515  					Book book = 2;
   516  				}
   517  
   518  				// bespoke operation message (not an LRO)
   519  				message Operation {
   520  				}
   521  			`, test)
   522  			method := file.GetServices()[0].GetMethods()[0]
   523  			resp := GetResponseType(method)
   524  			got := ""
   525  			if resp != nil {
   526  				got = resp.GetName()
   527  			}
   528  			if got != test.want {
   529  				t.Errorf(
   530  					"GetOutputOrLROResponseMessage got %q, want %q",
   531  					got, test.want,
   532  				)
   533  			}
   534  		})
   535  	}
   536  }
   537  
   538  func TestFindResourceChildren(t *testing.T) {
   539  	publisher := &apb.ResourceDescriptor{
   540  		Type: "library.googleapis.com/Publisher",
   541  		Pattern: []string{
   542  			"publishers/{publisher}",
   543  		},
   544  	}
   545  	shelf := &apb.ResourceDescriptor{
   546  		Type: "library.googleapis.com/Shelf",
   547  		Pattern: []string{
   548  			"shelves/{shelf}",
   549  		},
   550  	}
   551  	book := &apb.ResourceDescriptor{
   552  		Type: "library.googleapis.com/Book",
   553  		Pattern: []string{
   554  			"publishers/{publisher}/books/{book}",
   555  		},
   556  	}
   557  	edition := &apb.ResourceDescriptor{
   558  		Type: "library.googleapis.com/Edition",
   559  		Pattern: []string{
   560  			"publishers/{publisher}/books/{book}/editions/{edition}",
   561  		},
   562  	}
   563  	files := testutils.ParseProtoStrings(t, map[string]string{
   564  		"book.proto": `
   565  			syntax = "proto3";
   566  			package test;
   567  
   568  			import "google/api/resource.proto";
   569  
   570  			message Book {
   571  				option (google.api.resource) = {
   572  					type: "library.googleapis.com/Book"
   573  					pattern: "publishers/{publisher}/books/{book}"
   574  				};
   575  
   576  				string name = 1;
   577  			}
   578  
   579  			message Edition {
   580  				option (google.api.resource) = {
   581  					type: "library.googleapis.com/Edition"
   582  					pattern: "publishers/{publisher}/books/{book}/editions/{edition}"
   583  				};
   584  
   585  				string name = 1;
   586  			}
   587  		`,
   588  		"shelf.proto": `
   589  			syntax = "proto3";
   590  			package test;
   591  
   592  			import "book.proto";
   593  			import "google/api/resource.proto";
   594  
   595  			message Shelf {
   596  				option (google.api.resource) = {
   597  					type: "library.googleapis.com/Shelf"
   598  					pattern: "shelves/{shelf}"
   599  				};
   600  
   601  				string name = 1;
   602  
   603  				repeated Book books = 2;
   604  			}
   605  		`,
   606  	})
   607  
   608  	for _, tst := range []struct {
   609  		name   string
   610  		parent *apb.ResourceDescriptor
   611  		want   []*apb.ResourceDescriptor
   612  	}{
   613  		{"has_child_same_file", book, []*apb.ResourceDescriptor{edition}},
   614  		{"has_child_other_file", publisher, []*apb.ResourceDescriptor{book, edition}},
   615  		{"no_children", shelf, nil},
   616  	} {
   617  		t.Run(tst.name, func(t *testing.T) {
   618  			got := FindResourceChildren(tst.parent, files["shelf.proto"])
   619  			if diff := cmp.Diff(tst.want, got, cmp.Comparer(proto.Equal)); diff != "" {
   620  				t.Errorf("got(-),want(+):\n%s", diff)
   621  			}
   622  		})
   623  	}
   624  }
   625  
   626  func TestHasFieldInfo(t *testing.T) {
   627  	testCases := []struct {
   628  		name, FieldInfo string
   629  		want            bool
   630  	}{
   631  		{
   632  			name:      "HasFieldInfo",
   633  			FieldInfo: "[(google.api.field_info).format = UUID4]",
   634  			want:      true,
   635  		},
   636  		{
   637  			name: "NoFieldInfo",
   638  			want: false,
   639  		},
   640  	}
   641  	for _, tc := range testCases {
   642  		t.Run(tc.name, func(t *testing.T) {
   643  			file := testutils.ParseProto3Tmpl(t, `
   644  			import "google/api/field_info.proto";
   645  			
   646  			message CreateBookRequest {
   647  				string foo = 1 {{.FieldInfo}};
   648  			}
   649  			`, tc)
   650  			fd := file.FindMessage("CreateBookRequest").FindFieldByName("foo")
   651  			if got := HasFieldInfo(fd); got != tc.want {
   652  				t.Errorf("HasFieldInfo(%+v): expected %v, got %v", fd, tc.want, got)
   653  			}
   654  		})
   655  	}
   656  }
   657  
   658  func TestGetFieldInfo(t *testing.T) {
   659  	testCases := []struct {
   660  		name, FieldInfo string
   661  		want            *apb.FieldInfo
   662  	}{
   663  		{
   664  			name:      "HasFieldInfo",
   665  			FieldInfo: "[(google.api.field_info).format = UUID4]",
   666  			want:      &apb.FieldInfo{Format: apb.FieldInfo_UUID4},
   667  		},
   668  		{
   669  			name: "NoFieldInfo",
   670  		},
   671  	}
   672  	for _, tc := range testCases {
   673  		t.Run(tc.name, func(t *testing.T) {
   674  			file := testutils.ParseProto3Tmpl(t, `
   675  			import "google/api/field_info.proto";
   676  			
   677  			message CreateBookRequest {
   678  				string foo = 1 {{.FieldInfo}};
   679  			}
   680  			`, tc)
   681  			fd := file.FindMessage("CreateBookRequest").FindFieldByName("foo")
   682  			got := GetFieldInfo(fd)
   683  			if diff := cmp.Diff(got, tc.want, cmp.Comparer(proto.Equal)); diff != "" {
   684  				t.Errorf("GetFieldInfo(%+v): got(-),want(+):\n%s", fd, diff)
   685  			}
   686  		})
   687  	}
   688  }
   689  
   690  func TestHasFormat(t *testing.T) {
   691  	testCases := []struct {
   692  		name, Format string
   693  		want         bool
   694  	}{
   695  		{
   696  			name:   "HasFormat",
   697  			Format: "format: UUID4",
   698  			want:   true,
   699  		},
   700  		{
   701  			name: "NoFormat",
   702  			want: false,
   703  		},
   704  	}
   705  	for _, tc := range testCases {
   706  		t.Run(tc.name, func(t *testing.T) {
   707  			file := testutils.ParseProto3Tmpl(t, `
   708  			import "google/api/field_info.proto";
   709  			
   710  			message CreateBookRequest {
   711  				string foo = 1 [(google.api.field_info) = {
   712  					{{.Format}}
   713  				}];
   714  			}
   715  			`, tc)
   716  			fd := file.FindMessage("CreateBookRequest").FindFieldByName("foo")
   717  			if got := HasFormat(fd); got != tc.want {
   718  				t.Errorf("HasFormat(%+v): expected %v, got %v", fd, tc.want, got)
   719  			}
   720  		})
   721  	}
   722  }
   723  
   724  func TestGetFormat(t *testing.T) {
   725  	testCases := []struct {
   726  		name, Format string
   727  		want         apb.FieldInfo_Format
   728  	}{
   729  		{
   730  			name:   "HasUUID4Format",
   731  			Format: "format: UUID4",
   732  			want:   apb.FieldInfo_UUID4,
   733  		},
   734  		{
   735  			name: "NoFormat",
   736  			want: apb.FieldInfo_FORMAT_UNSPECIFIED,
   737  		},
   738  	}
   739  	for _, tc := range testCases {
   740  		t.Run(tc.name, func(t *testing.T) {
   741  			file := testutils.ParseProto3Tmpl(t, `
   742  			import "google/api/field_info.proto";
   743  			
   744  			message CreateBookRequest {
   745  				string foo = 1 [(google.api.field_info) = {
   746  					{{.Format}}
   747  				}];
   748  			}
   749  			`, tc)
   750  			fd := file.FindMessage("CreateBookRequest").FindFieldByName("foo")
   751  			if got := GetFormat(fd); got != tc.want {
   752  				t.Errorf("GetFormat(%+v): expected %v, got %v", fd, tc.want, got)
   753  			}
   754  		})
   755  	}
   756  }