github.com/chasestarr/deis@v1.13.5-0.20170519182049-1d9e59fbdbfc/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  
   109          # test logs - 404 from deis-logger
   110          mock_response.status_code = 404
   111          response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
   112          self.assertEqual(response.status_code, 204)
   113  
   114          # test logs - unanticipated status code from deis-logger
   115          mock_response.status_code = 400
   116          response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
   117          self.assertEqual(response.status_code, 500)
   118          self.assertEqual(response.content, "Error accessing logs for {}".format(app_id))
   119  
   120          # test logs - success accessing deis-logger
   121          mock_response.status_code = 200
   122          mock_response.content = FAKE_LOG_DATA
   123          response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
   124          self.assertEqual(response.status_code, 200)
   125          self.assertEqual(response.content, FAKE_LOG_DATA)
   126  
   127          # test logs - HTTP request error while accessing deis-logger
   128          mock_get.side_effect = requests.exceptions.RequestException('Boom!')
   129          response = self.client.get(url, HTTP_AUTHORIZATION="token {}".format(self.token))
   130          self.assertEqual(response.status_code, 500)
   131          self.assertEqual(response.content, "Error accessing logs for {}".format(app_id))
   132  
   133          # TODO: test run needs an initial build
   134  
   135      @mock.patch('api.models.logger')
   136      def test_app_release_notes_in_logs(self, mock_logger):
   137          """Verifies that an app's release summary is dumped into the logs."""
   138          url = '/v1/apps'
   139          app_name = 'autotest'
   140          body = {'id': app_name}
   141  
   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          app = App.objects.get(id=app_name)
   146          # check app logs
   147          exp_msg = "[{app_name}]: {self.user.username} created initial release".format(**locals())
   148          mock_logger.log.assert_called_with(logging.INFO, exp_msg)
   149          app.log('hello world')
   150          exp_msg = "[{app_name}]: hello world".format(**locals())
   151          mock_logger.log.assert_called_with(logging.INFO, exp_msg)
   152          app.log('goodbye world', logging.WARNING)
   153          # assert logging with a different log level
   154          exp_msg = "[{app_name}]: goodbye world".format(**locals())
   155          mock_logger.log.assert_called_with(logging.WARNING, exp_msg)
   156  
   157      def test_app_errors(self):
   158          app_id = 'autotest-errors'
   159          url = '/v1/apps'
   160          body = {'id': 'camelCase'}
   161          response = self.client.post(url, json.dumps(body), content_type='application/json',
   162                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   163          self.assertContains(response, 'App IDs can only contain [a-z0-9-]', status_code=400)
   164          url = '/v1/apps'
   165          body = {'id': app_id}
   166          response = self.client.post(url, json.dumps(body), content_type='application/json',
   167                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   168          self.assertEqual(response.status_code, 201)
   169          app_id = response.data['id']  # noqa
   170          url = '/v1/apps/{app_id}'.format(**locals())
   171          response = self.client.delete(url,
   172                                        HTTP_AUTHORIZATION='token {}'.format(self.token))
   173          self.assertEquals(response.status_code, 204)
   174          for endpoint in ('containers', 'config', 'releases', 'builds'):
   175              url = '/v1/apps/{app_id}/{endpoint}'.format(**locals())
   176              response = self.client.get(url,
   177                                         HTTP_AUTHORIZATION='token {}'.format(self.token))
   178              self.assertEquals(response.status_code, 404)
   179  
   180      def test_app_reserved_names(self):
   181          """Nobody should be able to create applications with names which are reserved."""
   182          url = '/v1/apps'
   183          reserved_names = ['foo', 'bar']
   184          with self.settings(DEIS_RESERVED_NAMES=reserved_names):
   185              for name in reserved_names:
   186                  body = {'id': name}
   187                  response = self.client.post(url, json.dumps(body), content_type='application/json',
   188                                              HTTP_AUTHORIZATION='token {}'.format(self.token))
   189                  self.assertContains(
   190                      response,
   191                      '{} is a reserved name.'.format(name),
   192                      status_code=400)
   193  
   194      def test_app_structure_is_valid_json(self):
   195          """Application structures should be valid JSON objects."""
   196          url = '/v1/apps'
   197          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   198          self.assertEqual(response.status_code, 201)
   199          app_id = response.data['id']
   200          self.assertIn('structure', response.data)
   201          self.assertEqual(response.data['structure'], {})
   202          app = App.objects.get(id=app_id)
   203          app.structure = {'web': 1}
   204          app.save()
   205          url = '/v1/apps/{}'.format(app_id)
   206          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   207          self.assertIn('structure', response.data)
   208          self.assertEqual(response.data['structure'], {"web": 1})
   209  
   210      @mock.patch('requests.post', mock_status_ok)
   211      @mock.patch('api.models.logger')
   212      def test_admin_can_manage_other_apps(self, mock_logger):
   213          """Administrators of Deis should be able to manage all applications.
   214          """
   215          # log in as non-admin user and create an app
   216          user = User.objects.get(username='autotest2')
   217          token = Token.objects.get(user=user)
   218          app_id = 'autotest'
   219          url = '/v1/apps'
   220          body = {'id': app_id}
   221          response = self.client.post(url, json.dumps(body), content_type='application/json',
   222                                      HTTP_AUTHORIZATION='token {}'.format(token))
   223          # log in as admin, check to see if they have access
   224          url = '/v1/apps/{}'.format(app_id)
   225          response = self.client.get(url,
   226                                     HTTP_AUTHORIZATION='token {}'.format(self.token))
   227          self.assertEqual(response.status_code, 200)
   228          # check app logs
   229          exp_msg = "autotest2 created initial release"
   230          exp_log_call = mock.call(logging.INFO, exp_msg)
   231          mock_logger.log.has_calls(exp_log_call)
   232          # TODO: test run needs an initial build
   233          # delete the app
   234          url = '/v1/apps/{}'.format(app_id)
   235          response = self.client.delete(url,
   236                                        HTTP_AUTHORIZATION='token {}'.format(self.token))
   237          self.assertEqual(response.status_code, 204)
   238  
   239      def test_admin_can_see_other_apps(self):
   240          """If a user creates an application, the administrator should be able
   241          to see it.
   242          """
   243          # log in as non-admin user and create an app
   244          user = User.objects.get(username='autotest2')
   245          token = Token.objects.get(user=user)
   246          app_id = 'autotest'
   247          url = '/v1/apps'
   248          body = {'id': app_id}
   249          response = self.client.post(url, json.dumps(body), content_type='application/json',
   250                                      HTTP_AUTHORIZATION='token {}'.format(token))
   251          # log in as admin
   252          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   253          self.assertEqual(response.data['count'], 1)
   254  
   255      def test_run_without_auth(self):
   256          """If the administrator has not provided SSH private key for run commands,
   257          make sure a friendly error message is provided on run"""
   258          settings.SSH_PRIVATE_KEY = ''
   259          url = '/v1/apps'
   260          body = {'id': 'autotest'}
   261          response = self.client.post(url, json.dumps(body), content_type='application/json',
   262                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   263          self.assertEqual(response.status_code, 201)
   264          app_id = response.data['id']  # noqa
   265          # test run
   266          url = '/v1/apps/{app_id}/run'.format(**locals())
   267          body = {'command': 'ls -al'}
   268          response = self.client.post(url, json.dumps(body), content_type='application/json',
   269                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   270          self.assertEquals(response.status_code, 400)
   271          self.assertEquals(response.data, {'detail': 'Support for admin commands '
   272                                                      'is not configured'})
   273  
   274      def test_run_without_release_should_error(self):
   275          """
   276          A user should not be able to run a one-off command unless a release
   277          is present.
   278          """
   279          app_id = 'autotest'
   280          url = '/v1/apps'
   281          body = {'id': app_id}
   282          response = self.client.post(url, json.dumps(body), content_type='application/json',
   283                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   284          url = '/v1/apps/{}/run'.format(app_id)
   285          body = {'command': 'ls -al'}
   286          response = self.client.post(url, json.dumps(body), content_type='application/json',
   287                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   288          self.assertEqual(response.status_code, 400)
   289          self.assertEqual(response.data, {'detail': 'No build associated with this '
   290                                                     'release to run this command'})
   291  
   292      def test_unauthorized_user_cannot_see_app(self):
   293          """
   294          An unauthorized user should not be able to access an app's resources.
   295  
   296          Since an unauthorized user can't access the application, these
   297          tests should return a 403, but currently return a 404. FIXME!
   298          """
   299          app_id = 'autotest'
   300          base_url = '/v1/apps'
   301          body = {'id': app_id}
   302          response = self.client.post(base_url, json.dumps(body), content_type='application/json',
   303                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   304          unauthorized_user = User.objects.get(username='autotest2')
   305          unauthorized_token = Token.objects.get(user=unauthorized_user).key
   306          url = '{}/{}/run'.format(base_url, app_id)
   307          body = {'command': 'foo'}
   308          response = self.client.post(url, json.dumps(body), content_type='application/json',
   309                                      HTTP_AUTHORIZATION='token {}'.format(unauthorized_token))
   310          self.assertEqual(response.status_code, 403)
   311          url = '{}/{}/logs'.format(base_url, app_id)
   312          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(unauthorized_token))
   313          self.assertEqual(response.status_code, 403)
   314          url = '{}/{}'.format(base_url, app_id)
   315          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(unauthorized_token))
   316          self.assertEqual(response.status_code, 403)
   317          response = self.client.delete(url,
   318                                        HTTP_AUTHORIZATION='token {}'.format(unauthorized_token))
   319          self.assertEqual(response.status_code, 403)
   320  
   321      def test_app_info_not_showing_wrong_app(self):
   322          app_id = 'autotest'
   323          base_url = '/v1/apps'
   324          body = {'id': app_id}
   325          response = self.client.post(base_url, json.dumps(body), content_type='application/json',
   326                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   327          url = '{}/foo'.format(base_url)
   328          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   329          self.assertEqual(response.status_code, 404)
   330  
   331      def test_app_transfer(self):
   332          owner = User.objects.get(username='autotest2')
   333          owner_token = Token.objects.get(user=owner).key
   334          app_id = 'autotest'
   335          base_url = '/v1/apps'
   336          body = {'id': app_id}
   337          response = self.client.post(base_url, json.dumps(body), content_type='application/json',
   338                                      HTTP_AUTHORIZATION='token {}'.format(owner_token))
   339          # Transfer App
   340          url = '{}/{}'.format(base_url, app_id)
   341          new_owner = User.objects.get(username='autotest3')
   342          new_owner_token = Token.objects.get(user=new_owner).key
   343          body = {'owner': new_owner.username}
   344          response = self.client.post(url, json.dumps(body), content_type='application/json',
   345                                      HTTP_AUTHORIZATION='token {}'.format(owner_token))
   346          self.assertEqual(response.status_code, 200)
   347  
   348          # Original user can no longer access it
   349          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(owner_token))
   350          self.assertEqual(response.status_code, 403)
   351  
   352          # New owner can access it
   353          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(new_owner_token))
   354          self.assertEqual(response.status_code, 200)
   355          self.assertEqual(response.data['owner'], new_owner.username)
   356  
   357          # Collaborators can't transfer
   358          body = {'username': owner.username}
   359          perms_url = url+"/perms/"
   360          response = self.client.post(perms_url, json.dumps(body), content_type='application/json',
   361                                      HTTP_AUTHORIZATION='token {}'.format(new_owner_token))
   362          self.assertEqual(response.status_code, 201)
   363          body = {'owner': self.user.username}
   364          response = self.client.post(url, json.dumps(body), content_type='application/json',
   365                                      HTTP_AUTHORIZATION='token {}'.format(owner_token))
   366          self.assertEqual(response.status_code, 403)
   367  
   368          # Admins can transfer
   369          body = {'owner': self.user.username}
   370          response = self.client.post(url, json.dumps(body), content_type='application/json',
   371                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   372          self.assertEqual(response.status_code, 200)
   373          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   374          self.assertEqual(response.status_code, 200)
   375          self.assertEqual(response.data['owner'], self.user.username)
   376  
   377  
   378  FAKE_LOG_DATA = """
   379  2013-08-15 12:41:25 [33454] [INFO] Starting gunicorn 17.5
   380  2013-08-15 12:41:25 [33454] [INFO] Listening at: http://0.0.0.0:5000 (33454)
   381  2013-08-15 12:41:25 [33454] [INFO] Using worker: sync
   382  2013-08-15 12:41:25 [33457] [INFO] Booting worker with pid 33457
   383  """