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)