| 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 |