github.com/jiasir/deis@v1.12.2/controller/api/tests/test_app.py (about)

     1  """
     2  Unit tests for the Deis api app.
     3  
     4  Run the tests with "./manage.py test api"
     5  """
     6  
     7  from __future__ import unicode_literals
     8  
     9  import json
    10  import logging
    11  import mock
    12  import requests
    13  
    14  from django.conf import settings
    15  from django.contrib.auth.models import User
    16  from django.test import TestCase
    17  from rest_framework.authtoken.models import Token
    18  
    19  from api.models import App
    20  from . import mock_status_ok
    21  
    22  
    23  class AppTest(TestCase):
    24      """Tests creation of applications"""
    25  
    26      fixtures = ['tests.json']
    27  
    28      def setUp(self):
    29          self.user = User.objects.get(username='autotest')
    30          self.token = Token.objects.get(user=self.user).key
    31          # provide mock authentication used for run commands
    32          settings.SSH_PRIVATE_KEY = '<some-ssh-private-key>'
    33  
    34      def tearDown(self):
    35          # reset global vars for other tests
    36          settings.SSH_PRIVATE_KEY = ''
    37  
    38      def test_app(self):
    39          """
    40          Test that a user can create, read, update and delete an application
    41          """
    42          url = '/v1/apps'
    43          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
    44          self.assertEqual(response.status_code, 201)
    45          app_id = response.data['id']  # noqa
    46          self.assertIn('id', response.data)
    47          response = self.client.get('/v1/apps',
    48                                     HTTP_AUTHORIZATION='token {}'.format(self.token))
    49          self.assertEqual(response.status_code, 200)
    50          self.assertEqual(len(response.data['results']), 1)
    51          url = '/v1/apps/{app_id}'.format(**locals())
    52          response = self.client.get(url,
    53                                     HTTP_AUTHORIZATION='token {}'.format(self.token))
    54          self.assertEqual(response.status_code, 200)
    55          body = {'id': 'new'}
    56          response = self.client.patch(url, json.dumps(body), content_type='application/json',
    57                                       HTTP_AUTHORIZATION='token {}'.format(self.token))
    58          self.assertEqual(response.status_code, 405)
    59          response = self.client.delete(url,
    60                                        HTTP_AUTHORIZATION='token {}'.format(self.token))
    61          self.assertEqual(response.status_code, 204)
    62  
    63      def test_response_data(self):
    64          """Test that the serialized response contains only relevant data."""
    65          body = {'id': 'test'}
    66          response = self.client.post('/v1/apps', json.dumps(body),
    67                                      content_type='application/json',
    68                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
    69          for key in response.data:
    70              self.assertIn(key, ['uuid', 'created', 'updated', 'id', 'owner', 'url', 'structure'])
    71          expected = {
    72              'id': 'test',
    73              'owner': self.user.username,
    74              'url': 'test.deisapp.local',
    75              'structure': {}
    76          }
    77          self.assertDictContainsSubset(expected, response.data)
    78  
    79      def test_app_override_id(self):
    80          body = {'id': 'myid'}
    81          response = self.client.post('/v1/apps', json.dumps(body),
    82                                      content_type='application/json',
    83                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
    84          self.assertEqual(response.status_code, 201)
    85          body = {'id': response.data['id']}
    86          response = self.client.post('/v1/apps', json.dumps(body),
    87                                      content_type='application/json',
    88                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
    89          self.assertContains(response, 'This field must be unique.', status_code=400)
    90          return response
    91  
    92      @mock.patch('requests.get')
    93      def test_app_actions(self, mock_get):
    94          url = '/v1/apps'
    95          body = {'id': 'autotest'}
    96          response = self.client.post(url, json.dumps(body), content_type='application/json',
    97                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
    98          self.assertEqual(response.status_code, 201)
    99          app_id = response.data['id']  # noqa
   100  
   101          # test logs - 204 from deis-logger
   102          mock_response = mock.Mock()
   103          mock_response.status_code = 204
   104          mock_get.return_value = mock_response
   105          url = "/v1/apps/{app_id}/logs".format(**locals())
   106          response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
   107          self.assertEqual(response.status_code, 204)
   108          self.assertEqual(response.data, "No logs for {}".format(app_id))
   109  
   110          # test logs - 404 from deis-logger
   111          mock_response.status_code = 404
   112          response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
   113          self.assertEqual(response.status_code, 204)
   114          self.assertEqual(response.data, "No logs for {}".format(app_id))
   115  
   116          # test logs - unanticipated status code from deis-logger
   117          mock_response.status_code = 400
   118          response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
   119          self.assertEqual(response.status_code, 500)
   120          self.assertEqual(response.data, "Error accessing logs for {}".format(app_id))
   121  
   122          # test logs - success accessing deis-logger
   123          mock_response.status_code = 200
   124          mock_response.content = FAKE_LOG_DATA
   125          response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
   126          self.assertEqual(response.status_code, 200)
   127          self.assertEqual(response.data, FAKE_LOG_DATA)
   128  
   129          # test logs - HTTP request error while accessing deis-logger
   130          mock_get.side_effect = requests.exceptions.RequestException('Boom!')
   131          response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
   132          self.assertEqual(response.status_code, 500)
   133          self.assertEqual(response.data, "Error accessing logs for {}".format(app_id))
   134  
   135          # TODO: test run needs an initial build
   136  
   137      @mock.patch('api.models.logger')
   138      def test_app_release_notes_in_logs(self, mock_logger):
   139          """Verifies that an app's release summary is dumped into the logs."""
   140          url = '/v1/apps'
   141          body = {'id': 'autotest'}
   142          response = self.client.post(url, json.dumps(body), content_type='application/json',
   143                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   144          self.assertEqual(response.status_code, 201)
   145          # check app logs
   146          exp_msg = "autotest created initial release"
   147          exp_log_call = mock.call(logging.INFO, exp_msg)
   148          mock_logger.log.has_calls(exp_log_call)
   149  
   150      def test_app_errors(self):
   151          app_id = 'autotest-errors'
   152          url = '/v1/apps'
   153          body = {'id': 'camelCase'}
   154          response = self.client.post(url, json.dumps(body), content_type='application/json',
   155                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   156          self.assertContains(response, 'App IDs can only contain [a-z0-9-]', status_code=400)
   157          url = '/v1/apps'
   158          body = {'id': app_id}
   159          response = self.client.post(url, json.dumps(body), content_type='application/json',
   160                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   161          self.assertEqual(response.status_code, 201)
   162          app_id = response.data['id']  # noqa
   163          url = '/v1/apps/{app_id}'.format(**locals())
   164          response = self.client.delete(url,
   165                                        HTTP_AUTHORIZATION='token {}'.format(self.token))
   166          self.assertEquals(response.status_code, 204)
   167          for endpoint in ('containers', 'config', 'releases', 'builds'):
   168              url = '/v1/apps/{app_id}/{endpoint}'.format(**locals())
   169              response = self.client.get(url,
   170                                         HTTP_AUTHORIZATION='token {}'.format(self.token))
   171              self.assertEquals(response.status_code, 404)
   172  
   173      def test_app_reserved_names(self):
   174          """Nobody should be able to create applications with names which are reserved."""
   175          url = '/v1/apps'
   176          reserved_names = ['foo', 'bar']
   177          with self.settings(DEIS_RESERVED_NAMES=reserved_names):
   178              for name in reserved_names:
   179                  body = {'id': name}
   180                  response = self.client.post(url, json.dumps(body), content_type='application/json',
   181                                              HTTP_AUTHORIZATION='token {}'.format(self.token))
   182                  self.assertContains(
   183                      response,
   184                      '{} is a reserved name.'.format(name),
   185                      status_code=400)
   186  
   187      def test_app_structure_is_valid_json(self):
   188          """Application structures should be valid JSON objects."""
   189          url = '/v1/apps'
   190          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   191          self.assertEqual(response.status_code, 201)
   192          app_id = response.data['id']
   193          self.assertIn('structure', response.data)
   194          self.assertEqual(response.data['structure'], {})
   195          app = App.objects.get(id=app_id)
   196          app.structure = {'web': 1}
   197          app.save()
   198          url = '/v1/apps/{}'.format(app_id)
   199          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   200          self.assertIn('structure', response.data)
   201          self.assertEqual(response.data['structure'], {"web": 1})
   202  
   203      @mock.patch('requests.post', mock_status_ok)
   204      @mock.patch('api.models.logger')
   205      def test_admin_can_manage_other_apps(self, mock_logger):
   206          """Administrators of Deis should be able to manage all applications.
   207          """
   208          # log in as non-admin user and create an app
   209          user = User.objects.get(username='autotest2')
   210          token = Token.objects.get(user=user)
   211          app_id = 'autotest'
   212          url = '/v1/apps'
   213          body = {'id': app_id}
   214          response = self.client.post(url, json.dumps(body), content_type='application/json',
   215                                      HTTP_AUTHORIZATION='token {}'.format(token))
   216          # log in as admin, check to see if they have access
   217          url = '/v1/apps/{}'.format(app_id)
   218          response = self.client.get(url,
   219                                     HTTP_AUTHORIZATION='token {}'.format(self.token))
   220          self.assertEqual(response.status_code, 200)
   221          # check app logs
   222          exp_msg = "autotest2 created initial release"
   223          exp_log_call = mock.call(logging.INFO, exp_msg)
   224          mock_logger.log.has_calls(exp_log_call)
   225          # TODO: test run needs an initial build
   226          # delete the app
   227          url = '/v1/apps/{}'.format(app_id)
   228          response = self.client.delete(url,
   229                                        HTTP_AUTHORIZATION='token {}'.format(self.token))
   230          self.assertEqual(response.status_code, 204)
   231  
   232      def test_admin_can_see_other_apps(self):
   233          """If a user creates an application, the administrator should be able
   234          to see it.
   235          """
   236          # log in as non-admin user and create an app
   237          user = User.objects.get(username='autotest2')
   238          token = Token.objects.get(user=user)
   239          app_id = 'autotest'
   240          url = '/v1/apps'
   241          body = {'id': app_id}
   242          response = self.client.post(url, json.dumps(body), content_type='application/json',
   243                                      HTTP_AUTHORIZATION='token {}'.format(token))
   244          # log in as admin
   245          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   246          self.assertEqual(response.data['count'], 1)
   247  
   248      def test_run_without_auth(self):
   249          """If the administrator has not provided SSH private key for run commands,
   250          make sure a friendly error message is provided on run"""
   251          settings.SSH_PRIVATE_KEY = ''
   252          url = '/v1/apps'
   253          body = {'id': 'autotest'}
   254          response = self.client.post(url, json.dumps(body), content_type='application/json',
   255                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   256          self.assertEqual(response.status_code, 201)
   257          app_id = response.data['id']  # noqa
   258          # test run
   259          url = '/v1/apps/{app_id}/run'.format(**locals())
   260          body = {'command': 'ls -al'}
   261          response = self.client.post(url, json.dumps(body), content_type='application/json',
   262                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   263          self.assertEquals(response.status_code, 400)
   264          self.assertEquals(response.data, {'detail': 'Support for admin commands '
   265                                                      'is not configured'})
   266  
   267      def test_run_without_release_should_error(self):
   268          """
   269          A user should not be able to run a one-off command unless a release
   270          is present.
   271          """
   272          app_id = 'autotest'
   273          url = '/v1/apps'
   274          body = {'id': app_id}
   275          response = self.client.post(url, json.dumps(body), content_type='application/json',
   276                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   277          url = '/v1/apps/{}/run'.format(app_id)
   278          body = {'command': 'ls -al'}
   279          response = self.client.post(url, json.dumps(body), content_type='application/json',
   280                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   281          self.assertEqual(response.status_code, 400)
   282          self.assertEqual(response.data, {'detail': 'No build associated with this '
   283                                                     'release to run this command'})
   284  
   285      def test_unauthorized_user_cannot_see_app(self):
   286          """
   287          An unauthorized user should not be able to access an app's resources.
   288  
   289          Since an unauthorized user can't access the application, these
   290          tests should return a 403, but currently return a 404. FIXME!
   291          """
   292          app_id = 'autotest'
   293          base_url = '/v1/apps'
   294          body = {'id': app_id}
   295          response = self.client.post(base_url, json.dumps(body), content_type='application/json',
   296                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   297          unauthorized_user = User.objects.get(username='autotest2')
   298          unauthorized_token = Token.objects.get(user=unauthorized_user).key
   299          url = '{}/{}/run'.format(base_url, app_id)
   300          body = {'command': 'foo'}
   301          response = self.client.post(url, json.dumps(body), content_type='application/json',
   302                                      HTTP_AUTHORIZATION='token {}'.format(unauthorized_token))
   303          self.assertEqual(response.status_code, 403)
   304          url = '{}/{}/logs'.format(base_url, app_id)
   305          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(unauthorized_token))
   306          self.assertEqual(response.status_code, 403)
   307          url = '{}/{}'.format(base_url, app_id)
   308          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(unauthorized_token))
   309          self.assertEqual(response.status_code, 403)
   310          response = self.client.delete(url,
   311                                        HTTP_AUTHORIZATION='token {}'.format(unauthorized_token))
   312          self.assertEqual(response.status_code, 403)
   313  
   314      def test_app_info_not_showing_wrong_app(self):
   315          app_id = 'autotest'
   316          base_url = '/v1/apps'
   317          body = {'id': app_id}
   318          response = self.client.post(base_url, json.dumps(body), content_type='application/json',
   319                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   320          url = '{}/foo'.format(base_url)
   321          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   322          self.assertEqual(response.status_code, 404)
   323  
   324      def test_app_transfer(self):
   325          owner = User.objects.get(username='autotest2')
   326          owner_token = Token.objects.get(user=owner).key
   327          app_id = 'autotest'
   328          base_url = '/v1/apps'
   329          body = {'id': app_id}
   330          response = self.client.post(base_url, json.dumps(body), content_type='application/json',
   331                                      HTTP_AUTHORIZATION='token {}'.format(owner_token))
   332          # Transfer App
   333          url = '{}/{}'.format(base_url, app_id)
   334          new_owner = User.objects.get(username='autotest3')
   335          new_owner_token = Token.objects.get(user=new_owner).key
   336          body = {'owner': new_owner.username}
   337          response = self.client.post(url, json.dumps(body), content_type='application/json',
   338                                      HTTP_AUTHORIZATION='token {}'.format(owner_token))
   339          self.assertEqual(response.status_code, 200)
   340  
   341          # Original user can no longer access it
   342          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(owner_token))
   343          self.assertEqual(response.status_code, 403)
   344  
   345          # New owner can access it
   346          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(new_owner_token))
   347          self.assertEqual(response.status_code, 200)
   348          self.assertEqual(response.data['owner'], new_owner.username)
   349  
   350          # Collaborators can't transfer
   351          body = {'username': owner.username}
   352          perms_url = url+"/perms/"
   353          response = self.client.post(perms_url, json.dumps(body), content_type='application/json',
   354                                      HTTP_AUTHORIZATION='token {}'.format(new_owner_token))
   355          self.assertEqual(response.status_code, 201)
   356          body = {'owner': self.user.username}
   357          response = self.client.post(url, json.dumps(body), content_type='application/json',
   358                                      HTTP_AUTHORIZATION='token {}'.format(owner_token))
   359          self.assertEqual(response.status_code, 403)
   360  
   361          # Admins can transfer
   362          body = {'owner': self.user.username}
   363          response = self.client.post(url, json.dumps(body), content_type='application/json',
   364                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   365          self.assertEqual(response.status_code, 200)
   366          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   367          self.assertEqual(response.status_code, 200)
   368          self.assertEqual(response.data['owner'], self.user.username)
   369  
   370  
   371  FAKE_LOG_DATA = """
   372  2013-08-15 12:41:25 [33454] [INFO] Starting gunicorn 17.5
   373  2013-08-15 12:41:25 [33454] [INFO] Listening at: http://0.0.0.0:5000 (33454)
   374  2013-08-15 12:41:25 [33454] [INFO] Using worker: sync
   375  2013-08-15 12:41:25 [33457] [INFO] Booting worker with pid 33457
   376  """