github.com/spg/deis@v1.7.3/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: 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 125 # test with log_lines 126 response = self.client.get(url + "?log_lines=1", 127 HTTP_AUTHORIZATION='token {}'.format(self.token)) 128 self.assertEqual(response.status_code, 200) 129 self.assertEqual(response.data, FAKE_LOG_DATA.splitlines(True)[4]) 130 131 os.remove(path) 132 # TODO: test run needs an initial build 133 134 def test_app_release_notes_in_logs(self): 135 """Verifies that an app's release summary is dumped into the logs.""" 136 url = '/v1/apps' 137 body = {'id': 'autotest'} 138 response = self.client.post(url, json.dumps(body), content_type='application/json', 139 HTTP_AUTHORIZATION='token {}'.format(self.token)) 140 self.assertEqual(response.status_code, 201) 141 app_id = response.data['id'] # noqa 142 path = os.path.join(settings.DEIS_LOG_DIR, app_id + '.log') 143 url = '/v1/apps/{app_id}/logs'.format(**locals()) 144 response = self.client.get(url, 145 HTTP_AUTHORIZATION='token {}'.format(self.token)) 146 self.assertIn('autotest created initial release', response.data) 147 self.assertEqual(response.status_code, 200) 148 # delete file for future runs 149 os.remove(path) 150 151 def test_app_errors(self): 152 app_id = 'autotest-errors' 153 url = '/v1/apps' 154 body = {'id': 'camelCase'} 155 response = self.client.post(url, json.dumps(body), content_type='application/json', 156 HTTP_AUTHORIZATION='token {}'.format(self.token)) 157 self.assertContains(response, 'App IDs can only contain [a-z0-9-]', status_code=400) 158 url = '/v1/apps' 159 body = {'id': app_id} 160 response = self.client.post(url, json.dumps(body), content_type='application/json', 161 HTTP_AUTHORIZATION='token {}'.format(self.token)) 162 self.assertEqual(response.status_code, 201) 163 app_id = response.data['id'] # noqa 164 url = '/v1/apps/{app_id}'.format(**locals()) 165 response = self.client.delete(url, 166 HTTP_AUTHORIZATION='token {}'.format(self.token)) 167 self.assertEquals(response.status_code, 204) 168 for endpoint in ('containers', 'config', 'releases', 'builds'): 169 url = '/v1/apps/{app_id}/{endpoint}'.format(**locals()) 170 response = self.client.get(url, 171 HTTP_AUTHORIZATION='token {}'.format(self.token)) 172 self.assertEquals(response.status_code, 404) 173 174 def test_app_reserved_names(self): 175 """Nobody should be able to create applications with names which are reserved.""" 176 url = '/v1/apps' 177 reserved_names = ['foo', 'bar'] 178 with self.settings(DEIS_RESERVED_NAMES=reserved_names): 179 for name in reserved_names: 180 body = {'id': name} 181 response = self.client.post(url, json.dumps(body), content_type='application/json', 182 HTTP_AUTHORIZATION='token {}'.format(self.token)) 183 self.assertContains( 184 response, 185 '{} is a reserved name.'.format(name), 186 status_code=400) 187 188 def test_app_structure_is_valid_json(self): 189 """Application structures should be valid JSON objects.""" 190 url = '/v1/apps' 191 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 192 self.assertEqual(response.status_code, 201) 193 app_id = response.data['id'] 194 self.assertIn('structure', response.data) 195 self.assertEqual(response.data['structure'], {}) 196 app = App.objects.get(id=app_id) 197 app.structure = {'web': 1} 198 app.save() 199 url = '/v1/apps/{}'.format(app_id) 200 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 201 self.assertIn('structure', response.data) 202 self.assertEqual(response.data['structure'], {"web": 1}) 203 204 @mock.patch('requests.post', mock_import_repository_task) 205 def test_admin_can_manage_other_apps(self): 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 app = App.objects.get(id=app_id) 217 # log in as admin, check to see if they have access 218 url = '/v1/apps/{}'.format(app_id) 219 response = self.client.get(url, 220 HTTP_AUTHORIZATION='token {}'.format(self.token)) 221 self.assertEqual(response.status_code, 200) 222 # check app logs 223 url = '/v1/apps/{app_id}/logs'.format(**locals()) 224 response = self.client.get(url, 225 HTTP_AUTHORIZATION='token {}'.format(self.token)) 226 self.assertEqual(response.status_code, 200) 227 self.assertIn('autotest2 created initial release', response.data) 228 # TODO: test run needs an initial build 229 # delete the app 230 url = '/v1/apps/{}'.format(app_id) 231 response = self.client.delete(url, 232 HTTP_AUTHORIZATION='token {}'.format(self.token)) 233 self.assertEqual(response.status_code, 204) 234 235 def test_admin_can_see_other_apps(self): 236 """If a user creates an application, the administrator should be able 237 to see it. 238 """ 239 # log in as non-admin user and create an app 240 user = User.objects.get(username='autotest2') 241 token = Token.objects.get(user=user) 242 app_id = 'autotest' 243 url = '/v1/apps' 244 body = {'id': app_id} 245 response = self.client.post(url, json.dumps(body), content_type='application/json', 246 HTTP_AUTHORIZATION='token {}'.format(token)) 247 # log in as admin 248 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 249 self.assertEqual(response.data['count'], 1) 250 251 def test_run_without_auth(self): 252 """If the administrator has not provided SSH private key for run commands, 253 make sure a friendly error message is provided on run""" 254 settings.SSH_PRIVATE_KEY = '' 255 url = '/v1/apps' 256 body = {'id': 'autotest'} 257 response = self.client.post(url, json.dumps(body), content_type='application/json', 258 HTTP_AUTHORIZATION='token {}'.format(self.token)) 259 self.assertEqual(response.status_code, 201) 260 app_id = response.data['id'] # noqa 261 # test run 262 url = '/v1/apps/{app_id}/run'.format(**locals()) 263 body = {'command': 'ls -al'} 264 response = self.client.post(url, json.dumps(body), content_type='application/json', 265 HTTP_AUTHORIZATION='token {}'.format(self.token)) 266 self.assertEquals(response.status_code, 400) 267 self.assertEquals(response.data, {'detail': 'Support for admin commands ' 268 'is not configured'}) 269 270 def test_run_without_release_should_error(self): 271 """ 272 A user should not be able to run a one-off command unless a release 273 is present. 274 """ 275 app_id = 'autotest' 276 url = '/v1/apps' 277 body = {'id': app_id} 278 response = self.client.post(url, json.dumps(body), content_type='application/json', 279 HTTP_AUTHORIZATION='token {}'.format(self.token)) 280 url = '/v1/apps/{}/run'.format(app_id) 281 body = {'command': 'ls -al'} 282 response = self.client.post(url, json.dumps(body), content_type='application/json', 283 HTTP_AUTHORIZATION='token {}'.format(self.token)) 284 self.assertEqual(response.status_code, 400) 285 self.assertEqual(response.data, {'detail': 'No build associated with this ' 286 'release to run this command'}) 287 288 def test_unauthorized_user_cannot_see_app(self): 289 """ 290 An unauthorized user should not be able to access an app's resources. 291 292 Since an unauthorized user can't access the application, these 293 tests should return a 403, but currently return a 404. FIXME! 294 """ 295 app_id = 'autotest' 296 base_url = '/v1/apps' 297 body = {'id': app_id} 298 response = self.client.post(base_url, json.dumps(body), content_type='application/json', 299 HTTP_AUTHORIZATION='token {}'.format(self.token)) 300 unauthorized_user = User.objects.get(username='autotest2') 301 unauthorized_token = Token.objects.get(user=unauthorized_user).key 302 url = '{}/{}/run'.format(base_url, app_id) 303 body = {'command': 'foo'} 304 response = self.client.post(url, json.dumps(body), content_type='application/json', 305 HTTP_AUTHORIZATION='token {}'.format(unauthorized_token)) 306 self.assertEqual(response.status_code, 403) 307 url = '{}/{}/logs'.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 url = '{}/{}'.format(base_url, app_id) 311 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(unauthorized_token)) 312 self.assertEqual(response.status_code, 403) 313 response = self.client.delete(url, 314 HTTP_AUTHORIZATION='token {}'.format(unauthorized_token)) 315 self.assertEqual(response.status_code, 403) 316 317 def test_app_info_not_showing_wrong_app(self): 318 app_id = 'autotest' 319 base_url = '/v1/apps' 320 body = {'id': app_id} 321 response = self.client.post(base_url, json.dumps(body), content_type='application/json', 322 HTTP_AUTHORIZATION='token {}'.format(self.token)) 323 url = '{}/foo'.format(base_url) 324 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 325 self.assertEqual(response.status_code, 404) 326 327 328 FAKE_LOG_DATA = """ 329 2013-08-15 12:41:25 [33454] [INFO] Starting gunicorn 17.5 330 2013-08-15 12:41:25 [33454] [INFO] Listening at: http://0.0.0.0:5000 (33454) 331 2013-08-15 12:41:25 [33454] [INFO] Using worker: sync 332 2013-08-15 12:41:25 [33457] [INFO] Booting worker with pid 33457 333 """