github.com/amrnt/deis@v1.3.1/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 response = self.client.get('/v1/apps', 54 HTTP_AUTHORIZATION='token {}'.format(self.token)) 55 self.assertEqual(response.status_code, 200) 56 self.assertEqual(len(response.data['results']), 1) 57 url = '/v1/apps/{app_id}'.format(**locals()) 58 response = self.client.get(url, 59 HTTP_AUTHORIZATION='token {}'.format(self.token)) 60 self.assertEqual(response.status_code, 200) 61 body = {'id': 'new'} 62 response = self.client.patch(url, json.dumps(body), content_type='application/json', 63 HTTP_AUTHORIZATION='token {}'.format(self.token)) 64 self.assertEqual(response.status_code, 405) 65 response = self.client.delete(url, 66 HTTP_AUTHORIZATION='token {}'.format(self.token)) 67 self.assertEqual(response.status_code, 204) 68 69 def test_response_data(self): 70 """Test that the serialized response contains only relevant data.""" 71 body = {'id': 'test'} 72 response = self.client.post('/v1/apps', json.dumps(body), 73 content_type='application/json', 74 HTTP_AUTHORIZATION='token {}'.format(self.token)) 75 for key in response.data.keys(): 76 self.assertIn(key, ['uuid', 'created', 'updated', 'id', 'owner', 'url', 'structure']) 77 expected = { 78 'id': 'test', 79 'owner': self.user.username, 80 'url': 'test.deisapp.local', 81 'structure': {} 82 } 83 self.assertDictContainsSubset(expected, response.data) 84 85 def test_app_override_id(self): 86 body = {'id': 'myid'} 87 response = self.client.post('/v1/apps', json.dumps(body), 88 content_type='application/json', 89 HTTP_AUTHORIZATION='token {}'.format(self.token)) 90 self.assertEqual(response.status_code, 201) 91 body = {'id': response.data['id']} 92 response = self.client.post('/v1/apps', json.dumps(body), 93 content_type='application/json', 94 HTTP_AUTHORIZATION='token {}'.format(self.token)) 95 self.assertContains(response, 'This field must be unique.', status_code=400) 96 return response 97 98 def test_app_actions(self): 99 url = '/v1/apps' 100 body = {'id': 'autotest'} 101 response = self.client.post(url, json.dumps(body), content_type='application/json', 102 HTTP_AUTHORIZATION='token {}'.format(self.token)) 103 self.assertEqual(response.status_code, 201) 104 app_id = response.data['id'] # noqa 105 # test logs 106 if not os.path.exists(settings.DEIS_LOG_DIR): 107 os.mkdir(settings.DEIS_LOG_DIR) 108 path = os.path.join(settings.DEIS_LOG_DIR, app_id + '.log') 109 # HACK: remove app lifecycle logs 110 if os.path.exists(path): 111 os.remove(path) 112 url = '/v1/apps/{app_id}/logs'.format(**locals()) 113 response = self.client.get(url, 114 HTTP_AUTHORIZATION='token {}'.format(self.token)) 115 self.assertEqual(response.status_code, 204) 116 self.assertEqual(response.data, 'No logs for {}'.format(app_id)) 117 # write out some fake log data and try again 118 with open(path, 'a') as f: 119 f.write(FAKE_LOG_DATA) 120 response = self.client.get(url, 121 HTTP_AUTHORIZATION='token {}'.format(self.token)) 122 self.assertEqual(response.status_code, 200) 123 self.assertEqual(response.data, FAKE_LOG_DATA) 124 os.remove(path) 125 # TODO: test run needs an initial build 126 127 def test_app_release_notes_in_logs(self): 128 """Verifies that an app's release summary is dumped into the logs.""" 129 url = '/v1/apps' 130 body = {'id': 'autotest'} 131 response = self.client.post(url, json.dumps(body), content_type='application/json', 132 HTTP_AUTHORIZATION='token {}'.format(self.token)) 133 self.assertEqual(response.status_code, 201) 134 app_id = response.data['id'] # noqa 135 path = os.path.join(settings.DEIS_LOG_DIR, app_id + '.log') 136 url = '/v1/apps/{app_id}/logs'.format(**locals()) 137 response = self.client.get(url, 138 HTTP_AUTHORIZATION='token {}'.format(self.token)) 139 self.assertIn('autotest created initial release', response.data) 140 self.assertEqual(response.status_code, 200) 141 # delete file for future runs 142 os.remove(path) 143 144 def test_app_errors(self): 145 app_id = 'autotest-errors' 146 url = '/v1/apps' 147 body = {'id': 'camelCase'} 148 response = self.client.post(url, json.dumps(body), content_type='application/json', 149 HTTP_AUTHORIZATION='token {}'.format(self.token)) 150 self.assertContains(response, 'App IDs can only contain [a-z0-9-]', status_code=400) 151 url = '/v1/apps' 152 body = {'id': 'deis'} 153 response = self.client.post(url, json.dumps(body), content_type='application/json', 154 HTTP_AUTHORIZATION='token {}'.format(self.token)) 155 self.assertContains(response, 'deis is a reserved name.', status_code=400) 156 body = {'id': app_id} 157 response = self.client.post(url, json.dumps(body), content_type='application/json', 158 HTTP_AUTHORIZATION='token {}'.format(self.token)) 159 self.assertEqual(response.status_code, 201) 160 app_id = response.data['id'] # noqa 161 url = '/v1/apps/{app_id}'.format(**locals()) 162 response = self.client.delete(url, 163 HTTP_AUTHORIZATION='token {}'.format(self.token)) 164 self.assertEquals(response.status_code, 204) 165 for endpoint in ('containers', 'config', 'releases', 'builds'): 166 url = '/v1/apps/{app_id}/{endpoint}'.format(**locals()) 167 response = self.client.get(url, 168 HTTP_AUTHORIZATION='token {}'.format(self.token)) 169 self.assertEquals(response.status_code, 404) 170 171 def test_app_structure_is_valid_json(self): 172 """Application structures should be valid JSON objects.""" 173 url = '/v1/apps' 174 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 175 self.assertEqual(response.status_code, 201) 176 app_id = response.data['id'] 177 self.assertIn('structure', response.data) 178 self.assertEqual(response.data['structure'], {}) 179 app = App.objects.get(id=app_id) 180 app.structure = {'web': 1} 181 app.save() 182 url = '/v1/apps/{}'.format(app_id) 183 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 184 self.assertIn('structure', response.data) 185 self.assertEqual(response.data['structure'], {"web": 1}) 186 187 @mock.patch('requests.post', mock_import_repository_task) 188 def test_admin_can_manage_other_apps(self): 189 """Administrators of Deis should be able to manage all applications. 190 """ 191 # log in as non-admin user and create an app 192 user = User.objects.get(username='autotest2') 193 token = Token.objects.get(user=user) 194 app_id = 'autotest' 195 url = '/v1/apps' 196 body = {'id': app_id} 197 response = self.client.post(url, json.dumps(body), content_type='application/json', 198 HTTP_AUTHORIZATION='token {}'.format(token)) 199 app = App.objects.get(id=app_id) 200 # log in as admin, check to see if they have access 201 url = '/v1/apps/{}'.format(app_id) 202 response = self.client.get(url, 203 HTTP_AUTHORIZATION='token {}'.format(self.token)) 204 self.assertEqual(response.status_code, 200) 205 # check app logs 206 url = '/v1/apps/{app_id}/logs'.format(**locals()) 207 response = self.client.get(url, 208 HTTP_AUTHORIZATION='token {}'.format(self.token)) 209 self.assertEqual(response.status_code, 200) 210 self.assertIn('autotest2 created initial release', response.data) 211 # TODO: test run needs an initial build 212 # delete the app 213 url = '/v1/apps/{}'.format(app_id) 214 response = self.client.delete(url, 215 HTTP_AUTHORIZATION='token {}'.format(self.token)) 216 self.assertEqual(response.status_code, 204) 217 218 def test_admin_can_see_other_apps(self): 219 """If a user creates an application, the administrator should be able 220 to see it. 221 """ 222 # log in as non-admin user and create an app 223 user = User.objects.get(username='autotest2') 224 token = Token.objects.get(user=user) 225 app_id = 'autotest' 226 url = '/v1/apps' 227 body = {'id': app_id} 228 response = self.client.post(url, json.dumps(body), content_type='application/json', 229 HTTP_AUTHORIZATION='token {}'.format(token)) 230 # log in as admin 231 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 232 self.assertEqual(response.data['count'], 1) 233 234 def test_run_without_auth(self): 235 """If the administrator has not provided SSH private key for run commands, 236 make sure a friendly error message is provided on run""" 237 settings.SSH_PRIVATE_KEY = '' 238 url = '/v1/apps' 239 body = {'id': 'autotest'} 240 response = self.client.post(url, json.dumps(body), content_type='application/json', 241 HTTP_AUTHORIZATION='token {}'.format(self.token)) 242 self.assertEqual(response.status_code, 201) 243 app_id = response.data['id'] # noqa 244 # test run 245 url = '/v1/apps/{app_id}/run'.format(**locals()) 246 body = {'command': 'ls -al'} 247 response = self.client.post(url, json.dumps(body), content_type='application/json', 248 HTTP_AUTHORIZATION='token {}'.format(self.token)) 249 self.assertEquals(response.status_code, 400) 250 self.assertEquals(response.data, {'detail': 'Support for admin commands ' 251 'is not configured'}) 252 253 def test_run_without_release_should_error(self): 254 """ 255 A user should not be able to run a one-off command unless a release 256 is present. 257 """ 258 app_id = 'autotest' 259 url = '/v1/apps' 260 body = {'id': app_id} 261 response = self.client.post(url, json.dumps(body), content_type='application/json', 262 HTTP_AUTHORIZATION='token {}'.format(self.token)) 263 url = '/v1/apps/{}/run'.format(app_id) 264 body = {'command': 'ls -al'} 265 response = self.client.post(url, json.dumps(body), content_type='application/json', 266 HTTP_AUTHORIZATION='token {}'.format(self.token)) 267 self.assertEqual(response.status_code, 400) 268 self.assertEqual(response.data, {'detail': 'No build associated with this ' 269 'release to run this command'}) 270 271 def test_unauthorized_user_cannot_see_app(self): 272 """ 273 An unauthorized user should not be able to access an app's resources. 274 275 Since an unauthorized user can't access the application, these 276 tests should return a 403, but currently return a 404. FIXME! 277 """ 278 app_id = 'autotest' 279 base_url = '/v1/apps' 280 body = {'id': app_id} 281 response = self.client.post(base_url, json.dumps(body), content_type='application/json', 282 HTTP_AUTHORIZATION='token {}'.format(self.token)) 283 unauthorized_user = User.objects.get(username='autotest2') 284 unauthorized_token = Token.objects.get(user=unauthorized_user).key 285 url = '{}/{}/run'.format(base_url, app_id) 286 body = {'command': 'foo'} 287 response = self.client.post(url, json.dumps(body), content_type='application/json', 288 HTTP_AUTHORIZATION='token {}'.format(unauthorized_token)) 289 self.assertEqual(response.status_code, 403) 290 url = '{}/{}/logs'.format(base_url, app_id) 291 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(unauthorized_token)) 292 self.assertEqual(response.status_code, 403) 293 url = '{}/{}'.format(base_url, app_id) 294 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(unauthorized_token)) 295 self.assertEqual(response.status_code, 403) 296 response = self.client.delete(url, 297 HTTP_AUTHORIZATION='token {}'.format(unauthorized_token)) 298 self.assertEqual(response.status_code, 403) 299 300 def test_app_info_not_showing_wrong_app(self): 301 app_id = 'autotest' 302 base_url = '/v1/apps' 303 body = {'id': app_id} 304 response = self.client.post(base_url, json.dumps(body), content_type='application/json', 305 HTTP_AUTHORIZATION='token {}'.format(self.token)) 306 url = '{}/foo'.format(base_url) 307 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 308 self.assertEqual(response.status_code, 404) 309 310 311 FAKE_LOG_DATA = """ 312 2013-08-15 12:41:25 [33454] [INFO] Starting gunicorn 17.5 313 2013-08-15 12:41:25 [33454] [INFO] Listening at: http://0.0.0.0:5000 (33454) 314 2013-08-15 12:41:25 [33454] [INFO] Using worker: sync 315 2013-08-15 12:41:25 [33457] [INFO] Booting worker with pid 33457 316 """