OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 Google Inc. All Rights Reserved. |
| 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 |
| 15 """Unit tests for the api_config_manager module.""" |
| 16 |
| 17 import re |
| 18 import unittest |
| 19 |
| 20 import endpoints.api_config_manager as api_config_manager |
| 21 |
| 22 |
| 23 class ApiConfigManagerTest(unittest.TestCase): |
| 24 |
| 25 def setUp(self): |
| 26 """Make ApiConfigManager with a few helpful fakes.""" |
| 27 self.config_manager = api_config_manager.ApiConfigManager() |
| 28 |
| 29 def test_process_api_config_empty_response(self): |
| 30 self.config_manager.process_api_config_response({}) |
| 31 actual_method = self.config_manager.lookup_rpc_method('guestbook_api.get', |
| 32 'v1') |
| 33 self.assertEqual(None, actual_method) |
| 34 |
| 35 def test_process_api_config_invalid_response(self): |
| 36 self.config_manager.process_api_config_response({'name': 'foo'}) |
| 37 actual_method = self.config_manager.lookup_rpc_method('guestbook_api.get', |
| 38 'v1') |
| 39 self.assertEqual(None, actual_method) |
| 40 |
| 41 def test_process_api_config(self): |
| 42 fake_method = {'httpMethod': 'GET', |
| 43 'path': 'greetings/{gid}', |
| 44 'rosyMethod': 'baz.bim'} |
| 45 config = {'name': 'guestbook_api', |
| 46 'version': 'X', |
| 47 'methods': {'guestbook_api.foo.bar': fake_method}} |
| 48 self.config_manager.process_api_config_response({'items': [config]}) |
| 49 actual_method = self.config_manager.lookup_rpc_method( |
| 50 'guestbook_api.foo.bar', 'X') |
| 51 self.assertEqual(fake_method, actual_method) |
| 52 |
| 53 def test_process_api_config_order_length(self): |
| 54 test_method_info = ( |
| 55 ('guestbook_api.foo.bar', 'greetings/{gid}', 'baz.bim'), |
| 56 ('guestbook_api.list', 'greetings', 'greetings.list'), |
| 57 ('guestbook_api.f3', 'greetings/{gid}/sender/property/blah', |
| 58 'greetings.f3'), |
| 59 ('guestbook_api.shortgreet', 'greet', 'greetings.short_greeting')) |
| 60 methods = {} |
| 61 for method_name, path, rosy_method in test_method_info: |
| 62 method = {'httpMethod': 'GET', |
| 63 'path': path, |
| 64 'rosyMethod': rosy_method} |
| 65 methods[method_name] = method |
| 66 config = {'name': 'guestbook_api', |
| 67 'version': 'X', |
| 68 'methods': methods} |
| 69 self.config_manager.process_api_config_response( |
| 70 {'items': [config]}) |
| 71 # Make sure all methods appear in the result. |
| 72 for method_name, _, _ in test_method_info: |
| 73 self.assertIsNotNone( |
| 74 self.config_manager.lookup_rpc_method(method_name, 'X')) |
| 75 # Make sure paths and partial paths return the right methods. |
| 76 self.assertEqual( |
| 77 self.config_manager.lookup_rest_method( |
| 78 'guestbook_api/X/greetings', 'GET')[0], |
| 79 'guestbook_api.list') |
| 80 self.assertEqual( |
| 81 self.config_manager.lookup_rest_method( |
| 82 'guestbook_api/X/greetings/1', 'GET')[0], |
| 83 'guestbook_api.foo.bar') |
| 84 self.assertEqual( |
| 85 self.config_manager.lookup_rest_method( |
| 86 'guestbook_api/X/greetings/2/sender/property/blah', 'GET')[0], |
| 87 'guestbook_api.f3') |
| 88 self.assertEqual( |
| 89 self.config_manager.lookup_rest_method( |
| 90 'guestbook_api/X/greet', 'GET')[0], |
| 91 'guestbook_api.shortgreet') |
| 92 |
| 93 def test_get_sorted_methods1(self): |
| 94 test_method_info = ( |
| 95 ('name1', 'greetings', 'POST'), |
| 96 ('name2', 'greetings', 'GET'), |
| 97 ('name3', 'short/but/many/constants', 'GET'), |
| 98 ('name4', 'greetings', ''), |
| 99 ('name5', 'greetings/{gid}', 'GET'), |
| 100 ('name6', 'greetings/{gid}', 'PUT'), |
| 101 ('name7', 'a/b/{var}/{var2}', 'GET')) |
| 102 methods = {} |
| 103 for method_name, path, http_method in test_method_info: |
| 104 method = {'httpMethod': http_method, |
| 105 'path': path} |
| 106 methods[method_name] = method |
| 107 sorted_methods = self.config_manager._get_sorted_methods(methods) |
| 108 |
| 109 expected_data = [ |
| 110 ('name3', 'short/but/many/constants', 'GET'), |
| 111 ('name7', 'a/b/{var}/{var2}', 'GET'), |
| 112 ('name4', 'greetings', ''), |
| 113 ('name2', 'greetings', 'GET'), |
| 114 ('name1', 'greetings', 'POST'), |
| 115 ('name5', 'greetings/{gid}', 'GET'), |
| 116 ('name6', 'greetings/{gid}', 'PUT')] |
| 117 expected_methods = [(name, {'httpMethod': http_method, 'path': path}) |
| 118 for name, path, http_method in expected_data] |
| 119 self.assertEqual(expected_methods, sorted_methods) |
| 120 |
| 121 def test_get_sorted_methods2(self): |
| 122 test_method_info = ( |
| 123 ('name1', 'abcdefghi', 'GET'), |
| 124 ('name2', 'foo', 'GET'), |
| 125 ('name3', 'greetings', 'GET'), |
| 126 ('name4', 'bar', 'POST'), |
| 127 ('name5', 'baz', 'GET'), |
| 128 ('name6', 'baz', 'PUT'), |
| 129 ('name7', 'baz', 'DELETE')) |
| 130 methods = {} |
| 131 for method_name, path, http_method in test_method_info: |
| 132 method = {'httpMethod': http_method, |
| 133 'path': path} |
| 134 methods[method_name] = method |
| 135 sorted_methods = self.config_manager._get_sorted_methods(methods) |
| 136 |
| 137 # Single-part paths should be sorted by path name, http_method. |
| 138 expected_data = [ |
| 139 ('name1', 'abcdefghi', 'GET'), |
| 140 ('name4', 'bar', 'POST'), |
| 141 ('name7', 'baz', 'DELETE'), |
| 142 ('name5', 'baz', 'GET'), |
| 143 ('name6', 'baz', 'PUT'), |
| 144 ('name2', 'foo', 'GET'), |
| 145 ('name3', 'greetings', 'GET')] |
| 146 expected_methods = [(name, {'httpMethod': http_method, 'path': path}) |
| 147 for name, path, http_method in expected_data] |
| 148 self.assertEqual(expected_methods, sorted_methods) |
| 149 |
| 150 def test_process_api_config_convert_https(self): |
| 151 """Test that the parsed API config has switched HTTPS to HTTP.""" |
| 152 config = {'name': 'guestbook_api', |
| 153 'version': 'X', |
| 154 'adapter': {'bns': 'https://localhost/_ah/spi', |
| 155 'type': 'lily'}, |
| 156 'root': 'https://localhost/_ah/api', |
| 157 'methods': {}} |
| 158 self.config_manager.process_api_config_response({'items': [config]}) |
| 159 |
| 160 self.assertEqual( |
| 161 'https://localhost/_ah/spi', |
| 162 self.config_manager.configs[('guestbook_api', 'X')]['adapter']['bns']) |
| 163 self.assertEqual( |
| 164 'https://localhost/_ah/api', |
| 165 self.config_manager.configs[('guestbook_api', 'X')]['root']) |
| 166 |
| 167 def test_save_lookup_rpc_method(self): |
| 168 # First attempt, guestbook.get does not exist |
| 169 actual_method = self.config_manager.lookup_rpc_method('guestbook_api.get', |
| 170 'v1') |
| 171 self.assertEqual(None, actual_method) |
| 172 |
| 173 # Now we manually save it, and should find it |
| 174 fake_method = {'some': 'object'} |
| 175 self.config_manager._save_rpc_method('guestbook_api.get', 'v1', fake_method) |
| 176 actual_method = self.config_manager.lookup_rpc_method('guestbook_api.get', |
| 177 'v1') |
| 178 self.assertEqual(fake_method, actual_method) |
| 179 |
| 180 def test_save_lookup_rest_method(self): |
| 181 # First attempt, guestbook.get does not exist |
| 182 method_spec = self.config_manager.lookup_rest_method( |
| 183 'guestbook_api/v1/greetings/i', 'GET') |
| 184 self.assertEqual((None, None, None), method_spec) |
| 185 |
| 186 # Now we manually save it, and should find it |
| 187 fake_method = {'httpMethod': 'GET', |
| 188 'path': 'greetings/{id}'} |
| 189 self.config_manager._save_rest_method('guestbook_api.get', 'guestbook_api', |
| 190 'v1', fake_method) |
| 191 method_name, method_spec, params = self.config_manager.lookup_rest_method( |
| 192 'guestbook_api/v1/greetings/i', 'GET') |
| 193 self.assertEqual('guestbook_api.get', method_name) |
| 194 self.assertEqual(fake_method, method_spec) |
| 195 self.assertEqual({'id': 'i'}, params) |
| 196 |
| 197 def test_lookup_rest_method_with_colon(self): |
| 198 fake_method = {'httpMethod': 'GET', |
| 199 'path': 'greetings:testcolon'} |
| 200 self.config_manager._save_rest_method('guestbook_api.get', 'guestbook_api', |
| 201 'v1', fake_method) |
| 202 method_name, method_spec, params = self.config_manager.lookup_rest_method( |
| 203 'guestbook_api/v1/greetings%3Atestcolon', 'GET') |
| 204 self.assertEqual('guestbook_api.get', method_name) |
| 205 self.assertEqual(fake_method, method_spec) |
| 206 |
| 207 def test_trailing_slash_optional(self): |
| 208 # Create a typical get resource URL. |
| 209 fake_method = {'httpMethod': 'GET', 'path': 'trailingslash'} |
| 210 self.config_manager._save_rest_method('guestbook_api.trailingslash', |
| 211 'guestbook_api', 'v1', fake_method) |
| 212 |
| 213 # Make sure we get this method when we query without a slash. |
| 214 method_name, method_spec, params = self.config_manager.lookup_rest_method( |
| 215 'guestbook_api/v1/trailingslash', 'GET') |
| 216 self.assertEqual('guestbook_api.trailingslash', method_name) |
| 217 self.assertEqual(fake_method, method_spec) |
| 218 self.assertEqual({}, params) |
| 219 |
| 220 # Make sure we get this method when we query with a slash. |
| 221 method_name, method_spec, params = self.config_manager.lookup_rest_method( |
| 222 'guestbook_api/v1/trailingslash/', 'GET') |
| 223 self.assertEqual('guestbook_api.trailingslash', method_name) |
| 224 self.assertEqual(fake_method, method_spec) |
| 225 self.assertEqual({}, params) |
| 226 |
| 227 |
| 228 class ParameterizedPathTest(unittest.TestCase): |
| 229 |
| 230 # <scrub> |
| 231 # See http://cs/spi/tools/devserver/ParameterizedPathTest.java |
| 232 # </scrub> |
| 233 def test_invalid_variable_name_leading_digit(self): |
| 234 self.assertEqual( |
| 235 None, re.match(api_config_manager._PATH_VARIABLE_PATTERN, '1abc')) |
| 236 |
| 237 # Ensure users can not add variables starting with ! |
| 238 # This is used for reserved variables (e.g. !name and !version) |
| 239 def test_invalid_var_name_leading_exclamation(self): |
| 240 self.assertEqual( |
| 241 None, re.match(api_config_manager._PATH_VARIABLE_PATTERN, '!abc')) |
| 242 |
| 243 def test_valid_variable_name(self): |
| 244 self.assertEqual( |
| 245 'AbC1', re.match(api_config_manager._PATH_VARIABLE_PATTERN, |
| 246 'AbC1').group(0)) |
| 247 |
| 248 def assert_no_match(self, path, param_path): |
| 249 """Assert that the given path does not match param_path pattern. |
| 250 |
| 251 For example, /xyz/123 does not match /abc/{x}. |
| 252 |
| 253 Args: |
| 254 path: A string, the inbound request path. |
| 255 param_path: A string, the parameterized path pattern to match against |
| 256 this path. |
| 257 """ |
| 258 config_manager = api_config_manager.ApiConfigManager |
| 259 params = config_manager._compile_path_pattern(param_path).match(path) |
| 260 self.assertEqual(None, params) |
| 261 |
| 262 def test_prefix_no_match(self): |
| 263 self.assert_no_match('/xyz/123', '/abc/{x}') |
| 264 |
| 265 def test_suffix_no_match(self): |
| 266 self.assert_no_match('/abc/123', '/abc/{x}/456') |
| 267 |
| 268 def test_suffix_no_match_with_more_variables(self): |
| 269 self.assert_no_match('/abc/456/123/789', '/abc/{x}/123/{y}/xyz') |
| 270 |
| 271 def test_no_match_collection_with_item(self): |
| 272 self.assert_no_match('/api/v1/resources/123', '/{name}/{version}/resources') |
| 273 |
| 274 def assert_match(self, path, param_path, param_count): |
| 275 """Assert that the given path does match param_path pattern. |
| 276 |
| 277 For example, /abc/123 does not match /abc/{x}. |
| 278 |
| 279 Args: |
| 280 path: A string, the inbound request path. |
| 281 param_path: A string, the parameterized path pattern to match against |
| 282 this path. |
| 283 param_count: An int, the expected number of parameters to match in |
| 284 pattern. |
| 285 |
| 286 Returns: |
| 287 Dict mapping path variable name to path variable value. |
| 288 """ |
| 289 config_manager = api_config_manager.ApiConfigManager |
| 290 match = config_manager._compile_path_pattern(param_path).match(path) |
| 291 self.assertTrue(match is not None) # Will be None if path was not matched |
| 292 params = config_manager._get_path_params(match) |
| 293 self.assertEquals(param_count, len(params)) |
| 294 return params |
| 295 |
| 296 def test_one_variable_match(self): |
| 297 params = self.assert_match('/abc/123', '/abc/{x}', 1) |
| 298 self.assertEquals('123', params.get('x')) |
| 299 |
| 300 def test_two_variable_match(self): |
| 301 params = self.assert_match('/abc/456/123/789', '/abc/{x}/123/{y}', 2) |
| 302 self.assertEquals('456', params.get('x')) |
| 303 self.assertEquals('789', params.get('y')) |
| 304 |
| 305 def test_message_variable_match(self): |
| 306 params = self.assert_match('/abc/123', '/abc/{x.y}', 1) |
| 307 self.assertEquals('123', params.get('x.y')) |
| 308 |
| 309 def test_message_and_simple_variable_match(self): |
| 310 params = self.assert_match('/abc/123/456', '/abc/{x.y.z}/{t}', 2) |
| 311 self.assertEquals('123', params.get('x.y.z')) |
| 312 self.assertEquals('456', params.get('t')) |
| 313 |
| 314 def test_space_in_path(self): |
| 315 params = self.assert_match('/abc/foo+bar', '/abc/{x}', 1) |
| 316 self.assertEquals('foo bar', params.get('x')) |
| 317 |
| 318 def assert_invalid_value(self, value): |
| 319 """Assert that the path parameter value is not valid. |
| 320 |
| 321 For example, /abc/3!:2 is invalid for /abc/{x}. |
| 322 |
| 323 Args: |
| 324 value: A string containing a variable value to check for validity. |
| 325 """ |
| 326 param_path = '/abc/{x}' |
| 327 path = '/abc/%s' % value |
| 328 config_manager = api_config_manager.ApiConfigManager |
| 329 params = config_manager._compile_path_pattern(param_path).match(path) |
| 330 self.assertEqual(None, params) |
| 331 |
| 332 def test_invalid_values(self): |
| 333 for reserved in [':', '?', '#', '[', ']', '{', '}']: |
| 334 self.assert_invalid_value('123%s' % reserved) |
| 335 |
| 336 |
| 337 if __name__ == '__main__': |
| 338 unittest.main() |
OLD | NEW |