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."""
25 from google
.appengine
.tools
.devappserver2
.endpoints
import api_config_manager
28 class ApiConfigManagerTest(unittest
.TestCase
):
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',
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',
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',
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
):
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',
65 ('guestbook_api.shortgreet', 'greet', 'greetings.short_greeting'))
67 for method_name
, path
, rosy_method
in test_method_info
:
68 method
= {'httpMethod': 'GET',
70 'rosyMethod': rosy_method
}
71 methods
[method_name
] = method
72 config
= json
.dumps({'name': 'guestbook_api',
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
:
80 self
.config_manager
.lookup_rpc_method(method_name
, 'X'))
81 # Make sure paths and partial paths return the right methods.
83 self
.config_manager
.lookup_rest_method(
84 'guestbook_api/X/greetings', 'GET')[0],
87 self
.config_manager
.lookup_rest_method(
88 'guestbook_api/X/greetings/1', 'GET')[0],
89 'guestbook_api.foo.bar')
91 self
.config_manager
.lookup_rest_method(
92 'guestbook_api/X/greetings/2/sender/property/blah', 'GET')[0],
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
):
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'))
109 for method_name
, path
, http_method
in test_method_info
:
110 method
= {'httpMethod': http_method
,
112 methods
[method_name
] = method
113 sorted_methods
= self
.config_manager
._get
_sorted
_methods
(methods
)
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
):
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'))
137 for method_name
, path
, http_method
in test_method_info
:
138 method
= {'httpMethod': http_method
,
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.
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',
162 'methods': {'guestbook_api.foo.bar': fake_method
}})
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',
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',
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',
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',
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
):
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
):
233 None, re
.match(api_config_manager
._PATH
_VARIABLE
_PATTERN
, '!abc'))
235 def test_valid_variable_name(self
):
237 'AbC1', re
.match(api_config_manager
._PATH
_VARIABLE
_PATTERN
,
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}.
246 path: A string, the inbound request path.
247 param_path: A string, the parameterized path pattern to match against
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}.
272 path: A string, the inbound request path.
273 param_path: A string, the parameterized path pattern to match against
275 param_count: An int, the expected number of parameters to match in
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
))
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}.
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__':