github.com/googleapis/api-linter@v1.65.2/rules/internal/utils/method_test.go (about) 1 // Copyright 2023 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 package utils 15 16 import ( 17 "testing" 18 19 "github.com/googleapis/api-linter/rules/internal/testutils" 20 ) 21 22 func TestIsCreateMethod(t *testing.T) { 23 for _, test := range []struct { 24 name string 25 RPCs string 26 want bool 27 }{ 28 {"ValidBook", ` 29 rpc CreateBook(CreateBookRequest) returns (Book) {}; 30 `, true}, 31 {"InvalidNonCreate", ` 32 rpc GenerateBook(CreateBookRequest) returns (Book) {}; 33 `, false}, 34 } { 35 t.Run(test.name, func(t *testing.T) { 36 file := testutils.ParseProto3Tmpl(t, ` 37 import "google/api/resource.proto"; 38 import "google/protobuf/field_mask.proto"; 39 service Foo { 40 {{.RPCs}} 41 } 42 43 // This is at the top to make it retrievable 44 // by the test code. 45 message Book { 46 option (google.api.resource) = { 47 type: "library.googleapis.com/Book" 48 pattern: "books/{book}" 49 singular: "book" 50 plural: "books" 51 }; 52 } 53 54 message CreateBookRequest { 55 // The parent resource where this book will be created. 56 // Format: publishers/{publisher} 57 string parent = 1; 58 59 // The book to create. 60 Book book = 2; 61 } 62 `, test) 63 method := file.GetServices()[0].GetMethods()[0] 64 got := IsCreateMethod(method) 65 if got != test.want { 66 t.Errorf("IsCreateMethod got %v, want %v", got, test.want) 67 } 68 }) 69 } 70 } 71 72 func TestIsUpdateMethod(t *testing.T) { 73 for _, test := range []struct { 74 name string 75 RPCs string 76 want bool 77 }{ 78 {"ValidBook", ` 79 rpc UpdateBook(UpdateBookRequest) returns (Book) {}; 80 `, true}, 81 {"InvalidNonUpdate", ` 82 rpc UpsertBook(UpdateBookRequest) returns (Book) {}; 83 `, false}, 84 } { 85 t.Run(test.name, func(t *testing.T) { 86 file := testutils.ParseProto3Tmpl(t, ` 87 import "google/api/resource.proto"; 88 import "google/protobuf/field_mask.proto"; 89 service Foo { 90 {{.RPCs}} 91 } 92 93 // This is at the top to make it retrievable 94 // by the test code. 95 message Book { 96 option (google.api.resource) = { 97 type: "library.googleapis.com/Book" 98 pattern: "books/{book}" 99 singular: "book" 100 plural: "books" 101 }; 102 } 103 104 message UpdateBookRequest { 105 Book book = 1; 106 google.protobuf.FieldMask update_mask = 2; 107 } 108 `, test) 109 method := file.GetServices()[0].GetMethods()[0] 110 got := IsUpdateMethod(method) 111 if got != test.want { 112 t.Errorf("IsUpdateMethod got %v, want %v", got, test.want) 113 } 114 }) 115 } 116 } 117 118 func TestIsListMethod(t *testing.T) { 119 for _, test := range []struct { 120 name string 121 RPCs string 122 want bool 123 }{ 124 {"ValidList", ` 125 rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {}; 126 `, true}, 127 {"ValidListRevisionsMethod", ` 128 rpc ListBookRevisions(ListBookRevisionsRequest) returns (ListBookRevisionsResponse) {}; 129 `, true}, 130 {"InvalidNonList", ` 131 rpc EnumerateBooks(ListBooksRequest) returns (ListBooksResponse) {}; 132 `, false}, 133 } { 134 t.Run(test.name, func(t *testing.T) { 135 file := testutils.ParseProto3Tmpl(t, ` 136 import "google/api/resource.proto"; 137 import "google/protobuf/field_mask.proto"; 138 service Foo { 139 {{.RPCs}} 140 } 141 142 // This is at the top to make it retrievable 143 // by the test code. 144 message Book { 145 option (google.api.resource) = { 146 type: "library.googleapis.com/Book" 147 pattern: "books/{book}" 148 singular: "book" 149 plural: "books" 150 }; 151 } 152 153 // This is at the top to make it retrievable 154 // by the test code. 155 message BookRevision { 156 option (google.api.resource) = { 157 type: "library.googleapis.com/BookRevision" 158 pattern: "books/{book}/revisions/{revision}" 159 singular: "bookRevision" 160 plural: "bookRevisions" 161 }; 162 } 163 164 message ListBooksRequest { 165 string parent = 1; 166 int32 page_size = 2; 167 string page_token = 3; 168 } 169 170 message ListBooksResponse { 171 repeated Book books = 1; 172 string next_page_token = 2; 173 } 174 175 message ListBookRevisionsRequest { 176 string parent = 1; 177 int32 page_size = 2; 178 string page_token = 3; 179 } 180 181 message ListBookRevisionsResponse { 182 repeated BookRevision book_revisions = 1; 183 string next_page_token = 2; 184 } 185 `, test) 186 method := file.GetServices()[0].GetMethods()[0] 187 got := IsListMethod(method) 188 if got != test.want { 189 t.Errorf("IsListMethod got %v, want %v", got, test.want) 190 } 191 }) 192 } 193 } 194 195 func TestIsLegacyListRevisionsMethod(t *testing.T) { 196 for _, test := range []struct { 197 name string 198 RPCs string 199 want bool 200 }{ 201 {"ValidLegacyListRevisionsMethod", ` 202 rpc ListBookRevisions(ListBookRevisionsRequest) returns (ListBookRevisionsResponse) { 203 option (google.api.http) = { 204 get: "/v1/{name=books/*}:listRevisions" 205 }; 206 }; 207 `, true}, 208 {"ValidLegacyListRevisionsMethodWithoutHTTP", ` 209 rpc ListBookRevisions(ListBookRevisionsRequest) returns (ListBookRevisionsResponse) {}; 210 `, true}, 211 {"InvalidLegacyListRevisionsMethod", ` 212 rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {}; 213 `, false}, 214 } { 215 t.Run(test.name, func(t *testing.T) { 216 file := testutils.ParseProto3Tmpl(t, ` 217 import "google/api/annotations.proto"; 218 import "google/api/resource.proto"; 219 import "google/protobuf/field_mask.proto"; 220 service Foo { 221 {{.RPCs}} 222 } 223 224 // This is at the top to make it retrievable 225 // by the test code. 226 message Book { 227 option (google.api.resource) = { 228 type: "library.googleapis.com/Book" 229 pattern: "books/{book}" 230 singular: "book" 231 plural: "books" 232 }; 233 } 234 235 message ListBooksRequest { 236 string parent = 1; 237 int32 page_size = 2; 238 string page_token = 3; 239 } 240 241 message ListBooksResponse { 242 repeated Book books = 1; 243 string next_page_token = 2; 244 } 245 246 message ListBookRevisionsRequest { 247 string name = 1; 248 int32 page_size = 2; 249 string page_token = 3; 250 } 251 252 message ListBookRevisionsResponse { 253 repeated Book books = 1; 254 string next_page_token = 2; 255 } 256 `, test) 257 method := file.GetServices()[0].GetMethods()[0] 258 got := IsLegacyListRevisionsMethod(method) 259 if got != test.want { 260 t.Errorf("IsLegacyListRevisionsMethod got %v, want %v", got, test.want) 261 } 262 }) 263 } 264 } 265 266 func TestGetListResourceMessage(t *testing.T) { 267 for _, test := range []struct { 268 name string 269 RPCs string 270 want string 271 }{ 272 {"ValidBooks", ` 273 rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {}; 274 `, "Book"}, 275 {"InvalidNotListMethod", ` 276 rpc GetBook(ListBooksRequest) returns (Book) {}; 277 `, ""}, 278 } { 279 t.Run(test.name, func(t *testing.T) { 280 file := testutils.ParseProto3Tmpl(t, ` 281 import "google/api/resource.proto"; 282 import "google/protobuf/field_mask.proto"; 283 service Foo { 284 {{.RPCs}} 285 } 286 287 // This is at the top to make it retrievable 288 // by the test code. 289 message Book { 290 option (google.api.resource) = { 291 type: "library.googleapis.com/Book" 292 pattern: "books/{book}" 293 singular: "book" 294 plural: "books" 295 }; 296 } 297 298 message ListBooksRequest { 299 string parent = 1; 300 int32 page_size = 2; 301 string page_token = 3; 302 } 303 304 message ListBooksResponse { 305 repeated Book books = 1; 306 string next_page_token = 2; 307 } 308 `, test) 309 method := file.GetServices()[0].GetMethods()[0] 310 message := GetListResourceMessage(method) 311 got := "" 312 if message != nil { 313 got = message.GetName() 314 } 315 if got != test.want { 316 t.Errorf("GetListResourceMessage got %q, want %q", got, test.want) 317 } 318 }) 319 } 320 }