1# Copyright 2017 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# http://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 15from __future__ import unicode_literals 16 17import mock 18import pytest 19 20from google.api_core import path_template 21 22 23@pytest.mark.parametrize( 24 "tmpl, args, kwargs, expected_result", 25 [ 26 # Basic positional params 27 ["/v1/*", ["a"], {}, "/v1/a"], 28 ["/v1/**", ["a/b"], {}, "/v1/a/b"], 29 ["/v1/*/*", ["a", "b"], {}, "/v1/a/b"], 30 ["/v1/*/*/**", ["a", "b", "c/d"], {}, "/v1/a/b/c/d"], 31 # Basic named params 32 ["/v1/{name}", [], {"name": "parent"}, "/v1/parent"], 33 ["/v1/{name=**}", [], {"name": "parent/child"}, "/v1/parent/child"], 34 # Named params with a sub-template 35 ["/v1/{name=parent/*}", [], {"name": "parent/child"}, "/v1/parent/child"], 36 [ 37 "/v1/{name=parent/**}", 38 [], 39 {"name": "parent/child/object"}, 40 "/v1/parent/child/object", 41 ], 42 # Combining positional and named params 43 ["/v1/*/{name}", ["a"], {"name": "parent"}, "/v1/a/parent"], 44 ["/v1/{name}/*", ["a"], {"name": "parent"}, "/v1/parent/a"], 45 [ 46 "/v1/{parent}/*/{child}/*", 47 ["a", "b"], 48 {"parent": "thor", "child": "thorson"}, 49 "/v1/thor/a/thorson/b", 50 ], 51 ["/v1/{name}/**", ["a/b"], {"name": "parent"}, "/v1/parent/a/b"], 52 # Combining positional and named params with sub-templates. 53 [ 54 "/v1/{name=parent/*}/*", 55 ["a"], 56 {"name": "parent/child"}, 57 "/v1/parent/child/a", 58 ], 59 [ 60 "/v1/*/{name=parent/**}", 61 ["a"], 62 {"name": "parent/child/object"}, 63 "/v1/a/parent/child/object", 64 ], 65 ], 66) 67def test_expand_success(tmpl, args, kwargs, expected_result): 68 result = path_template.expand(tmpl, *args, **kwargs) 69 assert result == expected_result 70 assert path_template.validate(tmpl, result) 71 72 73@pytest.mark.parametrize( 74 "tmpl, args, kwargs, exc_match", 75 [ 76 # Missing positional arg. 77 ["v1/*", [], {}, "Positional"], 78 # Missing named arg. 79 ["v1/{name}", [], {}, "Named"], 80 ], 81) 82def test_expanded_failure(tmpl, args, kwargs, exc_match): 83 with pytest.raises(ValueError, match=exc_match): 84 path_template.expand(tmpl, *args, **kwargs) 85 86 87@pytest.mark.parametrize( 88 "request_obj, field, expected_result", 89 [ 90 [{"field": "stringValue"}, "field", "stringValue"], 91 [{"field": "stringValue"}, "nosuchfield", None], 92 [{"field": "stringValue"}, "field.subfield", None], 93 [{"field": {"subfield": "stringValue"}}, "field", None], 94 [{"field": {"subfield": "stringValue"}}, "field.subfield", "stringValue"], 95 [{"field": {"subfield": [1, 2, 3]}}, "field.subfield", [1, 2, 3]], 96 [{"field": {"subfield": "stringValue"}}, "field", None], 97 [{"field": {"subfield": "stringValue"}}, "field.nosuchfield", None], 98 [ 99 {"field": {"subfield": {"subsubfield": "stringValue"}}}, 100 "field.subfield.subsubfield", 101 "stringValue", 102 ], 103 ["string", "field", None], 104 ], 105) 106def test_get_field(request_obj, field, expected_result): 107 result = path_template.get_field(request_obj, field) 108 assert result == expected_result 109 110 111@pytest.mark.parametrize( 112 "request_obj, field, expected_result", 113 [ 114 [{"field": "stringValue"}, "field", {}], 115 [{"field": "stringValue"}, "nosuchfield", {"field": "stringValue"}], 116 [{"field": "stringValue"}, "field.subfield", {"field": "stringValue"}], 117 [{"field": {"subfield": "stringValue"}}, "field.subfield", {"field": {}}], 118 [ 119 {"field": {"subfield": "stringValue", "q": "w"}, "e": "f"}, 120 "field.subfield", 121 {"field": {"q": "w"}, "e": "f"}, 122 ], 123 [ 124 {"field": {"subfield": "stringValue"}}, 125 "field.nosuchfield", 126 {"field": {"subfield": "stringValue"}}, 127 ], 128 [ 129 {"field": {"subfield": {"subsubfield": "stringValue", "q": "w"}}}, 130 "field.subfield.subsubfield", 131 {"field": {"subfield": {"q": "w"}}}, 132 ], 133 ["string", "field", "string"], 134 ["string", "field.subfield", "string"], 135 ], 136) 137def test_delete_field(request_obj, field, expected_result): 138 path_template.delete_field(request_obj, field) 139 assert request_obj == expected_result 140 141 142@pytest.mark.parametrize( 143 "tmpl, path", 144 [ 145 # Single segment template, but multi segment value 146 ["v1/*", "v1/a/b"], 147 ["v1/*/*", "v1/a/b/c"], 148 # Single segement named template, but multi segment value 149 ["v1/{name}", "v1/a/b"], 150 ["v1/{name}/{value}", "v1/a/b/c"], 151 # Named value with a sub-template but invalid value 152 ["v1/{name=parent/*}", "v1/grandparent/child"], 153 ], 154) 155def test_validate_failure(tmpl, path): 156 assert not path_template.validate(tmpl, path) 157 158 159def test__expand_variable_match_unexpected(): 160 match = mock.Mock(spec=["group"]) 161 match.group.return_value = None 162 with pytest.raises(ValueError, match="Unknown"): 163 path_template._expand_variable_match([], {}, match) 164 165 166def test__replace_variable_with_pattern(): 167 match = mock.Mock(spec=["group"]) 168 match.group.return_value = None 169 with pytest.raises(ValueError, match="Unknown"): 170 path_template._replace_variable_with_pattern(match) 171 172 173@pytest.mark.parametrize( 174 "http_options, request_kwargs, expected_result", 175 [ 176 [ 177 [["get", "/v1/no/template", ""]], 178 {"foo": "bar"}, 179 ["get", "/v1/no/template", {}, {"foo": "bar"}], 180 ], 181 # Single templates 182 [ 183 [["get", "/v1/{field}", ""]], 184 {"field": "parent"}, 185 ["get", "/v1/parent", {}, {}], 186 ], 187 [ 188 [["get", "/v1/{field.sub}", ""]], 189 {"field": {"sub": "parent"}, "foo": "bar"}, 190 ["get", "/v1/parent", {}, {"field": {}, "foo": "bar"}], 191 ], 192 ], 193) 194def test_transcode_base_case(http_options, request_kwargs, expected_result): 195 http_options, expected_result = helper_test_transcode(http_options, expected_result) 196 result = path_template.transcode(http_options, **request_kwargs) 197 assert result == expected_result 198 199 200@pytest.mark.parametrize( 201 "http_options, request_kwargs, expected_result", 202 [ 203 [ 204 [["get", "/v1/{field.subfield}", ""]], 205 {"field": {"subfield": "parent"}, "foo": "bar"}, 206 ["get", "/v1/parent", {}, {"field": {}, "foo": "bar"}], 207 ], 208 [ 209 [["get", "/v1/{field.subfield.subsubfield}", ""]], 210 {"field": {"subfield": {"subsubfield": "parent"}}, "foo": "bar"}, 211 ["get", "/v1/parent", {}, {"field": {"subfield": {}}, "foo": "bar"}], 212 ], 213 [ 214 [["get", "/v1/{field.subfield1}/{field.subfield2}", ""]], 215 {"field": {"subfield1": "parent", "subfield2": "child"}, "foo": "bar"}, 216 ["get", "/v1/parent/child", {}, {"field": {}, "foo": "bar"}], 217 ], 218 ], 219) 220def test_transcode_subfields(http_options, request_kwargs, expected_result): 221 http_options, expected_result = helper_test_transcode(http_options, expected_result) 222 result = path_template.transcode(http_options, **request_kwargs) 223 assert result == expected_result 224 225 226@pytest.mark.parametrize( 227 "http_options, request_kwargs, expected_result", 228 [ 229 # Single segment wildcard 230 [ 231 [["get", "/v1/{field=*}", ""]], 232 {"field": "parent"}, 233 ["get", "/v1/parent", {}, {}], 234 ], 235 [ 236 [["get", "/v1/{field=a/*/b/*}", ""]], 237 {"field": "a/parent/b/child", "foo": "bar"}, 238 ["get", "/v1/a/parent/b/child", {}, {"foo": "bar"}], 239 ], 240 # Double segment wildcard 241 [ 242 [["get", "/v1/{field=**}", ""]], 243 {"field": "parent/p1"}, 244 ["get", "/v1/parent/p1", {}, {}], 245 ], 246 [ 247 [["get", "/v1/{field=a/**/b/**}", ""]], 248 {"field": "a/parent/p1/b/child/c1", "foo": "bar"}, 249 ["get", "/v1/a/parent/p1/b/child/c1", {}, {"foo": "bar"}], 250 ], 251 # Combined single and double segment wildcard 252 [ 253 [["get", "/v1/{field=a/*/b/**}", ""]], 254 {"field": "a/parent/b/child/c1"}, 255 ["get", "/v1/a/parent/b/child/c1", {}, {}], 256 ], 257 [ 258 [["get", "/v1/{field=a/**/b/*}/v2/{name}", ""]], 259 {"field": "a/parent/p1/b/child", "name": "first", "foo": "bar"}, 260 ["get", "/v1/a/parent/p1/b/child/v2/first", {}, {"foo": "bar"}], 261 ], 262 ], 263) 264def test_transcode_with_wildcard(http_options, request_kwargs, expected_result): 265 http_options, expected_result = helper_test_transcode(http_options, expected_result) 266 result = path_template.transcode(http_options, **request_kwargs) 267 assert result == expected_result 268 269 270@pytest.mark.parametrize( 271 "http_options, request_kwargs, expected_result", 272 [ 273 # Single field body 274 [ 275 [["post", "/v1/no/template", "data"]], 276 {"data": {"id": 1, "info": "some info"}, "foo": "bar"}, 277 ["post", "/v1/no/template", {"id": 1, "info": "some info"}, {"foo": "bar"}], 278 ], 279 [ 280 [["post", "/v1/{field=a/*}/b/{name=**}", "data"]], 281 { 282 "field": "a/parent", 283 "name": "first/last", 284 "data": {"id": 1, "info": "some info"}, 285 "foo": "bar", 286 }, 287 [ 288 "post", 289 "/v1/a/parent/b/first/last", 290 {"id": 1, "info": "some info"}, 291 {"foo": "bar"}, 292 ], 293 ], 294 # Wildcard body 295 [ 296 [["post", "/v1/{field=a/*}/b/{name=**}", "*"]], 297 { 298 "field": "a/parent", 299 "name": "first/last", 300 "data": {"id": 1, "info": "some info"}, 301 "foo": "bar", 302 }, 303 [ 304 "post", 305 "/v1/a/parent/b/first/last", 306 {"data": {"id": 1, "info": "some info"}, "foo": "bar"}, 307 {}, 308 ], 309 ], 310 ], 311) 312def test_transcode_with_body(http_options, request_kwargs, expected_result): 313 http_options, expected_result = helper_test_transcode(http_options, expected_result) 314 result = path_template.transcode(http_options, **request_kwargs) 315 assert result == expected_result 316 317 318@pytest.mark.parametrize( 319 "http_options, request_kwargs, expected_result", 320 [ 321 # Additional bindings 322 [ 323 [ 324 ["post", "/v1/{field=a/*}/b/{name=**}", "extra_data"], 325 ["post", "/v1/{field=a/*}/b/{name=**}", "*"], 326 ], 327 { 328 "field": "a/parent", 329 "name": "first/last", 330 "data": {"id": 1, "info": "some info"}, 331 "foo": "bar", 332 }, 333 [ 334 "post", 335 "/v1/a/parent/b/first/last", 336 {"data": {"id": 1, "info": "some info"}, "foo": "bar"}, 337 {}, 338 ], 339 ], 340 [ 341 [ 342 ["get", "/v1/{field=a/*}/b/{name=**}", ""], 343 ["get", "/v1/{field=a/*}/b/first/last", ""], 344 ], 345 {"field": "a/parent", "foo": "bar"}, 346 ["get", "/v1/a/parent/b/first/last", {}, {"foo": "bar"}], 347 ], 348 ], 349) 350def test_transcode_with_additional_bindings( 351 http_options, request_kwargs, expected_result 352): 353 http_options, expected_result = helper_test_transcode(http_options, expected_result) 354 result = path_template.transcode(http_options, **request_kwargs) 355 assert result == expected_result 356 357 358@pytest.mark.parametrize( 359 "http_options, request_kwargs", 360 [ 361 [[["get", "/v1/{name}", ""]], {"foo": "bar"}], 362 [[["get", "/v1/{name}", ""]], {"name": "first/last"}], 363 [[["get", "/v1/{name=mr/*/*}", ""]], {"name": "first/last"}], 364 [[["post", "/v1/{name}", "data"]], {"name": "first/last"}], 365 ], 366) 367def test_transcode_fails(http_options, request_kwargs): 368 http_options, _ = helper_test_transcode(http_options, range(4)) 369 with pytest.raises(ValueError): 370 path_template.transcode(http_options, **request_kwargs) 371 372 373def helper_test_transcode(http_options_list, expected_result_list): 374 http_options = [] 375 for opt_list in http_options_list: 376 http_option = {"method": opt_list[0], "uri": opt_list[1]} 377 if opt_list[2]: 378 http_option["body"] = opt_list[2] 379 http_options.append(http_option) 380 381 expected_result = { 382 "method": expected_result_list[0], 383 "uri": expected_result_list[1], 384 "query_params": expected_result_list[3], 385 } 386 if expected_result_list[2]: 387 expected_result["body"] = expected_result_list[2] 388 389 return (http_options, expected_result) 390