github.com/googleapis/api-linter@v1.65.2/rules/aip0127/http_template_pattern_test.go (about)

     1  // Copyright 2022 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 aip0127
    16  
    17  import (
    18  	"testing"
    19  
    20  	"github.com/googleapis/api-linter/rules/internal/testutils"
    21  )
    22  
    23  func TestHttpTemplatePattern_PatternMatching(t *testing.T) {
    24  	tests := []struct {
    25  		name            string
    26  		HTTPAnnotation  string
    27  		ResourcePattern string
    28  		problems        testutils.Problems
    29  	}{
    30  		// HTTP variable uses literals
    31  		{"LiteralMatchesSameLiteralInPattern", "/v1/{name=shelves}", "shelves", nil},
    32  		{"LiteralDoesNotMatchDifferentLiteral", "/v1/{name=shelves}", "books", testutils.Problems{{Message: "does not match"}}},
    33  		{"SuffixAfterHttpVariableIgnoredForMatch", "/v1/{name=shelves}/books", "shelves", nil},
    34  
    35  		// HTTP variable uses single wildcard
    36  		{"SingleWildcardMatchesAnyLiteralSegment", "/v1/{name=*}", "shelves", nil},
    37  		{"SingleWildcardMatchesAnyVariableSegment", "/v1/{name=*}", "{shelf}", nil},
    38  		{"SingleWildcardDoesNotMatchMultipleUrlSegments", "/v1/{name=*}", "shelves/{shelf}", testutils.Problems{{Message: "does not match"}}},
    39  		{"LiteralAndWildcardMatch", "/v1/{name=shelves/*}", "shelves/{shelf}", nil},
    40  		// This case is only theoretical, as "{shelf}" represents a resource ID,
    41  		// rather than a resource name. It should not be observed in practice.
    42  		{"ImplicitWildcardMatches", "/v1/{name}/books", "{shelf}", nil},
    43  		{"MulitpleWildcardsMatches", "/v1/{name=shelves/*/books/*}", "shelves/{shelf}/books/{book}", nil},
    44  
    45  		// HTTP variable uses double wildcard
    46  		{"DoubleWildcardMatchesZeroSegments", "/v1/{name=**}", "", nil},
    47  		{"DoubleWildcardMatchesOneLiteralSegment", "/v1/{name=**}", "shelves", nil},
    48  		{"DoubleWildcardMatchesMultipleLiteralSegments", "/v1/{name=**}", "my/shelves", nil},
    49  		{"DoubleWildcardMatchesOneVariableSegment", "/v1/{name=**}", "{shelf}", nil},
    50  		{"DoubleWildcardMatchesMultipleVariableSegments", "/v1/{name=**}", "{shelf}/{book}", nil},
    51  		{"DoubleWildcardMatchesMixedLiteralVariableSegments", "/v1/{name=**}", "shelves/{shelf}", nil},
    52  		{"DoubleWildcardPrecededByLiteralMatches", "/v1/{name=shelves/**}", "shelves/{shelf}", nil},
    53  		{"DoubleWildcardFollowedByLiteralMatches", "/v1/{name=**/shelves/*}", "my/shelves/{shelf}", nil},
    54  		{"DoubleWildcardMissingLiteralDoesNotMatch", "/v1/{name=shelves/**}", "{shelf}", testutils.Problems{{Message: "does not match"}}},
    55  	}
    56  	for _, test := range tests {
    57  		t.Run(test.name, func(t *testing.T) {
    58  			f := testutils.ParseProto3Tmpl(t, `
    59  				import "google/api/annotations.proto";
    60  				import "google/api/resource.proto";
    61  				service Library {
    62  					rpc GetBook(GetBookRequest) returns (Book) {
    63  						option (google.api.http) = {
    64  							get: "{{.HTTPAnnotation}}"
    65  						};
    66  					}
    67  				}
    68  				message GetBookRequest {
    69  					// Format: shelves/{shelf}/books/{book}
    70  					string name = 1 [(google.api.resource_reference).type = "library.googleapis.com/Book"];
    71  				}
    72  				message Book {
    73  					option (google.api.resource) = {
    74  						type: "library.googleapis.com/Book"
    75  						pattern: "{{.ResourcePattern}}"
    76  					};
    77  					string name = 1;
    78  				}
    79  			`, test)
    80  			m := f.GetServices()[0].GetMethods()[0]
    81  			if diff := test.problems.SetDescriptor(m).Diff(httpTemplatePattern.Lint(f)); diff != "" {
    82  				t.Errorf(diff)
    83  			}
    84  		})
    85  	}
    86  }
    87  
    88  func TestHttpTemplatePattern_MultiplePatterns(t *testing.T) {
    89  	tests := []struct {
    90  		name             string
    91  		HTTPAnnotation   string
    92  		ResourcePattern1 string
    93  		ResourcePattern2 string
    94  		problems         testutils.Problems
    95  	}{
    96  		{"MatchesIfFirstPatternMatches", "/v1/{name=shelves}", "shelves", "books", nil},
    97  		{"MatchesIfSecondPatternMatches", "/v1/{name=shelves}", "books", "shelves", nil},
    98  		{"FailsIfNeitherPatternMatches", "/v1/{name=shelves}", "books", "bins", testutils.Problems{{Message: "does not match"}}},
    99  	}
   100  	for _, test := range tests {
   101  		t.Run(test.name, func(t *testing.T) {
   102  			f := testutils.ParseProto3Tmpl(t, `
   103  				import "google/api/annotations.proto";
   104  				import "google/api/resource.proto";
   105  				service Library {
   106  					rpc GetBook(GetBookRequest) returns (Book) {
   107  						option (google.api.http) = {
   108  							get: "{{.HTTPAnnotation}}"
   109  						};
   110  					}
   111  				}
   112  				message GetBookRequest {
   113  					string name = 1 [(google.api.resource_reference).type = "library.googleapis.com/Book"];
   114  				}
   115  				message Book {
   116  					option (google.api.resource) = {
   117  						type: "library.googleapis.com/Book"
   118  						pattern: "{{.ResourcePattern1}}"
   119  						pattern: "{{.ResourcePattern2}}"
   120  					};
   121  					string name = 1;
   122  				}
   123  			`, test)
   124  			m := f.GetServices()[0].GetMethods()[0]
   125  			if diff := test.problems.SetDescriptor(m).Diff(httpTemplatePattern.Lint(f)); diff != "" {
   126  				t.Errorf(diff)
   127  			}
   128  		})
   129  	}
   130  }
   131  
   132  func TestHttpTemplatePattern_SkipCheckIfNoHTTPRules(t *testing.T) {
   133  	f := testutils.ParseProto3String(t, `
   134  			import "google/api/annotations.proto";
   135  			import "google/api/resource.proto";
   136  			service Library {
   137  				rpc GetBook(GetBookRequest) returns (Book) {}
   138  			}
   139  			message GetBookRequest {
   140  				string name = 1 [(google.api.resource_reference).type = "library.googleapis.com/Book"];
   141  			}
   142  			message Book {
   143  				option (google.api.resource) = {
   144  					type: "library.googleapis.com/Book"
   145  				};
   146  				string name = 1;
   147  			}
   148  		`)
   149  	if problems := httpTemplatePattern.Lint(f); len(problems) > 0 {
   150  		t.Errorf("%v", problems)
   151  	}
   152  }
   153  
   154  func TestHttpTemplatePattern_SkipCheckIfHTTPRuleHasNoVariables(t *testing.T) {
   155  	f := testutils.ParseProto3String(t, `
   156  			import "google/api/annotations.proto";
   157  			import "google/api/resource.proto";
   158  			service Library {
   159  				rpc GetBook(GetBookRequest) returns (Book) {
   160  					option (google.api.http) = {
   161  						get: "/v1/book"
   162  					};
   163  				}
   164  			}
   165  			message GetBookRequest {
   166  				string name = 1 [(google.api.resource_reference).type = "library.googleapis.com/Book"];
   167  			}
   168  			message Book {
   169  				option (google.api.resource) = {
   170  					type: "library.googleapis.com/Book"
   171  				};
   172  				string name = 1;
   173  			}
   174  		`)
   175  	if problems := httpTemplatePattern.Lint(f); len(problems) > 0 {
   176  		t.Errorf("%v", problems)
   177  	}
   178  }
   179  
   180  func TestHttpTemplatePattern_SkipCheckIfFieldPathMissingResourceAnnotation(t *testing.T) {
   181  	f := testutils.ParseProto3String(t, `
   182  			import "google/api/annotations.proto";
   183  			import "google/api/resource.proto";
   184  			service Library {
   185  				rpc GetBook(GetBookRequest) returns (Book) {
   186  					option (google.api.http) = {
   187  						get: "/v1/{name=shelves}"
   188  					};
   189  				}
   190  			}
   191  			message GetBookRequest {
   192  				string name = 1;
   193  			}
   194  			message Book {
   195  				option (google.api.resource) = {
   196  					type: "library.googleapis.com/Book"
   197  				};
   198  				string name = 1;
   199  			}
   200  		`)
   201  	if problems := httpTemplatePattern.Lint(f); len(problems) > 0 {
   202  		t.Errorf("%v", problems)
   203  	}
   204  }