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 }