App Engine Python SDK version 1.7.7
[gae.git] / python / google / appengine / tools / devappserver2 / endpoints / api_config_manager_test.py
blob65e30b53b07431f8d8e68f48bdf75ffa511f3cc8
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Unit tests for the api_config_manager module."""
21 import json
22 import re
23 import unittest
25 from google.appengine.tools.devappserver2.endpoints import api_config_manager
28 class ApiConfigManagerTest(unittest.TestCase):
30 def setUp(self):
31 """Make ApiConfigManager with a few helpful fakes."""
32 self.config_manager = api_config_manager.ApiConfigManager()
34 def test_parse_api_config_empty_response(self):
35 self.config_manager.parse_api_config_response('')
36 actual_method = self.config_manager.lookup_rpc_method('guestbook_api.get',
37 'v1')
38 self.assertEqual(None, actual_method)
40 def test_parse_api_config_invalid_response(self):
41 self.config_manager.parse_api_config_response('{"name": "foo"}')
42 actual_method = self.config_manager.lookup_rpc_method('guestbook_api.get',
43 'v1')
44 self.assertEqual(None, actual_method)
46 def test_parse_api_config(self):
47 fake_method = {'httpMethod': 'GET',
48 'path': 'greetings/{gid}',
49 'rosyMethod': 'baz.bim'}
50 config = json.dumps({'name': 'guestbook_api',
51 'version': 'X',
52 'methods': {'guestbook_api.foo.bar': fake_method}})
53 self.config_manager.parse_api_config_response(
54 json.dumps({'items': [config]}))
55 actual_method = self.config_manager.lookup_rpc_method(
56 'guestbook_api.foo.bar', 'X')
57 self.assertEqual(fake_method, actual_method)
59 def test_parse_api_config_order_length(self):
60 test_method_info = (
61 ('guestbook_api.foo.bar', 'greetings/{gid}', 'baz.bim'),
62 ('guestbook_api.list', 'greetings', 'greetings.list'),
63 ('guestbook_api.f3', 'greetings/{gid}/sender/property/blah',
64 'greetings.f3'),
65 ('guestbook_api.shortgreet', 'greet', 'greetings.short_greeting'))
66 methods = {}
67 for method_name, path, rosy_method in test_method_info:
68 method = {'httpMethod': 'GET',
69 'path': path,
70 'rosyMethod': rosy_method}
71 methods[method_name] = method
72 config = json.dumps({'name': 'guestbook_api',
73 'version': 'X',
74 'methods': methods})
75 self.config_manager.parse_api_config_response(
76 json.dumps({'items': [config]}))
77 # Make sure all methods appear in the result.
78 for method_name, _, _ in test_method_info:
79 self.assertIsNotNone(
80 self.config_manager.lookup_rpc_method(method_name, 'X'))
81 # Make sure paths and partial paths return the right methods.
82 self.assertEqual(
83 self.config_manager.lookup_rest_method(
84 'guestbook_api/X/greetings', 'GET')[0],
85 'guestbook_api.list')
86 self.assertEqual(
87 self.config_manager.lookup_rest_method(
88 'guestbook_api/X/greetings/1', 'GET')[0],
89 'guestbook_api.foo.bar')
90 self.assertEqual(
91 self.config_manager.lookup_rest_method(
92 'guestbook_api/X/greetings/2/sender/property/blah', 'GET')[0],
93 'guestbook_api.f3')
94 self.assertEqual(
95 self.config_manager.lookup_rest_method(
96 'guestbook_api/X/greet', 'GET')[0],
97 'guestbook_api.shortgreet')
99 def test_get_sorted_methods1(self):
100 test_method_info = (
101 ('name1', 'greetings', 'POST'),
102 ('name2', 'greetings', 'GET'),
103 ('name3', 'short/but/many/constants', 'GET'),
104 ('name4', 'greetings', ''),
105 ('name5', 'greetings/{gid}', 'GET'),
106 ('name6', 'greetings/{gid}', 'PUT'),
107 ('name7', 'a/b/{var}/{var2}', 'GET'))
108 methods = {}
109 for method_name, path, http_method in test_method_info:
110 method = {'httpMethod': http_method,
111 'path': path}
112 methods[method_name] = method
113 sorted_methods = self.config_manager._get_sorted_methods(methods)
115 expected_data = [
116 ('name3', 'short/but/many/constants', 'GET'),
117 ('name7', 'a/b/{var}/{var2}', 'GET'),
118 ('name4', 'greetings', ''),
119 ('name2', 'greetings', 'GET'),
120 ('name1', 'greetings', 'POST'),
121 ('name5', 'greetings/{gid}', 'GET'),
122 ('name6', 'greetings/{gid}', 'PUT')]
123 expected_methods = [(name, {'httpMethod': http_method, 'path': path})
124 for name, path, http_method in expected_data]
125 self.assertEqual(expected_methods, sorted_methods)
127 def test_get_sorted_methods2(self):
128 test_method_info = (
129 ('name1', 'abcdefghi', 'GET'),
130 ('name2', 'foo', 'GET'),
131 ('name3', 'greetings', 'GET'),
132 ('name4', 'bar', 'POST'),
133 ('name5', 'baz', 'GET'),
134 ('name6', 'baz', 'PUT'),
135 ('name7', 'baz', 'DELETE'))
136 methods = {}
137 for method_name, path, http_method in test_method_info:
138 method = {'httpMethod': http_method,
139 'path': path}
140 methods[method_name] = method
141 sorted_methods = self.config_manager._get_sorted_methods(methods)
143 # Single-part paths should be sorted by path name, http_method.
144 expected_data = [
145 ('name1', 'abcdefghi', 'GET'),
146 ('name4', 'bar', 'POST'),
147 ('name7', 'baz', 'DELETE'),
148 ('name5', 'baz', 'GET'),
149 ('name6', 'baz', 'PUT'),
150 ('name2', 'foo', 'GET'),
151 ('name3', 'greetings', 'GET')]
152 expected_methods = [(name, {'httpMethod': http_method, 'path': path})
153 for name, path, http_method in expected_data]
154 self.assertEqual(expected_methods, sorted_methods)
156 def test_parse_api_config_invalid_api_config(self):
157 fake_method = {'httpMethod': 'GET',
158 'path': 'greetings/{gid}',
159 'rosyMethod': 'baz.bim'}
160 config = json.dumps({'name': 'guestbook_api',
161 'version': 'X',
162 'methods': {'guestbook_api.foo.bar': fake_method}})
163 # Invalid Json
164 config2 = '{'
165 self.config_manager.parse_api_config_response(
166 json.dumps({'items': [config, config2]}))
167 actual_method = self.config_manager.lookup_rpc_method(
168 'guestbook_api.foo.bar', 'X')
169 self.assertEqual(fake_method, actual_method)
171 def test_save_lookup_rpc_method(self):
172 # First attempt, guestbook.get does not exist
173 actual_method = self.config_manager.lookup_rpc_method('guestbook_api.get',
174 'v1')
175 self.assertEqual(None, actual_method)
177 # Now we manually save it, and should find it
178 fake_method = {'some': 'object'}
179 self.config_manager._save_rpc_method('guestbook_api.get', 'v1', fake_method)
180 actual_method = self.config_manager.lookup_rpc_method('guestbook_api.get',
181 'v1')
182 self.assertEqual(fake_method, actual_method)
184 def test_save_lookup_rest_method(self):
185 # First attempt, guestbook.get does not exist
186 method_spec = self.config_manager.lookup_rest_method(
187 'guestbook_api/v1/greetings/i', 'GET')
188 self.assertEqual((None, None, None), method_spec)
190 # Now we manually save it, and should find it
191 fake_method = {'httpMethod': 'GET',
192 'path': 'greetings/{id}'}
193 self.config_manager._save_rest_method('guestbook_api.get', 'v1',
194 fake_method)
195 method_name, method_spec, params = self.config_manager.lookup_rest_method(
196 'guestbook_api/v1/greetings/i', 'GET')
197 self.assertEqual('guestbook_api.get', method_name)
198 self.assertEqual(fake_method, method_spec)
199 self.assertEqual({'id': 'i'}, params)
201 def test_trailing_slash_optional(self):
202 # Create a typical get resource URL.
203 fake_method = {'httpMethod': 'GET', 'path': 'trailingslash'}
204 self.config_manager._save_rest_method('guestbook_api.trailingslash', 'v1',
205 fake_method)
207 # Make sure we get this method when we query without a slash.
208 method_name, method_spec, params = self.config_manager.lookup_rest_method(
209 'guestbook_api/v1/trailingslash', 'GET')
210 self.assertEqual('guestbook_api.trailingslash', method_name)
211 self.assertEqual(fake_method, method_spec)
212 self.assertEqual({}, params)
214 # Make sure we get this method when we query with a slash.
215 method_name, method_spec, params = self.config_manager.lookup_rest_method(
216 'guestbook_api/v1/trailingslash/', 'GET')
217 self.assertEqual('guestbook_api.trailingslash', method_name)
218 self.assertEqual(fake_method, method_spec)
219 self.assertEqual({}, params)
222 class ParameterizedPathTest(unittest.TestCase):
225 def test_invalid_variable_name_leading_digit(self):
226 self.assertEqual(
227 None, re.match(api_config_manager._PATH_VARIABLE_PATTERN, '1abc'))
229 # Ensure users can not add variables starting with !
230 # This is used for reserved variables (e.g. !name and !version)
231 def test_invalid_var_name_leading_exclamation(self):
232 self.assertEqual(
233 None, re.match(api_config_manager._PATH_VARIABLE_PATTERN, '!abc'))
235 def test_valid_variable_name(self):
236 self.assertEqual(
237 'AbC1', re.match(api_config_manager._PATH_VARIABLE_PATTERN,
238 'AbC1').group(0))
240 def assert_no_match(self, path, param_path):
241 """Assert that the given path does not match param_path pattern.
243 For example, /xyz/123 does not match /abc/{x}.
245 Args:
246 path: A string, the inbound request path.
247 param_path: A string, the parameterized path pattern to match against
248 this path.
250 config_manager = api_config_manager.ApiConfigManager
251 params = config_manager._compile_path_pattern(param_path).match(path)
252 self.assertEqual(None, params)
254 def test_prefix_no_match(self):
255 self.assert_no_match('/xyz/123', '/abc/{x}')
257 def test_suffix_no_match(self):
258 self.assert_no_match('/abc/123', '/abc/{x}/456')
260 def test_suffix_no_match_with_more_variables(self):
261 self.assert_no_match('/abc/456/123/789', '/abc/{x}/123/{y}/xyz')
263 def test_no_match_collection_with_item(self):
264 self.assert_no_match('/api/v1/resources/123', '/{name}/{version}/resources')
266 def assert_match(self, path, param_path, param_count):
267 """Assert that the given path does match param_path pattern.
269 For example, /abc/123 does not match /abc/{x}.
271 Args:
272 path: A string, the inbound request path.
273 param_path: A string, the parameterized path pattern to match against
274 this path.
275 param_count: An int, the expected number of parameters to match in
276 pattern.
278 Returns:
279 Dict mapping path variable name to path variable value.
281 config_manager = api_config_manager.ApiConfigManager
282 match = config_manager._compile_path_pattern(param_path).match(path)
283 self.assertTrue(match is not None) # Will be None if path was not matched
284 params = match.groupdict()
285 self.assertEquals(param_count, len(params))
286 return params
288 def test_one_variable_match(self):
289 params = self.assert_match('/abc/123', '/abc/{x}', 1)
290 self.assertEquals('123', params.get('x'))
292 def test_two_variable_match(self):
293 params = self.assert_match('/abc/456/123/789', '/abc/{x}/123/{y}', 2)
294 self.assertEquals('456', params.get('x'))
295 self.assertEquals('789', params.get('y'))
297 def assert_invalid_value(self, value):
298 """Assert that the path parameter value is not valid.
300 For example, /abc/3!:2 is invalid for /abc/{x}.
302 Args:
303 value: A string containing a variable value to check for validity.
305 param_path = '/abc/{x}'
306 path = '/abc/%s' % value
307 config_manager = api_config_manager.ApiConfigManager
308 params = config_manager._compile_path_pattern(param_path).match(path)
309 self.assertEqual(None, params)
311 def test_invalid_values(self):
312 for reserved in [':', '?', '#', '[', ']', '{', '}']:
313 self.assert_invalid_value('123%s' % reserved)
315 if __name__ == '__main__':
316 unittest.main()