OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2015 The Swarming Authors. All rights reserved. | 2 # Copyright 2015 The Swarming Authors. All rights reserved. |
3 # Use of this source code is governed by the Apache v2.0 license that can be | 3 # Use of this source code is governed by the Apache v2.0 license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 import base64 | 6 import base64 |
7 import copy | 7 import copy |
8 import httplib | 8 import httplib |
9 | 9 |
10 from test_env import future | 10 from test_env import future |
(...skipping 13 matching lines...) Expand all Loading... |
24 import api | 24 import api |
25 import projects | 25 import projects |
26 import storage | 26 import storage |
27 | 27 |
28 | 28 |
29 class ApiTest(test_case.EndpointsTestCase): | 29 class ApiTest(test_case.EndpointsTestCase): |
30 api_service_cls = api.ConfigApi | 30 api_service_cls = api.ConfigApi |
31 | 31 |
32 def setUp(self): | 32 def setUp(self): |
33 super(ApiTest, self).setUp() | 33 super(ApiTest, self).setUp() |
34 self.mock(acl, 'has_project_access', mock.Mock(return_value=True)) | 34 self.mock(acl, 'has_project_access', mock.Mock()) |
35 self.mock(acl, 'can_read_config_set', mock.Mock(return_value=True)) | 35 acl.has_project_access.side_effect = ( |
36 self.mock(acl, 'can_read_project_config', mock.Mock(return_value=True)) | 36 lambda pid: pid != 'secret' |
| 37 ) |
| 38 self.mock(acl, 'has_service_access', mock.Mock(return_value=True)) |
37 self.mock(projects, 'get_projects', mock.Mock()) | 39 self.mock(projects, 'get_projects', mock.Mock()) |
38 projects.get_projects.return_value = [ | 40 projects.get_projects.return_value = [ |
39 service_config_pb2.Project(id='chromium'), | 41 service_config_pb2.Project(id='chromium'), |
40 service_config_pb2.Project(id='v8'), | 42 service_config_pb2.Project(id='v8'), |
41 ] | 43 ] |
42 self.mock(projects, 'get_metadata', mock.Mock()) | 44 self.mock(projects, 'get_metadata', mock.Mock()) |
43 projects.get_metadata.return_value = project_config_pb2.ProjectCfg() | 45 projects.get_metadata.return_value = project_config_pb2.ProjectCfg() |
44 self.mock(projects, 'get_repo', mock.Mock()) | 46 self.mock(projects, 'get_repo', mock.Mock()) |
45 projects.get_repo.return_value = ( | 47 projects.get_repo.return_value = ( |
46 projects.RepositoryType.GITILES, 'https://localhost/project') | 48 projects.RepositoryType.GITILES, 'https://localhost/project') |
(...skipping 30 matching lines...) Expand all Loading... |
77 self.assertEqual(resp, { | 79 self.assertEqual(resp, { |
78 'mappings': [ | 80 'mappings': [ |
79 { | 81 { |
80 'config_set': 'services/x', | 82 'config_set': 'services/x', |
81 'location': 'http://x', | 83 'location': 'http://x', |
82 }, | 84 }, |
83 ], | 85 ], |
84 }) | 86 }) |
85 | 87 |
86 def test_get_config_one_forbidden(self): | 88 def test_get_config_one_forbidden(self): |
87 acl.can_read_config_set.return_value = False | 89 self.mock(acl, 'can_read_config_set', mock.Mock(return_value=False)) |
88 with self.call_should_fail(httplib.FORBIDDEN): | 90 with self.call_should_fail(httplib.FORBIDDEN): |
89 req = { | 91 req = { |
90 'config_set': 'services/x', | 92 'config_set': 'services/x', |
91 } | 93 } |
92 self.call_api('get_mapping', req) | 94 self.call_api('get_mapping', req) |
93 | 95 |
94 def test_get_config_all(self): | 96 def test_get_config_all(self): |
95 self.mock(storage, 'get_mapping_async', mock.Mock()) | 97 self.mock(storage, 'get_mapping_async', mock.Mock()) |
96 storage.get_mapping_async.return_value = future({ | 98 storage.get_mapping_async.return_value = future({ |
97 'services/x': 'http://x', | 99 'services/x': 'http://x', |
(...skipping 14 matching lines...) Expand all Loading... |
112 }, | 114 }, |
113 ], | 115 ], |
114 }) | 116 }) |
115 | 117 |
116 def test_get_config_all_partially_forbidden(self): | 118 def test_get_config_all_partially_forbidden(self): |
117 self.mock(storage, 'get_mapping_async', mock.Mock()) | 119 self.mock(storage, 'get_mapping_async', mock.Mock()) |
118 storage.get_mapping_async.return_value = future({ | 120 storage.get_mapping_async.return_value = future({ |
119 'services/x': 'http://x', | 121 'services/x': 'http://x', |
120 'services/y': 'http://y', | 122 'services/y': 'http://y', |
121 }) | 123 }) |
122 acl.can_read_config_set.side_effect = [True, False] | 124 self.mock(acl, 'can_read_config_set', mock.Mock(side_effect=[True, False])) |
123 | 125 |
124 resp = self.call_api('get_mapping', {}).json_body | 126 resp = self.call_api('get_mapping', {}).json_body |
125 | 127 |
126 self.assertEqual(resp, { | 128 self.assertEqual(resp, { |
127 'mappings': [ | 129 'mappings': [ |
128 { | 130 { |
129 'config_set': 'services/x', | 131 'config_set': 'services/x', |
130 'location': 'http://x', | 132 'location': 'http://x', |
131 }, | 133 }, |
132 ], | 134 ], |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
187 storage, 'get_config_hash_async', lambda *_, **__: future((None, None))) | 189 storage, 'get_config_hash_async', lambda *_, **__: future((None, None))) |
188 | 190 |
189 req = { | 191 req = { |
190 'config_set': 'services/x', | 192 'config_set': 'services/x', |
191 'path': 'a.cfg', | 193 'path': 'a.cfg', |
192 } | 194 } |
193 with self.call_should_fail(httplib.NOT_FOUND): | 195 with self.call_should_fail(httplib.NOT_FOUND): |
194 self.call_api('get_config', req) | 196 self.call_api('get_config', req) |
195 | 197 |
196 def test_get_wrong_config_set(self): | 198 def test_get_wrong_config_set(self): |
197 acl.can_read_config_set.side_effect = ValueError | 199 self.mock(acl, 'can_read_config_set', mock.Mock(side_effect=ValueError)) |
198 | 200 |
199 req = { | 201 req = { |
200 'config_set': 'xxx', | 202 'config_set': 'xxx', |
201 'path': 'my.cfg', | 203 'path': 'my.cfg', |
202 'revision': 'deadbeef', | 204 'revision': 'deadbeef', |
203 } | 205 } |
204 with self.call_should_fail(httplib.BAD_REQUEST): | 206 with self.call_should_fail(httplib.BAD_REQUEST): |
205 self.call_api('get_config', req).json_body | 207 self.call_api('get_config', req).json_body |
206 | 208 |
207 def test_get_config_without_permissions(self): | 209 def test_get_config_without_permissions(self): |
208 acl.can_read_config_set.return_value = False | 210 self.mock(acl, 'can_read_config_set', mock.Mock(return_value=False)) |
209 self.mock(storage, 'get_config_hash_async', mock.Mock()) | 211 self.mock(storage, 'get_config_hash_async', mock.Mock()) |
210 | 212 |
211 req = { | 213 req = { |
212 'config_set': 'services/luci-config', | 214 'config_set': 'services/luci-config', |
213 'path': 'projects.cfg', | 215 'path': 'projects.cfg', |
214 } | 216 } |
215 with self.call_should_fail(httplib.NOT_FOUND): | 217 with self.call_should_fail(404): |
216 self.call_api('get_config', req) | 218 self.call_api('get_config', req) |
217 self.assertFalse(storage.get_config_hash_async.called) | 219 self.assertFalse(storage.get_config_hash_async.called) |
218 | 220 |
219 ############################################################################## | 221 ############################################################################## |
220 # get_config_by_hash | 222 # get_config_by_hash |
221 | 223 |
222 def test_get_config_by_hash(self): | 224 def test_get_config_by_hash(self): |
223 self.mock(storage, 'get_config_by_hash_async', mock.Mock()) | 225 self.mock(storage, 'get_config_by_hash_async', mock.Mock()) |
224 storage.get_config_by_hash_async.return_value = future('some content') | 226 storage.get_config_by_hash_async.return_value = future('some content') |
225 | 227 |
226 req = {'content_hash': 'deadbeef'} | 228 req = {'content_hash': 'deadbeef'} |
227 resp = self.call_api('get_config_by_hash', req).json_body | 229 resp = self.call_api('get_config_by_hash', req).json_body |
228 | 230 |
229 self.assertEqual(resp, { | 231 self.assertEqual(resp, { |
230 'content': base64.b64encode('some content'), | 232 'content': base64.b64encode('some content'), |
231 }) | 233 }) |
232 | 234 |
233 storage.get_config_by_hash_async.return_value = future(None) | 235 storage.get_config_by_hash_async.return_value = future(None) |
234 with self.call_should_fail(httplib.NOT_FOUND): | 236 with self.call_should_fail(httplib.NOT_FOUND): |
235 self.call_api('get_config_by_hash', req) | 237 self.call_api('get_config_by_hash', req) |
236 | 238 |
237 ############################################################################## | 239 ############################################################################## |
238 # get_projects | 240 # get_projects |
239 | 241 |
240 def test_get_projects(self): | 242 def test_get_projects(self): |
241 projects.get_projects.return_value = [ | 243 projects.get_projects.return_value = [ |
242 service_config_pb2.Project(id='chromium'), | 244 service_config_pb2.Project(id='chromium'), |
243 service_config_pb2.Project(id='v8'), | 245 service_config_pb2.Project(id='v8'), |
244 service_config_pb2.Project(id='inconsistent'), | 246 service_config_pb2.Project(id='inconsistent'), |
| 247 service_config_pb2.Project(id='secret'), |
245 ] | 248 ] |
246 projects.get_metadata.side_effect = [ | 249 projects.get_metadata.side_effect = [ |
247 project_config_pb2.ProjectCfg(name='Chromium, the best browser'), | 250 project_config_pb2.ProjectCfg( |
248 project_config_pb2.ProjectCfg(), | 251 name='Chromium, the best browser', access='all'), |
249 project_config_pb2.ProjectCfg(), | 252 project_config_pb2.ProjectCfg(access='all'), |
| 253 project_config_pb2.ProjectCfg(access='all'), |
| 254 project_config_pb2.ProjectCfg(access='administrators'), |
250 ] | 255 ] |
251 projects.get_repo.side_effect = [ | 256 projects.get_repo.side_effect = [ |
252 (projects.RepositoryType.GITILES, 'http://localhost/chromium'), | 257 (projects.RepositoryType.GITILES, 'http://localhost/chromium'), |
253 (projects.RepositoryType.GITILES, 'http://localhost/v8'), | 258 (projects.RepositoryType.GITILES, 'http://localhost/v8'), |
254 (None, None) | 259 (None, None), |
| 260 (projects.RepositoryType.GITILES, 'http://localhost/secret'), |
255 ] | 261 ] |
256 | 262 |
257 resp = self.call_api('get_projects', {}).json_body | 263 resp = self.call_api('get_projects', {}).json_body |
258 | 264 |
259 self.assertEqual(resp, { | 265 self.assertEqual(resp, { |
260 'projects': [ | 266 'projects': [ |
261 { | 267 { |
262 'id': 'chromium', | 268 'id': 'chromium', |
263 'name': 'Chromium, the best browser', | 269 'name': 'Chromium, the best browser', |
264 'repo_type': 'GITILES', | 270 'repo_type': 'GITILES', |
265 'repo_url': 'http://localhost/chromium', | 271 'repo_url': 'http://localhost/chromium', |
266 }, | 272 }, |
267 { | 273 { |
268 'id': 'v8', | 274 'id': 'v8', |
269 'repo_type': 'GITILES', | 275 'repo_type': 'GITILES', |
270 'repo_url': 'http://localhost/v8', | 276 'repo_url': 'http://localhost/v8', |
271 }, | 277 }, |
272 ], | 278 ], |
273 }) | 279 }) |
274 | 280 |
275 def test_get_projects_without_permissions(self): | 281 def test_get_projects_without_permissions(self): |
276 acl.has_project_access.return_value = False | 282 acl.has_project_access.return_value = False |
277 with self.call_should_fail(httplib.FORBIDDEN): | 283 self.call_api('get_projects', {}) |
278 self.call_api('get_projects', {}) | |
279 | 284 |
280 ############################################################################## | 285 ############################################################################## |
281 # get_refs | 286 # get_refs |
282 | 287 |
283 def test_get_refs(self): | 288 def test_get_refs(self): |
284 self.mock_refs() | 289 self.mock_refs() |
285 | 290 |
286 req = {'project_id': 'chromium'} | 291 req = {'project_id': 'chromium'} |
287 resp = self.call_api('get_refs', req).json_body | 292 resp = self.call_api('get_refs', req).json_body |
288 | 293 |
289 self.assertEqual(resp, { | 294 self.assertEqual(resp, { |
290 'refs': [ | 295 'refs': [ |
291 {'name': 'refs/heads/master'}, | 296 {'name': 'refs/heads/master'}, |
292 {'name': 'refs/heads/release42'}, | 297 {'name': 'refs/heads/release42'}, |
293 ], | 298 ], |
294 }) | 299 }) |
295 | 300 |
296 def test_get_refs_without_permissions(self): | 301 def test_get_refs_without_permissions(self): |
297 self.mock_refs() | 302 self.mock_refs() |
298 acl.can_read_project_config.return_value = False | 303 acl.has_project_access.side_effect = None |
| 304 acl.has_project_access.return_value = False |
299 | 305 |
300 req = {'project_id': 'chromium'} | 306 req = {'project_id': 'chromium'} |
301 with self.call_should_fail(httplib.NOT_FOUND): | 307 with self.call_should_fail(httplib.NOT_FOUND): |
302 self.call_api('get_refs', req) | 308 self.call_api('get_refs', req) |
303 self.assertFalse(projects.get_refs.called) | 309 self.assertFalse(projects.get_refs.called) |
304 | 310 |
305 | |
306 def test_get_refs_of_non_existent_project(self): | 311 def test_get_refs_of_non_existent_project(self): |
307 self.mock(projects, 'get_refs', mock.Mock()) | 312 self.mock(projects, 'get_refs', mock.Mock()) |
308 projects.get_refs.return_value = None | 313 projects.get_refs.return_value = None |
309 req = {'project_id': 'nonexistent'} | 314 req = {'project_id': 'non-existent'} |
310 with self.call_should_fail(httplib.NOT_FOUND): | 315 with self.call_should_fail(httplib.NOT_FOUND): |
311 self.call_api('get_refs', req) | 316 self.call_api('get_refs', req) |
312 | 317 |
313 ############################################################################## | 318 ############################################################################## |
314 # get_project_configs | 319 # get_project_configs |
315 | 320 |
316 def test_get_config_multi(self): | 321 def test_get_config_multi(self): |
317 self.mock_refs() | 322 self.mock_refs() |
| 323 projects.get_projects.return_value.extend([ |
| 324 service_config_pb2.Project(id='inconsistent'), |
| 325 service_config_pb2.Project(id='secret'), |
| 326 ]) |
318 | 327 |
319 self.mock(storage, 'get_latest_multi_async', mock.Mock()) | 328 self.mock(storage, 'get_latest_multi_async', mock.Mock()) |
320 storage.get_latest_multi_async.return_value = future([ | 329 storage.get_latest_multi_async.return_value = future([ |
321 { | 330 { |
322 'config_set': 'projects/chromium', | 331 'config_set': 'projects/chromium', |
323 'revision': 'deadbeef', | 332 'revision': 'deadbeef', |
324 'content_hash': 'abc0123', | 333 'content_hash': 'abc0123', |
325 'content': 'config text', | 334 'content': 'config text', |
326 }, | 335 }, |
327 { | 336 { |
(...skipping 10 matching lines...) Expand all Loading... |
338 self.assertEqual(resp, { | 347 self.assertEqual(resp, { |
339 'configs': [{ | 348 'configs': [{ |
340 'config_set': 'projects/chromium', | 349 'config_set': 'projects/chromium', |
341 'revision': 'deadbeef', | 350 'revision': 'deadbeef', |
342 'content_hash': 'abc0123', | 351 'content_hash': 'abc0123', |
343 'content': base64.b64encode('config text'), | 352 'content': base64.b64encode('config text'), |
344 }], | 353 }], |
345 }) | 354 }) |
346 config_sets_arg = storage.get_latest_multi_async.call_args[0][0] | 355 config_sets_arg = storage.get_latest_multi_async.call_args[0][0] |
347 self.assertEqual( | 356 self.assertEqual( |
348 list(config_sets_arg), ['projects/chromium', 'projects/v8']) | 357 list(config_sets_arg), |
349 | 358 ['projects/chromium', 'projects/v8', 'projects/inconsistent']) |
350 def test_get_project_configs_without_permission(self): | |
351 self.mock(api, 'get_projects', mock.Mock()) | |
352 acl.has_project_access.return_value = False | |
353 | |
354 req = {'path': 'cq.cfg'} | |
355 with self.call_should_fail(httplib.FORBIDDEN): | |
356 self.call_api('get_project_configs', req) | |
357 self.assertFalse(api.get_projects.called) | |
358 | 359 |
359 ############################################################################## | 360 ############################################################################## |
360 # get_ref_configs | 361 # get_ref_configs |
361 | 362 |
362 def test_get_ref_configs(self): | 363 def test_get_ref_configs(self): |
363 self.mock_refs() | 364 self.mock_refs() |
364 | 365 |
365 self.mock(api, 'get_config_multi', mock.Mock()) | 366 self.mock(api, 'get_config_multi', mock.Mock()) |
366 res = api.GetConfigMultiResponseMessage() | 367 res = api.GetConfigMultiResponseMessage() |
367 api.get_config_multi.return_value = res | 368 api.get_config_multi.return_value = res |
368 | 369 |
369 req = {'path': 'cq.cfg'} | 370 req = {'path': 'cq.cfg'} |
370 resp = self.call_api('get_ref_configs', req).json_body | 371 resp = self.call_api('get_ref_configs', req).json_body |
371 | 372 |
372 config_sets = api.get_config_multi.call_args[0][0] | 373 config_sets = api.get_config_multi.call_args[0][0] |
373 self.assertEqual( | 374 self.assertEqual( |
374 list(config_sets), | 375 list(config_sets), |
375 [ | 376 [ |
376 'projects/chromium/refs/heads/master', | 377 'projects/chromium/refs/heads/master', |
377 'projects/chromium/refs/heads/release42', | 378 'projects/chromium/refs/heads/release42', |
378 'projects/v8/refs/heads/master', | 379 'projects/v8/refs/heads/master', |
379 'projects/v8/refs/heads/release42', | 380 'projects/v8/refs/heads/release42', |
380 ]) | 381 ]) |
381 | 382 |
382 def test_get_ref_configs_without_permission(self): | 383 def test_get_ref_configs_without_permission(self): |
383 self.mock(api, 'get_projects', mock.Mock()) | |
384 acl.has_project_access.return_value = False | 384 acl.has_project_access.return_value = False |
385 | 385 |
386 req = {'path': 'cq.cfg'} | 386 req = {'path': 'cq.cfg'} |
387 with self.call_should_fail(httplib.NOT_FOUND): | 387 resp = self.call_api('get_ref_configs', req).json_body |
388 self.call_api('get_ref_configs', req) | 388 self.assertEqual(resp, {}) |
389 self.assertFalse(api.get_projects.called) | |
390 | 389 |
391 | 390 |
392 if __name__ == '__main__': | 391 if __name__ == '__main__': |
393 test_env.main() | 392 test_env.main() |
OLD | NEW |