github.com/amrnt/deis@v1.3.1/controller/api/tests/test_config.py (about) 1 # -*- coding: utf-8 -*- 2 """ 3 Unit tests for the Deis api app. 4 5 Run the tests with "./manage.py test api" 6 """ 7 8 from __future__ import unicode_literals 9 10 import json 11 import mock 12 import requests 13 14 from django.contrib.auth.models import User 15 from django.test import TransactionTestCase 16 from rest_framework.authtoken.models import Token 17 18 from api.models import Config 19 20 21 def mock_import_repository_task(*args, **kwargs): 22 resp = requests.Response() 23 resp.status_code = 200 24 resp._content_consumed = True 25 return resp 26 27 28 class ConfigTest(TransactionTestCase): 29 30 """Tests setting and updating config values""" 31 32 fixtures = ['tests.json'] 33 34 def setUp(self): 35 self.user = User.objects.get(username='autotest') 36 self.token = Token.objects.get(user=self.user).key 37 38 @mock.patch('requests.post', mock_import_repository_task) 39 def test_config(self): 40 """ 41 Test that config is auto-created for a new app and that 42 config can be updated using a PATCH 43 """ 44 url = '/v1/apps' 45 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 46 self.assertEqual(response.status_code, 201) 47 app_id = response.data['id'] 48 # check to see that an initial/empty config was created 49 url = "/v1/apps/{app_id}/config".format(**locals()) 50 response = self.client.get(url, 51 HTTP_AUTHORIZATION='token {}'.format(self.token)) 52 self.assertEqual(response.status_code, 200) 53 self.assertIn('values', response.data) 54 self.assertEqual(response.data['values'], {}) 55 config1 = response.data 56 # set an initial config value 57 body = {'values': json.dumps({'NEW_URL1': 'http://localhost:8080/'})} 58 response = self.client.post(url, json.dumps(body), content_type='application/json', 59 HTTP_AUTHORIZATION='token {}'.format(self.token)) 60 self.assertEqual(response.status_code, 201) 61 config2 = response.data 62 self.assertNotEqual(config1['uuid'], config2['uuid']) 63 self.assertIn('NEW_URL1', response.data['values']) 64 # read the config 65 response = self.client.get(url, 66 HTTP_AUTHORIZATION='token {}'.format(self.token)) 67 self.assertEqual(response.status_code, 200) 68 config3 = response.data 69 self.assertEqual(config2, config3) 70 self.assertIn('NEW_URL1', response.data['values']) 71 # set an additional config value 72 body = {'values': json.dumps({'NEW_URL2': 'http://localhost:8080/'})} 73 response = self.client.post(url, json.dumps(body), content_type='application/json', 74 HTTP_AUTHORIZATION='token {}'.format(self.token)) 75 self.assertEqual(response.status_code, 201) 76 config3 = response.data 77 self.assertNotEqual(config2['uuid'], config3['uuid']) 78 self.assertIn('NEW_URL1', response.data['values']) 79 self.assertIn('NEW_URL2', response.data['values']) 80 # read the config again 81 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 82 self.assertEqual(response.status_code, 200) 83 config4 = response.data 84 self.assertEqual(config3, config4) 85 self.assertIn('NEW_URL1', response.data['values']) 86 self.assertIn('NEW_URL2', response.data['values']) 87 # unset a config value 88 body = {'values': json.dumps({'NEW_URL2': None})} 89 response = self.client.post(url, json.dumps(body), content_type='application/json', 90 HTTP_AUTHORIZATION='token {}'.format(self.token)) 91 self.assertEqual(response.status_code, 201) 92 config5 = response.data 93 self.assertNotEqual(config4['uuid'], config5['uuid']) 94 self.assertNotIn('NEW_URL2', json.dumps(response.data['values'])) 95 # unset all config values 96 body = {'values': json.dumps({'NEW_URL1': None})} 97 response = self.client.post(url, json.dumps(body), content_type='application/json', 98 HTTP_AUTHORIZATION='token {}'.format(self.token)) 99 self.assertEqual(response.status_code, 201) 100 self.assertNotIn('NEW_URL1', json.dumps(response.data['values'])) 101 # disallow put/patch/delete 102 response = self.client.put(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 103 self.assertEqual(response.status_code, 405) 104 response = self.client.patch(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 105 self.assertEqual(response.status_code, 405) 106 response = self.client.delete(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 107 self.assertEqual(response.status_code, 405) 108 return config5 109 110 @mock.patch('requests.post', mock_import_repository_task) 111 def test_response_data(self): 112 """Test that the serialized response contains only relevant data.""" 113 body = {'id': 'test'} 114 response = self.client.post('/v1/apps', json.dumps(body), 115 content_type='application/json', 116 HTTP_AUTHORIZATION='token {}'.format(self.token)) 117 url = "/v1/apps/test/config".format() 118 # set an initial config value 119 body = {'values': json.dumps({'PORT': '5000'})} 120 response = self.client.post(url, json.dumps(body), content_type='application/json', 121 HTTP_AUTHORIZATION='token {}'.format(self.token)) 122 for key in response.data.keys(): 123 self.assertIn(key, ['uuid', 'owner', 'created', 'updated', 'app', 'values', 'memory', 124 'cpu', 'tags']) 125 expected = { 126 'owner': self.user.username, 127 'app': 'test', 128 'values': {'PORT': '5000'}, 129 'memory': {}, 130 'cpu': {}, 131 'tags': {} 132 } 133 self.assertDictContainsSubset(expected, response.data) 134 135 @mock.patch('requests.post', mock_import_repository_task) 136 def test_config_set_same_key(self): 137 """ 138 Test that config sets on the same key function properly 139 """ 140 url = '/v1/apps' 141 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 142 self.assertEqual(response.status_code, 201) 143 app_id = response.data['id'] 144 url = "/v1/apps/{app_id}/config".format(**locals()) 145 # set an initial config value 146 body = {'values': json.dumps({'PORT': '5000'})} 147 response = self.client.post(url, json.dumps(body), content_type='application/json', 148 HTTP_AUTHORIZATION='token {}'.format(self.token)) 149 self.assertEqual(response.status_code, 201) 150 self.assertIn('PORT', response.data['values']) 151 # reset same config value 152 body = {'values': json.dumps({'PORT': '5001'})} 153 response = self.client.post(url, json.dumps(body), content_type='application/json', 154 HTTP_AUTHORIZATION='token {}'.format(self.token)) 155 self.assertEqual(response.status_code, 201) 156 self.assertIn('PORT', response.data['values']) 157 self.assertEqual(response.data['values']['PORT'], '5001') 158 159 @mock.patch('requests.post', mock_import_repository_task) 160 def test_config_set_unicode(self): 161 """ 162 Test that config sets with unicode values are accepted. 163 """ 164 url = '/v1/apps' 165 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 166 self.assertEqual(response.status_code, 201) 167 app_id = response.data['id'] 168 url = "/v1/apps/{app_id}/config".format(**locals()) 169 # set an initial config value 170 body = {'values': json.dumps({'POWERED_BY': 'Деис'})} 171 response = self.client.post(url, json.dumps(body), content_type='application/json', 172 HTTP_AUTHORIZATION='token {}'.format(self.token)) 173 self.assertEqual(response.status_code, 201) 174 self.assertIn('POWERED_BY', response.data['values']) 175 # reset same config value 176 body = {'values': json.dumps({'POWERED_BY': 'Кроликов'})} 177 response = self.client.post(url, json.dumps(body), content_type='application/json', 178 HTTP_AUTHORIZATION='token {}'.format(self.token)) 179 self.assertEqual(response.status_code, 201) 180 self.assertIn('POWERED_BY', response.data['values']) 181 self.assertEqual(response.data['values']['POWERED_BY'], 'Кроликов') 182 # set an integer to test unicode regression 183 body = {'values': json.dumps({'INTEGER': 1})} 184 response = self.client.post(url, json.dumps(body), content_type='application/json', 185 HTTP_AUTHORIZATION='token {}'.format(self.token)) 186 self.assertEqual(response.status_code, 201) 187 self.assertIn('INTEGER', response.data['values']) 188 self.assertEqual(response.data['values']['INTEGER'], 1) 189 190 @mock.patch('requests.post', mock_import_repository_task) 191 def test_config_str(self): 192 """Test the text representation of a node.""" 193 config5 = self.test_config() 194 config = Config.objects.get(uuid=config5['uuid']) 195 self.assertEqual(str(config), "{}-{}".format(config5['app'], config5['uuid'][:7])) 196 197 @mock.patch('requests.post', mock_import_repository_task) 198 def test_admin_can_create_config_on_other_apps(self): 199 """If a non-admin creates an app, an administrator should be able to set config 200 values for that app. 201 """ 202 user = User.objects.get(username='autotest2') 203 token = Token.objects.get(user=user).key 204 url = '/v1/apps' 205 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(token)) 206 self.assertEqual(response.status_code, 201) 207 app_id = response.data['id'] 208 url = "/v1/apps/{app_id}/config".format(**locals()) 209 # set an initial config value 210 body = {'values': json.dumps({'PORT': '5000'})} 211 response = self.client.post(url, json.dumps(body), content_type='application/json', 212 HTTP_AUTHORIZATION='token {}'.format(self.token)) 213 self.assertEqual(response.status_code, 201) 214 self.assertIn('PORT', response.data['values']) 215 return response 216 217 @mock.patch('requests.post', mock_import_repository_task) 218 def test_limit_memory(self): 219 """ 220 Test that limit is auto-created for a new app and that 221 limits can be updated using a PATCH 222 """ 223 url = '/v1/apps' 224 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 225 self.assertEqual(response.status_code, 201) 226 app_id = response.data['id'] 227 url = '/v1/apps/{app_id}/config'.format(**locals()) 228 # check default limit 229 response = self.client.get(url, content_type='application/json', 230 HTTP_AUTHORIZATION='token {}'.format(self.token)) 231 self.assertEqual(response.status_code, 200) 232 self.assertIn('memory', response.data) 233 self.assertEqual(response.data['memory'], {}) 234 # regression test for https://github.com/deis/deis/issues/1563 235 self.assertNotIn('"', response.data['memory']) 236 # set an initial limit 237 mem = {'web': '1G'} 238 body = {'memory': json.dumps(mem)} 239 response = self.client.post(url, json.dumps(body), content_type='application/json', 240 HTTP_AUTHORIZATION='token {}'.format(self.token)) 241 self.assertEqual(response.status_code, 201) 242 limit1 = response.data 243 # check memory limits 244 response = self.client.get(url, content_type='application/json', 245 HTTP_AUTHORIZATION='token {}'.format(self.token)) 246 self.assertEqual(response.status_code, 200) 247 self.assertIn('memory', response.data) 248 memory = response.data['memory'] 249 self.assertIn('web', memory) 250 self.assertEqual(memory['web'], '1G') 251 # set an additional value 252 body = {'memory': json.dumps({'worker': '512M'})} 253 response = self.client.post(url, json.dumps(body), content_type='application/json', 254 HTTP_AUTHORIZATION='token {}'.format(self.token)) 255 self.assertEqual(response.status_code, 201) 256 limit2 = response.data 257 self.assertNotEqual(limit1['uuid'], limit2['uuid']) 258 memory = response.data['memory'] 259 self.assertIn('worker', memory) 260 self.assertEqual(memory['worker'], '512M') 261 self.assertIn('web', memory) 262 self.assertEqual(memory['web'], '1G') 263 # read the limit again 264 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 265 self.assertEqual(response.status_code, 200) 266 limit3 = response.data 267 self.assertEqual(limit2, limit3) 268 memory = response.data['memory'] 269 self.assertIn('worker', memory) 270 self.assertEqual(memory['worker'], '512M') 271 self.assertIn('web', memory) 272 self.assertEqual(memory['web'], '1G') 273 # regression test for https://github.com/deis/deis/issues/1613 274 # ensure that config:set doesn't wipe out previous limits 275 body = {'values': json.dumps({'NEW_URL2': 'http://localhost:8080/'})} 276 response = self.client.post(url, json.dumps(body), content_type='application/json', 277 HTTP_AUTHORIZATION='token {}'.format(self.token)) 278 self.assertEqual(response.status_code, 201) 279 self.assertIn('NEW_URL2', response.data['values']) 280 # read the limit again 281 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 282 self.assertEqual(response.status_code, 200) 283 memory = response.data['memory'] 284 self.assertIn('worker', memory) 285 self.assertEqual(memory['worker'], '512M') 286 self.assertIn('web', memory) 287 self.assertEqual(memory['web'], '1G') 288 # unset a value 289 body = {'memory': json.dumps({'worker': None})} 290 response = self.client.post(url, json.dumps(body), content_type='application/json', 291 HTTP_AUTHORIZATION='token {}'.format(self.token)) 292 self.assertEqual(response.status_code, 201) 293 limit4 = response.data 294 self.assertNotEqual(limit3['uuid'], limit4['uuid']) 295 self.assertNotIn('worker', json.dumps(response.data['memory'])) 296 # disallow put/patch/delete 297 response = self.client.put(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 298 self.assertEqual(response.status_code, 405) 299 response = self.client.patch(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 300 self.assertEqual(response.status_code, 405) 301 response = self.client.delete(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 302 self.assertEqual(response.status_code, 405) 303 return limit4 304 305 @mock.patch('requests.post', mock_import_repository_task) 306 def test_limit_cpu(self): 307 """ 308 Test that CPU limits can be set 309 """ 310 url = '/v1/apps' 311 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 312 self.assertEqual(response.status_code, 201) 313 app_id = response.data['id'] 314 url = '/v1/apps/{app_id}/config'.format(**locals()) 315 # check default limit 316 response = self.client.get(url, content_type='application/json', 317 HTTP_AUTHORIZATION='token {}'.format(self.token)) 318 self.assertEqual(response.status_code, 200) 319 self.assertIn('cpu', response.data) 320 self.assertEqual(response.data['cpu'], {}) 321 # regression test for https://github.com/deis/deis/issues/1563 322 self.assertNotIn('"', response.data['cpu']) 323 # set an initial limit 324 body = {'cpu': json.dumps({'web': '1024'})} 325 response = self.client.post(url, json.dumps(body), content_type='application/json', 326 HTTP_AUTHORIZATION='token {}'.format(self.token)) 327 self.assertEqual(response.status_code, 201) 328 limit1 = response.data 329 # check memory limits 330 response = self.client.get(url, content_type='application/json', 331 HTTP_AUTHORIZATION='token {}'.format(self.token)) 332 self.assertEqual(response.status_code, 200) 333 self.assertIn('cpu', response.data) 334 cpu = response.data['cpu'] 335 self.assertIn('web', cpu) 336 self.assertEqual(cpu['web'], '1024') 337 # set an additional value 338 body = {'cpu': json.dumps({'worker': '512'})} 339 response = self.client.post(url, json.dumps(body), content_type='application/json', 340 HTTP_AUTHORIZATION='token {}'.format(self.token)) 341 self.assertEqual(response.status_code, 201) 342 limit2 = response.data 343 self.assertNotEqual(limit1['uuid'], limit2['uuid']) 344 cpu = response.data['cpu'] 345 self.assertIn('worker', cpu) 346 self.assertEqual(cpu['worker'], '512') 347 self.assertIn('web', cpu) 348 self.assertEqual(cpu['web'], '1024') 349 # read the limit again 350 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 351 self.assertEqual(response.status_code, 200) 352 limit3 = response.data 353 self.assertEqual(limit2, limit3) 354 cpu = response.data['cpu'] 355 self.assertIn('worker', cpu) 356 self.assertEqual(cpu['worker'], '512') 357 self.assertIn('web', cpu) 358 self.assertEqual(cpu['web'], '1024') 359 # unset a value 360 body = {'memory': json.dumps({'worker': None})} 361 response = self.client.post(url, json.dumps(body), content_type='application/json', 362 HTTP_AUTHORIZATION='token {}'.format(self.token)) 363 self.assertEqual(response.status_code, 201) 364 limit4 = response.data 365 self.assertNotEqual(limit3['uuid'], limit4['uuid']) 366 self.assertNotIn('worker', json.dumps(response.data['memory'])) 367 # disallow put/patch/delete 368 response = self.client.put(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 369 self.assertEqual(response.status_code, 405) 370 response = self.client.patch(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 371 self.assertEqual(response.status_code, 405) 372 response = self.client.delete(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 373 self.assertEqual(response.status_code, 405) 374 return limit4 375 376 @mock.patch('requests.post', mock_import_repository_task) 377 def test_tags(self): 378 """ 379 Test that tags can be set on an application 380 """ 381 url = '/v1/apps' 382 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 383 self.assertEqual(response.status_code, 201) 384 app_id = response.data['id'] 385 url = '/v1/apps/{app_id}/config'.format(**locals()) 386 # check default 387 response = self.client.get(url, content_type='application/json', 388 HTTP_AUTHORIZATION='token {}'.format(self.token)) 389 self.assertEqual(response.status_code, 200) 390 self.assertIn('tags', response.data) 391 self.assertEqual(response.data['tags'], {}) 392 # set some tags 393 body = {'tags': json.dumps({'environ': 'dev'})} 394 response = self.client.post(url, json.dumps(body), content_type='application/json', 395 HTTP_AUTHORIZATION='token {}'.format(self.token)) 396 self.assertEqual(response.status_code, 201) 397 tags1 = response.data 398 # check tags again 399 response = self.client.get(url, content_type='application/json', 400 HTTP_AUTHORIZATION='token {}'.format(self.token)) 401 self.assertEqual(response.status_code, 200) 402 self.assertIn('tags', response.data) 403 tags = response.data['tags'] 404 self.assertIn('environ', tags) 405 self.assertEqual(tags['environ'], 'dev') 406 # set an additional value 407 body = {'tags': json.dumps({'rack': '1'})} 408 response = self.client.post(url, json.dumps(body), content_type='application/json', 409 HTTP_AUTHORIZATION='token {}'.format(self.token)) 410 self.assertEqual(response.status_code, 201) 411 tags2 = response.data 412 self.assertNotEqual(tags1['uuid'], tags2['uuid']) 413 tags = response.data['tags'] 414 self.assertIn('rack', tags) 415 self.assertEqual(tags['rack'], '1') 416 self.assertIn('environ', tags) 417 self.assertEqual(tags['environ'], 'dev') 418 # read the limit again 419 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 420 self.assertEqual(response.status_code, 200) 421 tags3 = response.data 422 self.assertEqual(tags2, tags3) 423 tags = response.data['tags'] 424 self.assertIn('rack', tags) 425 self.assertEqual(tags['rack'], '1') 426 self.assertIn('environ', tags) 427 self.assertEqual(tags['environ'], 'dev') 428 # unset a value 429 body = {'tags': json.dumps({'rack': None})} 430 response = self.client.post(url, json.dumps(body), content_type='application/json', 431 HTTP_AUTHORIZATION='token {}'.format(self.token)) 432 self.assertEqual(response.status_code, 201) 433 tags4 = response.data 434 self.assertNotEqual(tags3['uuid'], tags4['uuid']) 435 self.assertNotIn('rack', json.dumps(response.data['tags'])) 436 # set invalid values 437 body = {'tags': json.dumps({'valid': 'in\nvalid'})} 438 response = self.client.post(url, json.dumps(body), content_type='application/json', 439 HTTP_AUTHORIZATION='token {}'.format(self.token)) 440 self.assertEqual(response.status_code, 400) 441 body = {'tags': json.dumps({'in.valid': 'valid'})} 442 response = self.client.post(url, json.dumps(body), content_type='application/json', 443 HTTP_AUTHORIZATION='token {}'.format(self.token)) 444 self.assertEqual(response.status_code, 400) 445 # disallow put/patch/delete 446 response = self.client.put(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 447 self.assertEqual(response.status_code, 405) 448 response = self.client.patch(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 449 self.assertEqual(response.status_code, 405) 450 response = self.client.delete(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 451 self.assertEqual(response.status_code, 405) 452 453 def test_config_owner_is_requesting_user(self): 454 """ 455 Ensure that setting the config value is owned by the requesting user 456 See https://github.com/deis/deis/issues/2650 457 """ 458 response = self.test_admin_can_create_config_on_other_apps() 459 self.assertEqual(response.data['owner'], self.user.username) 460 461 def test_unauthorized_user_cannot_modify_config(self): 462 """ 463 An unauthorized user should not be able to modify other config. 464 465 Since an unauthorized user can't access the application, these 466 requests should return a 403. 467 """ 468 app_id = 'autotest' 469 base_url = '/v1/apps' 470 body = {'id': app_id} 471 response = self.client.post(base_url, json.dumps(body), content_type='application/json', 472 HTTP_AUTHORIZATION='token {}'.format(self.token)) 473 unauthorized_user = User.objects.get(username='autotest2') 474 unauthorized_token = Token.objects.get(user=unauthorized_user).key 475 url = '{}/{}/config'.format(base_url, app_id) 476 body = {'values': {'FOO': 'bar'}} 477 response = self.client.post(url, json.dumps(body), content_type='application/json', 478 HTTP_AUTHORIZATION='token {}'.format(unauthorized_token)) 479 self.assertEqual(response.status_code, 403)