github.com/inflatablewoman/deis@v1.0.1-0.20141111034523-a4511c46a6ce/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 mock 11 import os.path 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 21 22 def mock_import_repository_task(*args, **kwargs): 23 resp = requests.Response() 24 resp.status_code = 200 25 resp._content_consumed = True 26 return resp 27 28 29 class AppTest(TestCase): 30 """Tests creation of applications""" 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 # provide mock authentication used for run commands 38 settings.SSH_PRIVATE_KEY = '<some-ssh-private-key>' 39 40 def tearDown(self): 41 # reset global vars for other tests 42 settings.SSH_PRIVATE_KEY = '' 43 44 def test_app(self): 45 """ 46 Test that a user can create, read, update and delete an application 47 """ 48 url = '/v1/apps' 49 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 50 self.assertEqual(response.status_code, 201) 51 app_id = response.data['id'] # noqa 52 self.assertIn('id', response.data) 53 self.assertIn('url', response.data) 54 self.assertEqual(response.data['url'], '{app_id}.deisapp.local'.format(**locals())) 55 response = self.client.get('/v1/apps', 56 HTTP_AUTHORIZATION='token {}'.format(self.token)) 57 self.assertEqual(response.status_code, 200) 58 self.assertEqual(len(response.data['results']), 1) 59 url = '/v1/apps/{app_id}'.format(**locals()) 60 response = self.client.get(url, 61 HTTP_AUTHORIZATION='token {}'.format(self.token)) 62 self.assertEqual(response.status_code, 200) 63 body = {'id': 'new'} 64 response = self.client.patch(url, json.dumps(body), content_type='application/json', 65 HTTP_AUTHORIZATION='token {}'.format(self.token)) 66 self.assertEqual(response.status_code, 405) 67 response = self.client.delete(url, 68 HTTP_AUTHORIZATION='token {}'.format(self.token)) 69 self.assertEqual(response.status_code, 204) 70 71 def test_app_override_id(self): 72 body = {'id': 'myid'} 73 response = self.client.post('/v1/apps', json.dumps(body), 74 content_type='application/json', 75 HTTP_AUTHORIZATION='token {}'.format(self.token)) 76 self.assertEqual(response.status_code, 201) 77 body = {'id': response.data['id']} 78 response = self.client.post('/v1/apps', json.dumps(body), 79 content_type='application/json', 80 HTTP_AUTHORIZATION='token {}'.format(self.token)) 81 self.assertContains(response, 'App with this Id already exists.', status_code=400) 82 return response 83 84 def test_app_actions(self): 85 url = '/v1/apps' 86 body = {'id': 'autotest'} 87 response = self.client.post(url, json.dumps(body), content_type='application/json', 88 HTTP_AUTHORIZATION='token {}'.format(self.token)) 89 self.assertEqual(response.status_code, 201) 90 app_id = response.data['id'] # noqa 91 # test logs 92 if not os.path.exists(settings.DEIS_LOG_DIR): 93 os.mkdir(settings.DEIS_LOG_DIR) 94 path = os.path.join(settings.DEIS_LOG_DIR, app_id + '.log') 95 # HACK: remove app lifecycle logs 96 if os.path.exists(path): 97 os.remove(path) 98 url = '/v1/apps/{app_id}/logs'.format(**locals()) 99 response = self.client.get(url, 100 HTTP_AUTHORIZATION='token {}'.format(self.token)) 101 self.assertEqual(response.status_code, 204) 102 self.assertEqual(response.data, 'No logs for {}'.format(app_id)) 103 # write out some fake log data and try again 104 with open(path, 'a') as f: 105 f.write(FAKE_LOG_DATA) 106 response = self.client.get(url, 107 HTTP_AUTHORIZATION='token {}'.format(self.token)) 108 self.assertEqual(response.status_code, 200) 109 self.assertEqual(response.data, FAKE_LOG_DATA) 110 os.remove(path) 111 # TODO: test run needs an initial build 112 113 def test_app_release_notes_in_logs(self): 114 """Verifies that an app's release summary is dumped into the logs.""" 115 url = '/v1/apps' 116 body = {'id': 'autotest'} 117 response = self.client.post(url, json.dumps(body), content_type='application/json', 118 HTTP_AUTHORIZATION='token {}'.format(self.token)) 119 self.assertEqual(response.status_code, 201) 120 app_id = response.data['id'] # noqa 121 path = os.path.join(settings.DEIS_LOG_DIR, app_id + '.log') 122 url = '/v1/apps/{app_id}/logs'.format(**locals()) 123 response = self.client.get(url, 124 HTTP_AUTHORIZATION='token {}'.format(self.token)) 125 self.assertIn('autotest created initial release', response.data) 126 self.assertEqual(response.status_code, 200) 127 # delete file for future runs 128 os.remove(path) 129 130 def test_app_errors(self): 131 app_id = 'autotest-errors' 132 url = '/v1/apps' 133 body = {'id': 'camelCase'} 134 response = self.client.post(url, json.dumps(body), content_type='application/json', 135 HTTP_AUTHORIZATION='token {}'.format(self.token)) 136 self.assertContains(response, 'App IDs can only contain [a-z0-9-]', status_code=400) 137 url = '/v1/apps' 138 body = {'id': 'deis'} 139 response = self.client.post(url, json.dumps(body), content_type='application/json', 140 HTTP_AUTHORIZATION='token {}'.format(self.token)) 141 self.assertContains(response, "App IDs cannot be 'deis'", status_code=400) 142 body = {'id': app_id} 143 response = self.client.post(url, json.dumps(body), content_type='application/json', 144 HTTP_AUTHORIZATION='token {}'.format(self.token)) 145 self.assertEqual(response.status_code, 201) 146 app_id = response.data['id'] # noqa 147 url = '/v1/apps/{app_id}'.format(**locals()) 148 response = self.client.delete(url, 149 HTTP_AUTHORIZATION='token {}'.format(self.token)) 150 self.assertEquals(response.status_code, 204) 151 for endpoint in ('containers', 'config', 'releases', 'builds'): 152 url = '/v1/apps/{app_id}/{endpoint}'.format(**locals()) 153 response = self.client.get(url, 154 HTTP_AUTHORIZATION='token {}'.format(self.token)) 155 self.assertEquals(response.status_code, 404) 156 157 def test_app_structure_is_valid_json(self): 158 """Application structures should be valid JSON objects.""" 159 url = '/v1/apps' 160 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 161 self.assertEqual(response.status_code, 201) 162 app_id = response.data['id'] 163 self.assertIn('structure', response.data) 164 self.assertEqual(response.data['structure'], {}) 165 app = App.objects.get(id=app_id) 166 app.structure = {'web': 1} 167 app.save() 168 url = '/v1/apps/{}'.format(app_id) 169 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 170 self.assertIn('structure', response.data) 171 self.assertEqual(response.data['structure'], {"web": 1}) 172 173 @mock.patch('requests.post', mock_import_repository_task) 174 def test_admin_can_manage_other_apps(self): 175 """Administrators of Deis should be able to manage all applications. 176 """ 177 # log in as non-admin user and create an app 178 user = User.objects.get(username='autotest2') 179 token = Token.objects.get(user=user) 180 app_id = 'autotest' 181 url = '/v1/apps' 182 body = {'id': app_id} 183 response = self.client.post(url, json.dumps(body), content_type='application/json', 184 HTTP_AUTHORIZATION='token {}'.format(token)) 185 app = App.objects.get(id=app_id) 186 # log in as admin, check to see if they have access 187 url = '/v1/apps/{}'.format(app_id) 188 response = self.client.get(url, 189 HTTP_AUTHORIZATION='token {}'.format(self.token)) 190 self.assertEqual(response.status_code, 200) 191 # check app logs 192 url = '/v1/apps/{app_id}/logs'.format(**locals()) 193 response = self.client.get(url, 194 HTTP_AUTHORIZATION='token {}'.format(self.token)) 195 self.assertEqual(response.status_code, 200) 196 self.assertIn('autotest2 created initial release', response.data) 197 # TODO: test run needs an initial build 198 # delete the app 199 url = '/v1/apps/{}'.format(app_id) 200 response = self.client.delete(url, 201 HTTP_AUTHORIZATION='token {}'.format(self.token)) 202 self.assertEqual(response.status_code, 204) 203 204 def test_admin_can_see_other_apps(self): 205 """If a user creates an application, the administrator should be able 206 to see it. 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 217 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 218 self.assertEqual(response.data['count'], 1) 219 220 def test_run_without_auth(self): 221 """If the administrator has not provided SSH private key for run commands, 222 make sure a friendly error message is provided on run""" 223 settings.SSH_PRIVATE_KEY = '' 224 url = '/v1/apps' 225 body = {'id': 'autotest'} 226 response = self.client.post(url, json.dumps(body), content_type='application/json', 227 HTTP_AUTHORIZATION='token {}'.format(self.token)) 228 self.assertEqual(response.status_code, 201) 229 app_id = response.data['id'] # noqa 230 # test run 231 url = '/v1/apps/{app_id}/run'.format(**locals()) 232 body = {'command': 'ls -al'} 233 response = self.client.post(url, json.dumps(body), content_type='application/json', 234 HTTP_AUTHORIZATION='token {}'.format(self.token)) 235 self.assertEquals(response.status_code, 400) 236 self.assertEquals(response.data, 'Support for admin commands is not configured') 237 238 def test_run_without_release_should_error(self): 239 """ 240 A user should not be able to run a one-off command unless a release 241 is present. 242 """ 243 app_id = 'autotest' 244 url = '/v1/apps' 245 body = {'id': app_id} 246 response = self.client.post(url, json.dumps(body), content_type='application/json', 247 HTTP_AUTHORIZATION='token {}'.format(self.token)) 248 url = '/v1/apps/{}/run'.format(app_id) 249 body = {'command': 'ls -al'} 250 response = self.client.post(url, json.dumps(body), content_type='application/json', 251 HTTP_AUTHORIZATION='token {}'.format(self.token)) 252 self.assertEqual(response.status_code, 400) 253 self.assertEqual(response.data, "No build associated with this release " 254 "to run this command") 255 256 257 FAKE_LOG_DATA = """ 258 2013-08-15 12:41:25 [33454] [INFO] Starting gunicorn 17.5 259 2013-08-15 12:41:25 [33454] [INFO] Listening at: http://0.0.0.0:5000 (33454) 260 2013-08-15 12:41:25 [33454] [INFO] Using worker: sync 261 2013-08-15 12:41:25 [33457] [INFO] Booting worker with pid 33457 262 """