##// END OF EJS Templates
tests: moved tests of admin user auth tokens into pyramid apps.
marcink -
r1519:897366ac default
parent child Browse files
Show More
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,114 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.model.db import User, UserApiKeys
24
25 from rhodecode.apps._base import ADMIN_PREFIX
26 from rhodecode.tests import (
27 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.utils import AssertResponse
30
31 fixture = Fixture()
32
33
34
35 def route_path(name, **kwargs):
36 return {
37 'users':
38 ADMIN_PREFIX + '/users',
39 'users_data':
40 ADMIN_PREFIX + '/users_data',
41 'edit_user_auth_tokens':
42 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
43 'edit_user_auth_tokens_add':
44 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
45 'edit_user_auth_tokens_delete':
46 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
47 }[name].format(**kwargs)
48
49
50 class TestAdminUsersView(TestController):
51
52 def test_auth_tokens_default_user(self):
53 self.log_user()
54 user = User.get_default_user()
55 response = self.app.get(
56 route_path('edit_user_auth_tokens', user_id=user.user_id),
57 status=302)
58
59 def test_auth_tokens(self):
60 self.log_user()
61
62 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
63 response = self.app.get(
64 route_path('edit_user_auth_tokens', user_id=user.user_id))
65 for token in user.auth_tokens:
66 response.mustcontain(token)
67 response.mustcontain('never')
68
69 @pytest.mark.parametrize("desc, lifetime", [
70 ('forever', -1),
71 ('5mins', 60*5),
72 ('30days', 60*60*24*30),
73 ])
74 def test_add_auth_token(self, desc, lifetime, user_util):
75 self.log_user()
76 user = user_util.create_user()
77 user_id = user.user_id
78
79 response = self.app.post(
80 route_path('edit_user_auth_tokens_add', user_id=user_id),
81 {'description': desc, 'lifetime': lifetime,
82 'csrf_token': self.csrf_token})
83 assert_session_flash(response, 'Auth token successfully created')
84
85 response = response.follow()
86 user = User.get(user_id)
87 for auth_token in user.auth_tokens:
88 response.mustcontain(auth_token)
89
90 def test_delete_auth_token(self, user_util):
91 self.log_user()
92 user = user_util.create_user()
93 user_id = user.user_id
94 keys = user.extra_auth_tokens
95 assert 2 == len(keys)
96
97 response = self.app.post(
98 route_path('edit_user_auth_tokens_add', user_id=user_id),
99 {'description': 'desc', 'lifetime': -1,
100 'csrf_token': self.csrf_token})
101 assert_session_flash(response, 'Auth token successfully created')
102 response.follow()
103
104 # now delete our key
105 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
106 assert 3 == len(keys)
107
108 response = self.app.post(
109 route_path('edit_user_auth_tokens_delete', user_id=user_id),
110 {'del_auth_token': keys[0].api_key, 'csrf_token': self.csrf_token})
111
112 assert_session_flash(response, 'Auth token successfully deleted')
113 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
114 assert 2 == len(keys)
@@ -1,620 +1,561 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from sqlalchemy.orm.exc import NoResultFound
22 from sqlalchemy.orm.exc import NoResultFound
23
23
24 from rhodecode.lib.auth import check_password
24 from rhodecode.lib.auth import check_password
25 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
26 from rhodecode.model import validators
26 from rhodecode.model import validators
27 from rhodecode.model.db import User, UserIpMap, UserApiKeys
27 from rhodecode.model.db import User, UserIpMap, UserApiKeys
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.user import UserModel
29 from rhodecode.model.user import UserModel
30 from rhodecode.tests import (
30 from rhodecode.tests import (
31 TestController, url, link_to, TEST_USER_ADMIN_LOGIN,
31 TestController, url, link_to, TEST_USER_ADMIN_LOGIN,
32 TEST_USER_REGULAR_LOGIN, assert_session_flash)
32 TEST_USER_REGULAR_LOGIN, assert_session_flash)
33 from rhodecode.tests.fixture import Fixture
33 from rhodecode.tests.fixture import Fixture
34 from rhodecode.tests.utils import AssertResponse
34 from rhodecode.tests.utils import AssertResponse
35
35
36 fixture = Fixture()
36 fixture = Fixture()
37
37
38
38
39 class TestAdminUsersController(TestController):
39 class TestAdminUsersController(TestController):
40 test_user_1 = 'testme'
40 test_user_1 = 'testme'
41 destroy_users = set()
41 destroy_users = set()
42
42
43 @classmethod
43 @classmethod
44 def teardown_method(cls, method):
44 def teardown_method(cls, method):
45 fixture.destroy_users(cls.destroy_users)
45 fixture.destroy_users(cls.destroy_users)
46
46
47 def test_index(self):
48 self.log_user()
49 self.app.get(url('users'))
50
51 def test_create(self):
47 def test_create(self):
52 self.log_user()
48 self.log_user()
53 username = 'newtestuser'
49 username = 'newtestuser'
54 password = 'test12'
50 password = 'test12'
55 password_confirmation = password
51 password_confirmation = password
56 name = 'name'
52 name = 'name'
57 lastname = 'lastname'
53 lastname = 'lastname'
58 email = 'mail@mail.com'
54 email = 'mail@mail.com'
59
55
60 response = self.app.get(url('new_user'))
56 response = self.app.get(url('new_user'))
61
57
62 response = self.app.post(url('users'), params={
58 response = self.app.post(url('users'), params={
63 'username': username,
59 'username': username,
64 'password': password,
60 'password': password,
65 'password_confirmation': password_confirmation,
61 'password_confirmation': password_confirmation,
66 'firstname': name,
62 'firstname': name,
67 'active': True,
63 'active': True,
68 'lastname': lastname,
64 'lastname': lastname,
69 'extern_name': 'rhodecode',
65 'extern_name': 'rhodecode',
70 'extern_type': 'rhodecode',
66 'extern_type': 'rhodecode',
71 'email': email,
67 'email': email,
72 'csrf_token': self.csrf_token,
68 'csrf_token': self.csrf_token,
73 })
69 })
74 user_link = link_to(
70 user_link = link_to(
75 username,
71 username,
76 url('edit_user', user_id=User.get_by_username(username).user_id))
72 url('edit_user', user_id=User.get_by_username(username).user_id))
77 assert_session_flash(response, 'Created user %s' % (user_link,))
73 assert_session_flash(response, 'Created user %s' % (user_link,))
78 self.destroy_users.add(username)
74 self.destroy_users.add(username)
79
75
80 new_user = User.query().filter(User.username == username).one()
76 new_user = User.query().filter(User.username == username).one()
81
77
82 assert new_user.username == username
78 assert new_user.username == username
83 assert check_password(password, new_user.password)
79 assert check_password(password, new_user.password)
84 assert new_user.name == name
80 assert new_user.name == name
85 assert new_user.lastname == lastname
81 assert new_user.lastname == lastname
86 assert new_user.email == email
82 assert new_user.email == email
87
83
88 response.follow()
84 response.follow()
89 response = response.follow()
85 response = response.follow()
90 response.mustcontain(username)
86 response.mustcontain(username)
91
87
92 def test_create_err(self):
88 def test_create_err(self):
93 self.log_user()
89 self.log_user()
94 username = 'new_user'
90 username = 'new_user'
95 password = ''
91 password = ''
96 name = 'name'
92 name = 'name'
97 lastname = 'lastname'
93 lastname = 'lastname'
98 email = 'errmail.com'
94 email = 'errmail.com'
99
95
100 response = self.app.get(url('new_user'))
96 response = self.app.get(url('new_user'))
101
97
102 response = self.app.post(url('users'), params={
98 response = self.app.post(url('users'), params={
103 'username': username,
99 'username': username,
104 'password': password,
100 'password': password,
105 'name': name,
101 'name': name,
106 'active': False,
102 'active': False,
107 'lastname': lastname,
103 'lastname': lastname,
108 'email': email,
104 'email': email,
109 'csrf_token': self.csrf_token,
105 'csrf_token': self.csrf_token,
110 })
106 })
111
107
112 msg = validators.ValidUsername(
108 msg = validators.ValidUsername(
113 False, {})._messages['system_invalid_username']
109 False, {})._messages['system_invalid_username']
114 msg = h.html_escape(msg % {'username': 'new_user'})
110 msg = h.html_escape(msg % {'username': 'new_user'})
115 response.mustcontain('<span class="error-message">%s</span>' % msg)
111 response.mustcontain('<span class="error-message">%s</span>' % msg)
116 response.mustcontain(
112 response.mustcontain(
117 '<span class="error-message">Please enter a value</span>')
113 '<span class="error-message">Please enter a value</span>')
118 response.mustcontain(
114 response.mustcontain(
119 '<span class="error-message">An email address must contain a'
115 '<span class="error-message">An email address must contain a'
120 ' single @</span>')
116 ' single @</span>')
121
117
122 def get_user():
118 def get_user():
123 Session().query(User).filter(User.username == username).one()
119 Session().query(User).filter(User.username == username).one()
124
120
125 with pytest.raises(NoResultFound):
121 with pytest.raises(NoResultFound):
126 get_user()
122 get_user()
127
123
128 def test_new(self):
124 def test_new(self):
129 self.log_user()
125 self.log_user()
130 self.app.get(url('new_user'))
126 self.app.get(url('new_user'))
131
127
132 @pytest.mark.parametrize("name, attrs", [
128 @pytest.mark.parametrize("name, attrs", [
133 ('firstname', {'firstname': 'new_username'}),
129 ('firstname', {'firstname': 'new_username'}),
134 ('lastname', {'lastname': 'new_username'}),
130 ('lastname', {'lastname': 'new_username'}),
135 ('admin', {'admin': True}),
131 ('admin', {'admin': True}),
136 ('admin', {'admin': False}),
132 ('admin', {'admin': False}),
137 ('extern_type', {'extern_type': 'ldap'}),
133 ('extern_type', {'extern_type': 'ldap'}),
138 ('extern_type', {'extern_type': None}),
134 ('extern_type', {'extern_type': None}),
139 ('extern_name', {'extern_name': 'test'}),
135 ('extern_name', {'extern_name': 'test'}),
140 ('extern_name', {'extern_name': None}),
136 ('extern_name', {'extern_name': None}),
141 ('active', {'active': False}),
137 ('active', {'active': False}),
142 ('active', {'active': True}),
138 ('active', {'active': True}),
143 ('email', {'email': 'some@email.com'}),
139 ('email', {'email': 'some@email.com'}),
144 ('language', {'language': 'de'}),
140 ('language', {'language': 'de'}),
145 ('language', {'language': 'en'}),
141 ('language', {'language': 'en'}),
146 # ('new_password', {'new_password': 'foobar123',
142 # ('new_password', {'new_password': 'foobar123',
147 # 'password_confirmation': 'foobar123'})
143 # 'password_confirmation': 'foobar123'})
148 ])
144 ])
149 def test_update(self, name, attrs):
145 def test_update(self, name, attrs):
150 self.log_user()
146 self.log_user()
151 usr = fixture.create_user(self.test_user_1, password='qweqwe',
147 usr = fixture.create_user(self.test_user_1, password='qweqwe',
152 email='testme@rhodecode.org',
148 email='testme@rhodecode.org',
153 extern_type='rhodecode',
149 extern_type='rhodecode',
154 extern_name=self.test_user_1,
150 extern_name=self.test_user_1,
155 skip_if_exists=True)
151 skip_if_exists=True)
156 Session().commit()
152 Session().commit()
157 self.destroy_users.add(self.test_user_1)
153 self.destroy_users.add(self.test_user_1)
158 params = usr.get_api_data()
154 params = usr.get_api_data()
159 cur_lang = params['language'] or 'en'
155 cur_lang = params['language'] or 'en'
160 params.update({
156 params.update({
161 'password_confirmation': '',
157 'password_confirmation': '',
162 'new_password': '',
158 'new_password': '',
163 'language': cur_lang,
159 'language': cur_lang,
164 '_method': 'put',
160 '_method': 'put',
165 'csrf_token': self.csrf_token,
161 'csrf_token': self.csrf_token,
166 })
162 })
167 params.update({'new_password': ''})
163 params.update({'new_password': ''})
168 params.update(attrs)
164 params.update(attrs)
169 if name == 'email':
165 if name == 'email':
170 params['emails'] = [attrs['email']]
166 params['emails'] = [attrs['email']]
171 elif name == 'extern_type':
167 elif name == 'extern_type':
172 # cannot update this via form, expected value is original one
168 # cannot update this via form, expected value is original one
173 params['extern_type'] = "rhodecode"
169 params['extern_type'] = "rhodecode"
174 elif name == 'extern_name':
170 elif name == 'extern_name':
175 # cannot update this via form, expected value is original one
171 # cannot update this via form, expected value is original one
176 params['extern_name'] = self.test_user_1
172 params['extern_name'] = self.test_user_1
177 # special case since this user is not
173 # special case since this user is not
178 # logged in yet his data is not filled
174 # logged in yet his data is not filled
179 # so we use creation data
175 # so we use creation data
180
176
181 response = self.app.post(url('user', user_id=usr.user_id), params)
177 response = self.app.post(url('user', user_id=usr.user_id), params)
182 assert response.status_int == 302
178 assert response.status_int == 302
183 assert_session_flash(response, 'User updated successfully')
179 assert_session_flash(response, 'User updated successfully')
184
180
185 updated_user = User.get_by_username(self.test_user_1)
181 updated_user = User.get_by_username(self.test_user_1)
186 updated_params = updated_user.get_api_data()
182 updated_params = updated_user.get_api_data()
187 updated_params.update({'password_confirmation': ''})
183 updated_params.update({'password_confirmation': ''})
188 updated_params.update({'new_password': ''})
184 updated_params.update({'new_password': ''})
189
185
190 del params['_method']
186 del params['_method']
191 del params['csrf_token']
187 del params['csrf_token']
192 assert params == updated_params
188 assert params == updated_params
193
189
194 def test_update_and_migrate_password(
190 def test_update_and_migrate_password(
195 self, autologin_user, real_crypto_backend):
191 self, autologin_user, real_crypto_backend):
196 from rhodecode.lib import auth
192 from rhodecode.lib import auth
197
193
198 # create new user, with sha256 password
194 # create new user, with sha256 password
199 temp_user = 'test_admin_sha256'
195 temp_user = 'test_admin_sha256'
200 user = fixture.create_user(temp_user)
196 user = fixture.create_user(temp_user)
201 user.password = auth._RhodeCodeCryptoSha256().hash_create(
197 user.password = auth._RhodeCodeCryptoSha256().hash_create(
202 b'test123')
198 b'test123')
203 Session().add(user)
199 Session().add(user)
204 Session().commit()
200 Session().commit()
205 self.destroy_users.add('test_admin_sha256')
201 self.destroy_users.add('test_admin_sha256')
206
202
207 params = user.get_api_data()
203 params = user.get_api_data()
208
204
209 params.update({
205 params.update({
210 'password_confirmation': 'qweqwe123',
206 'password_confirmation': 'qweqwe123',
211 'new_password': 'qweqwe123',
207 'new_password': 'qweqwe123',
212 'language': 'en',
208 'language': 'en',
213 '_method': 'put',
209 '_method': 'put',
214 'csrf_token': autologin_user.csrf_token,
210 'csrf_token': autologin_user.csrf_token,
215 })
211 })
216
212
217 response = self.app.post(url('user', user_id=user.user_id), params)
213 response = self.app.post(url('user', user_id=user.user_id), params)
218 assert response.status_int == 302
214 assert response.status_int == 302
219 assert_session_flash(response, 'User updated successfully')
215 assert_session_flash(response, 'User updated successfully')
220
216
221 # new password should be bcrypted, after log-in and transfer
217 # new password should be bcrypted, after log-in and transfer
222 user = User.get_by_username(temp_user)
218 user = User.get_by_username(temp_user)
223 assert user.password.startswith('$')
219 assert user.password.startswith('$')
224
220
225 updated_user = User.get_by_username(temp_user)
221 updated_user = User.get_by_username(temp_user)
226 updated_params = updated_user.get_api_data()
222 updated_params = updated_user.get_api_data()
227 updated_params.update({'password_confirmation': 'qweqwe123'})
223 updated_params.update({'password_confirmation': 'qweqwe123'})
228 updated_params.update({'new_password': 'qweqwe123'})
224 updated_params.update({'new_password': 'qweqwe123'})
229
225
230 del params['_method']
226 del params['_method']
231 del params['csrf_token']
227 del params['csrf_token']
232 assert params == updated_params
228 assert params == updated_params
233
229
234 def test_delete(self):
230 def test_delete(self):
235 self.log_user()
231 self.log_user()
236 username = 'newtestuserdeleteme'
232 username = 'newtestuserdeleteme'
237
233
238 fixture.create_user(name=username)
234 fixture.create_user(name=username)
239
235
240 new_user = Session().query(User)\
236 new_user = Session().query(User)\
241 .filter(User.username == username).one()
237 .filter(User.username == username).one()
242 response = self.app.post(url('user', user_id=new_user.user_id),
238 response = self.app.post(url('user', user_id=new_user.user_id),
243 params={'_method': 'delete',
239 params={'_method': 'delete',
244 'csrf_token': self.csrf_token})
240 'csrf_token': self.csrf_token})
245
241
246 assert_session_flash(response, 'Successfully deleted user')
242 assert_session_flash(response, 'Successfully deleted user')
247
243
248 def test_delete_owner_of_repository(self):
244 def test_delete_owner_of_repository(self):
249 self.log_user()
245 self.log_user()
250 username = 'newtestuserdeleteme_repo_owner'
246 username = 'newtestuserdeleteme_repo_owner'
251 obj_name = 'test_repo'
247 obj_name = 'test_repo'
252 usr = fixture.create_user(name=username)
248 usr = fixture.create_user(name=username)
253 self.destroy_users.add(username)
249 self.destroy_users.add(username)
254 fixture.create_repo(obj_name, cur_user=usr.username)
250 fixture.create_repo(obj_name, cur_user=usr.username)
255
251
256 new_user = Session().query(User)\
252 new_user = Session().query(User)\
257 .filter(User.username == username).one()
253 .filter(User.username == username).one()
258 response = self.app.post(url('user', user_id=new_user.user_id),
254 response = self.app.post(url('user', user_id=new_user.user_id),
259 params={'_method': 'delete',
255 params={'_method': 'delete',
260 'csrf_token': self.csrf_token})
256 'csrf_token': self.csrf_token})
261
257
262 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
258 msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
263 'Switch owners or remove those repositories:%s' % (username,
259 'Switch owners or remove those repositories:%s' % (username,
264 obj_name)
260 obj_name)
265 assert_session_flash(response, msg)
261 assert_session_flash(response, msg)
266 fixture.destroy_repo(obj_name)
262 fixture.destroy_repo(obj_name)
267
263
268 def test_delete_owner_of_repository_detaching(self):
264 def test_delete_owner_of_repository_detaching(self):
269 self.log_user()
265 self.log_user()
270 username = 'newtestuserdeleteme_repo_owner_detach'
266 username = 'newtestuserdeleteme_repo_owner_detach'
271 obj_name = 'test_repo'
267 obj_name = 'test_repo'
272 usr = fixture.create_user(name=username)
268 usr = fixture.create_user(name=username)
273 self.destroy_users.add(username)
269 self.destroy_users.add(username)
274 fixture.create_repo(obj_name, cur_user=usr.username)
270 fixture.create_repo(obj_name, cur_user=usr.username)
275
271
276 new_user = Session().query(User)\
272 new_user = Session().query(User)\
277 .filter(User.username == username).one()
273 .filter(User.username == username).one()
278 response = self.app.post(url('user', user_id=new_user.user_id),
274 response = self.app.post(url('user', user_id=new_user.user_id),
279 params={'_method': 'delete',
275 params={'_method': 'delete',
280 'user_repos': 'detach',
276 'user_repos': 'detach',
281 'csrf_token': self.csrf_token})
277 'csrf_token': self.csrf_token})
282
278
283 msg = 'Detached 1 repositories'
279 msg = 'Detached 1 repositories'
284 assert_session_flash(response, msg)
280 assert_session_flash(response, msg)
285 fixture.destroy_repo(obj_name)
281 fixture.destroy_repo(obj_name)
286
282
287 def test_delete_owner_of_repository_deleting(self):
283 def test_delete_owner_of_repository_deleting(self):
288 self.log_user()
284 self.log_user()
289 username = 'newtestuserdeleteme_repo_owner_delete'
285 username = 'newtestuserdeleteme_repo_owner_delete'
290 obj_name = 'test_repo'
286 obj_name = 'test_repo'
291 usr = fixture.create_user(name=username)
287 usr = fixture.create_user(name=username)
292 self.destroy_users.add(username)
288 self.destroy_users.add(username)
293 fixture.create_repo(obj_name, cur_user=usr.username)
289 fixture.create_repo(obj_name, cur_user=usr.username)
294
290
295 new_user = Session().query(User)\
291 new_user = Session().query(User)\
296 .filter(User.username == username).one()
292 .filter(User.username == username).one()
297 response = self.app.post(url('user', user_id=new_user.user_id),
293 response = self.app.post(url('user', user_id=new_user.user_id),
298 params={'_method': 'delete',
294 params={'_method': 'delete',
299 'user_repos': 'delete',
295 'user_repos': 'delete',
300 'csrf_token': self.csrf_token})
296 'csrf_token': self.csrf_token})
301
297
302 msg = 'Deleted 1 repositories'
298 msg = 'Deleted 1 repositories'
303 assert_session_flash(response, msg)
299 assert_session_flash(response, msg)
304
300
305 def test_delete_owner_of_repository_group(self):
301 def test_delete_owner_of_repository_group(self):
306 self.log_user()
302 self.log_user()
307 username = 'newtestuserdeleteme_repo_group_owner'
303 username = 'newtestuserdeleteme_repo_group_owner'
308 obj_name = 'test_group'
304 obj_name = 'test_group'
309 usr = fixture.create_user(name=username)
305 usr = fixture.create_user(name=username)
310 self.destroy_users.add(username)
306 self.destroy_users.add(username)
311 fixture.create_repo_group(obj_name, cur_user=usr.username)
307 fixture.create_repo_group(obj_name, cur_user=usr.username)
312
308
313 new_user = Session().query(User)\
309 new_user = Session().query(User)\
314 .filter(User.username == username).one()
310 .filter(User.username == username).one()
315 response = self.app.post(url('user', user_id=new_user.user_id),
311 response = self.app.post(url('user', user_id=new_user.user_id),
316 params={'_method': 'delete',
312 params={'_method': 'delete',
317 'csrf_token': self.csrf_token})
313 'csrf_token': self.csrf_token})
318
314
319 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
315 msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
320 'Switch owners or remove those repository groups:%s' % (username,
316 'Switch owners or remove those repository groups:%s' % (username,
321 obj_name)
317 obj_name)
322 assert_session_flash(response, msg)
318 assert_session_flash(response, msg)
323 fixture.destroy_repo_group(obj_name)
319 fixture.destroy_repo_group(obj_name)
324
320
325 def test_delete_owner_of_repository_group_detaching(self):
321 def test_delete_owner_of_repository_group_detaching(self):
326 self.log_user()
322 self.log_user()
327 username = 'newtestuserdeleteme_repo_group_owner_detach'
323 username = 'newtestuserdeleteme_repo_group_owner_detach'
328 obj_name = 'test_group'
324 obj_name = 'test_group'
329 usr = fixture.create_user(name=username)
325 usr = fixture.create_user(name=username)
330 self.destroy_users.add(username)
326 self.destroy_users.add(username)
331 fixture.create_repo_group(obj_name, cur_user=usr.username)
327 fixture.create_repo_group(obj_name, cur_user=usr.username)
332
328
333 new_user = Session().query(User)\
329 new_user = Session().query(User)\
334 .filter(User.username == username).one()
330 .filter(User.username == username).one()
335 response = self.app.post(url('user', user_id=new_user.user_id),
331 response = self.app.post(url('user', user_id=new_user.user_id),
336 params={'_method': 'delete',
332 params={'_method': 'delete',
337 'user_repo_groups': 'delete',
333 'user_repo_groups': 'delete',
338 'csrf_token': self.csrf_token})
334 'csrf_token': self.csrf_token})
339
335
340 msg = 'Deleted 1 repository groups'
336 msg = 'Deleted 1 repository groups'
341 assert_session_flash(response, msg)
337 assert_session_flash(response, msg)
342
338
343 def test_delete_owner_of_repository_group_deleting(self):
339 def test_delete_owner_of_repository_group_deleting(self):
344 self.log_user()
340 self.log_user()
345 username = 'newtestuserdeleteme_repo_group_owner_delete'
341 username = 'newtestuserdeleteme_repo_group_owner_delete'
346 obj_name = 'test_group'
342 obj_name = 'test_group'
347 usr = fixture.create_user(name=username)
343 usr = fixture.create_user(name=username)
348 self.destroy_users.add(username)
344 self.destroy_users.add(username)
349 fixture.create_repo_group(obj_name, cur_user=usr.username)
345 fixture.create_repo_group(obj_name, cur_user=usr.username)
350
346
351 new_user = Session().query(User)\
347 new_user = Session().query(User)\
352 .filter(User.username == username).one()
348 .filter(User.username == username).one()
353 response = self.app.post(url('user', user_id=new_user.user_id),
349 response = self.app.post(url('user', user_id=new_user.user_id),
354 params={'_method': 'delete',
350 params={'_method': 'delete',
355 'user_repo_groups': 'detach',
351 'user_repo_groups': 'detach',
356 'csrf_token': self.csrf_token})
352 'csrf_token': self.csrf_token})
357
353
358 msg = 'Detached 1 repository groups'
354 msg = 'Detached 1 repository groups'
359 assert_session_flash(response, msg)
355 assert_session_flash(response, msg)
360 fixture.destroy_repo_group(obj_name)
356 fixture.destroy_repo_group(obj_name)
361
357
362 def test_delete_owner_of_user_group(self):
358 def test_delete_owner_of_user_group(self):
363 self.log_user()
359 self.log_user()
364 username = 'newtestuserdeleteme_user_group_owner'
360 username = 'newtestuserdeleteme_user_group_owner'
365 obj_name = 'test_user_group'
361 obj_name = 'test_user_group'
366 usr = fixture.create_user(name=username)
362 usr = fixture.create_user(name=username)
367 self.destroy_users.add(username)
363 self.destroy_users.add(username)
368 fixture.create_user_group(obj_name, cur_user=usr.username)
364 fixture.create_user_group(obj_name, cur_user=usr.username)
369
365
370 new_user = Session().query(User)\
366 new_user = Session().query(User)\
371 .filter(User.username == username).one()
367 .filter(User.username == username).one()
372 response = self.app.post(url('user', user_id=new_user.user_id),
368 response = self.app.post(url('user', user_id=new_user.user_id),
373 params={'_method': 'delete',
369 params={'_method': 'delete',
374 'csrf_token': self.csrf_token})
370 'csrf_token': self.csrf_token})
375
371
376 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
372 msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
377 'Switch owners or remove those user groups:%s' % (username,
373 'Switch owners or remove those user groups:%s' % (username,
378 obj_name)
374 obj_name)
379 assert_session_flash(response, msg)
375 assert_session_flash(response, msg)
380 fixture.destroy_user_group(obj_name)
376 fixture.destroy_user_group(obj_name)
381
377
382 def test_delete_owner_of_user_group_detaching(self):
378 def test_delete_owner_of_user_group_detaching(self):
383 self.log_user()
379 self.log_user()
384 username = 'newtestuserdeleteme_user_group_owner_detaching'
380 username = 'newtestuserdeleteme_user_group_owner_detaching'
385 obj_name = 'test_user_group'
381 obj_name = 'test_user_group'
386 usr = fixture.create_user(name=username)
382 usr = fixture.create_user(name=username)
387 self.destroy_users.add(username)
383 self.destroy_users.add(username)
388 fixture.create_user_group(obj_name, cur_user=usr.username)
384 fixture.create_user_group(obj_name, cur_user=usr.username)
389
385
390 new_user = Session().query(User)\
386 new_user = Session().query(User)\
391 .filter(User.username == username).one()
387 .filter(User.username == username).one()
392 try:
388 try:
393 response = self.app.post(url('user', user_id=new_user.user_id),
389 response = self.app.post(url('user', user_id=new_user.user_id),
394 params={'_method': 'delete',
390 params={'_method': 'delete',
395 'user_user_groups': 'detach',
391 'user_user_groups': 'detach',
396 'csrf_token': self.csrf_token})
392 'csrf_token': self.csrf_token})
397
393
398 msg = 'Detached 1 user groups'
394 msg = 'Detached 1 user groups'
399 assert_session_flash(response, msg)
395 assert_session_flash(response, msg)
400 finally:
396 finally:
401 fixture.destroy_user_group(obj_name)
397 fixture.destroy_user_group(obj_name)
402
398
403 def test_delete_owner_of_user_group_deleting(self):
399 def test_delete_owner_of_user_group_deleting(self):
404 self.log_user()
400 self.log_user()
405 username = 'newtestuserdeleteme_user_group_owner_deleting'
401 username = 'newtestuserdeleteme_user_group_owner_deleting'
406 obj_name = 'test_user_group'
402 obj_name = 'test_user_group'
407 usr = fixture.create_user(name=username)
403 usr = fixture.create_user(name=username)
408 self.destroy_users.add(username)
404 self.destroy_users.add(username)
409 fixture.create_user_group(obj_name, cur_user=usr.username)
405 fixture.create_user_group(obj_name, cur_user=usr.username)
410
406
411 new_user = Session().query(User)\
407 new_user = Session().query(User)\
412 .filter(User.username == username).one()
408 .filter(User.username == username).one()
413 response = self.app.post(url('user', user_id=new_user.user_id),
409 response = self.app.post(url('user', user_id=new_user.user_id),
414 params={'_method': 'delete',
410 params={'_method': 'delete',
415 'user_user_groups': 'delete',
411 'user_user_groups': 'delete',
416 'csrf_token': self.csrf_token})
412 'csrf_token': self.csrf_token})
417
413
418 msg = 'Deleted 1 user groups'
414 msg = 'Deleted 1 user groups'
419 assert_session_flash(response, msg)
415 assert_session_flash(response, msg)
420
416
421 def test_edit(self):
417 def test_edit(self):
422 self.log_user()
418 self.log_user()
423 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
419 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
424 self.app.get(url('edit_user', user_id=user.user_id))
420 self.app.get(url('edit_user', user_id=user.user_id))
425
421
426 @pytest.mark.parametrize(
422 @pytest.mark.parametrize(
427 'repo_create, repo_create_write, user_group_create, repo_group_create,'
423 'repo_create, repo_create_write, user_group_create, repo_group_create,'
428 'fork_create, inherit_default_permissions, expect_error,'
424 'fork_create, inherit_default_permissions, expect_error,'
429 'expect_form_error', [
425 'expect_form_error', [
430 ('hg.create.none', 'hg.create.write_on_repogroup.false',
426 ('hg.create.none', 'hg.create.write_on_repogroup.false',
431 'hg.usergroup.create.false', 'hg.repogroup.create.false',
427 'hg.usergroup.create.false', 'hg.repogroup.create.false',
432 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
428 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
433 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
429 ('hg.create.repository', 'hg.create.write_on_repogroup.false',
434 'hg.usergroup.create.false', 'hg.repogroup.create.false',
430 'hg.usergroup.create.false', 'hg.repogroup.create.false',
435 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
431 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
436 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
432 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
437 'hg.usergroup.create.true', 'hg.repogroup.create.true',
433 'hg.usergroup.create.true', 'hg.repogroup.create.true',
438 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
434 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
439 False),
435 False),
440 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
436 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
441 'hg.usergroup.create.true', 'hg.repogroup.create.true',
437 'hg.usergroup.create.true', 'hg.repogroup.create.true',
442 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
438 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
443 True),
439 True),
444 ('', '', '', '', '', '', True, False),
440 ('', '', '', '', '', '', True, False),
445 ])
441 ])
446 def test_global_perms_on_user(
442 def test_global_perms_on_user(
447 self, repo_create, repo_create_write, user_group_create,
443 self, repo_create, repo_create_write, user_group_create,
448 repo_group_create, fork_create, expect_error, expect_form_error,
444 repo_group_create, fork_create, expect_error, expect_form_error,
449 inherit_default_permissions):
445 inherit_default_permissions):
450 self.log_user()
446 self.log_user()
451 user = fixture.create_user('dummy')
447 user = fixture.create_user('dummy')
452 uid = user.user_id
448 uid = user.user_id
453
449
454 # ENABLE REPO CREATE ON A GROUP
450 # ENABLE REPO CREATE ON A GROUP
455 perm_params = {
451 perm_params = {
456 'inherit_default_permissions': False,
452 'inherit_default_permissions': False,
457 'default_repo_create': repo_create,
453 'default_repo_create': repo_create,
458 'default_repo_create_on_write': repo_create_write,
454 'default_repo_create_on_write': repo_create_write,
459 'default_user_group_create': user_group_create,
455 'default_user_group_create': user_group_create,
460 'default_repo_group_create': repo_group_create,
456 'default_repo_group_create': repo_group_create,
461 'default_fork_create': fork_create,
457 'default_fork_create': fork_create,
462 'default_inherit_default_permissions': inherit_default_permissions,
458 'default_inherit_default_permissions': inherit_default_permissions,
463 '_method': 'put',
459 '_method': 'put',
464 'csrf_token': self.csrf_token,
460 'csrf_token': self.csrf_token,
465 }
461 }
466 response = self.app.post(
462 response = self.app.post(
467 url('edit_user_global_perms', user_id=uid),
463 url('edit_user_global_perms', user_id=uid),
468 params=perm_params)
464 params=perm_params)
469
465
470 if expect_form_error:
466 if expect_form_error:
471 assert response.status_int == 200
467 assert response.status_int == 200
472 response.mustcontain('Value must be one of')
468 response.mustcontain('Value must be one of')
473 else:
469 else:
474 if expect_error:
470 if expect_error:
475 msg = 'An error occurred during permissions saving'
471 msg = 'An error occurred during permissions saving'
476 else:
472 else:
477 msg = 'User global permissions updated successfully'
473 msg = 'User global permissions updated successfully'
478 ug = User.get(uid)
474 ug = User.get(uid)
479 del perm_params['_method']
475 del perm_params['_method']
480 del perm_params['inherit_default_permissions']
476 del perm_params['inherit_default_permissions']
481 del perm_params['csrf_token']
477 del perm_params['csrf_token']
482 assert perm_params == ug.get_default_perms()
478 assert perm_params == ug.get_default_perms()
483 assert_session_flash(response, msg)
479 assert_session_flash(response, msg)
484 fixture.destroy_user(uid)
480 fixture.destroy_user(uid)
485
481
486 def test_global_permissions_initial_values(self, user_util):
482 def test_global_permissions_initial_values(self, user_util):
487 self.log_user()
483 self.log_user()
488 user = user_util.create_user()
484 user = user_util.create_user()
489 uid = user.user_id
485 uid = user.user_id
490 response = self.app.get(url('edit_user_global_perms', user_id=uid))
486 response = self.app.get(url('edit_user_global_perms', user_id=uid))
491 default_user = User.get_default_user()
487 default_user = User.get_default_user()
492 default_permissions = default_user.get_default_perms()
488 default_permissions = default_user.get_default_perms()
493 assert_response = AssertResponse(response)
489 assert_response = AssertResponse(response)
494 expected_permissions = (
490 expected_permissions = (
495 'default_repo_create', 'default_repo_create_on_write',
491 'default_repo_create', 'default_repo_create_on_write',
496 'default_fork_create', 'default_repo_group_create',
492 'default_fork_create', 'default_repo_group_create',
497 'default_user_group_create', 'default_inherit_default_permissions')
493 'default_user_group_create', 'default_inherit_default_permissions')
498 for permission in expected_permissions:
494 for permission in expected_permissions:
499 css_selector = '[name={}][checked=checked]'.format(permission)
495 css_selector = '[name={}][checked=checked]'.format(permission)
500 element = assert_response.get_element(css_selector)
496 element = assert_response.get_element(css_selector)
501 assert element.value == default_permissions[permission]
497 assert element.value == default_permissions[permission]
502
498
503 def test_ips(self):
499 def test_ips(self):
504 self.log_user()
500 self.log_user()
505 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
501 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
506 response = self.app.get(url('edit_user_ips', user_id=user.user_id))
502 response = self.app.get(url('edit_user_ips', user_id=user.user_id))
507 response.mustcontain('All IP addresses are allowed')
503 response.mustcontain('All IP addresses are allowed')
508
504
509 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
505 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
510 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
506 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
511 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
507 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
512 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
508 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
513 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
509 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
514 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
510 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
515 ('127_bad_ip', 'foobar', 'foobar', True),
511 ('127_bad_ip', 'foobar', 'foobar', True),
516 ])
512 ])
517 def test_add_ip(self, test_name, ip, ip_range, failure):
513 def test_add_ip(self, test_name, ip, ip_range, failure):
518 self.log_user()
514 self.log_user()
519 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
515 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
520 user_id = user.user_id
516 user_id = user.user_id
521
517
522 response = self.app.post(url('edit_user_ips', user_id=user_id),
518 response = self.app.post(url('edit_user_ips', user_id=user_id),
523 params={'new_ip': ip, '_method': 'put',
519 params={'new_ip': ip, '_method': 'put',
524 'csrf_token': self.csrf_token})
520 'csrf_token': self.csrf_token})
525
521
526 if failure:
522 if failure:
527 assert_session_flash(
523 assert_session_flash(
528 response, 'Please enter a valid IPv4 or IpV6 address')
524 response, 'Please enter a valid IPv4 or IpV6 address')
529 response = self.app.get(url('edit_user_ips', user_id=user_id))
525 response = self.app.get(url('edit_user_ips', user_id=user_id))
530 response.mustcontain(no=[ip])
526 response.mustcontain(no=[ip])
531 response.mustcontain(no=[ip_range])
527 response.mustcontain(no=[ip_range])
532
528
533 else:
529 else:
534 response = self.app.get(url('edit_user_ips', user_id=user_id))
530 response = self.app.get(url('edit_user_ips', user_id=user_id))
535 response.mustcontain(ip)
531 response.mustcontain(ip)
536 response.mustcontain(ip_range)
532 response.mustcontain(ip_range)
537
533
538 # cleanup
534 # cleanup
539 for del_ip in UserIpMap.query().filter(
535 for del_ip in UserIpMap.query().filter(
540 UserIpMap.user_id == user_id).all():
536 UserIpMap.user_id == user_id).all():
541 Session().delete(del_ip)
537 Session().delete(del_ip)
542 Session().commit()
538 Session().commit()
543
539
544 def test_delete_ip(self):
540 def test_delete_ip(self):
545 self.log_user()
541 self.log_user()
546 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
542 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
547 user_id = user.user_id
543 user_id = user.user_id
548 ip = '127.0.0.1/32'
544 ip = '127.0.0.1/32'
549 ip_range = '127.0.0.1 - 127.0.0.1'
545 ip_range = '127.0.0.1 - 127.0.0.1'
550 new_ip = UserModel().add_extra_ip(user_id, ip)
546 new_ip = UserModel().add_extra_ip(user_id, ip)
551 Session().commit()
547 Session().commit()
552 new_ip_id = new_ip.ip_id
548 new_ip_id = new_ip.ip_id
553
549
554 response = self.app.get(url('edit_user_ips', user_id=user_id))
550 response = self.app.get(url('edit_user_ips', user_id=user_id))
555 response.mustcontain(ip)
551 response.mustcontain(ip)
556 response.mustcontain(ip_range)
552 response.mustcontain(ip_range)
557
553
558 self.app.post(url('edit_user_ips', user_id=user_id),
554 self.app.post(url('edit_user_ips', user_id=user_id),
559 params={'_method': 'delete', 'del_ip_id': new_ip_id,
555 params={'_method': 'delete', 'del_ip_id': new_ip_id,
560 'csrf_token': self.csrf_token})
556 'csrf_token': self.csrf_token})
561
557
562 response = self.app.get(url('edit_user_ips', user_id=user_id))
558 response = self.app.get(url('edit_user_ips', user_id=user_id))
563 response.mustcontain('All IP addresses are allowed')
559 response.mustcontain('All IP addresses are allowed')
564 response.mustcontain(no=[ip])
560 response.mustcontain(no=[ip])
565 response.mustcontain(no=[ip_range])
561 response.mustcontain(no=[ip_range])
566
567 def test_auth_tokens(self):
568 self.log_user()
569
570 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
571 response = self.app.get(
572 url('edit_user_auth_tokens', user_id=user.user_id))
573 for token in user.auth_tokens:
574 response.mustcontain(token)
575 response.mustcontain('never')
576
577 @pytest.mark.parametrize("desc, lifetime", [
578 ('forever', -1),
579 ('5mins', 60*5),
580 ('30days', 60*60*24*30),
581 ])
582 def test_add_auth_token(self, desc, lifetime, user_util):
583 self.log_user()
584 user = user_util.create_user()
585 user_id = user.user_id
586
587 response = self.app.post(
588 url('edit_user_auth_tokens', user_id=user_id),
589 {'_method': 'put', 'description': desc, 'lifetime': lifetime,
590 'csrf_token': self.csrf_token})
591 assert_session_flash(response, 'Auth token successfully created')
592
593 response = response.follow()
594 user = User.get(user_id)
595 for auth_token in user.auth_tokens:
596 response.mustcontain(auth_token)
597
598 def test_remove_auth_token(self, user_util):
599 self.log_user()
600 user = user_util.create_user()
601 user_id = user.user_id
602
603 response = self.app.post(
604 url('edit_user_auth_tokens', user_id=user_id),
605 {'_method': 'put', 'description': 'desc', 'lifetime': -1,
606 'csrf_token': self.csrf_token})
607 assert_session_flash(response, 'Auth token successfully created')
608 response = response.follow()
609
610 # now delete our key
611 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
612 assert 3 == len(keys)
613
614 response = self.app.post(
615 url('edit_user_auth_tokens', user_id=user_id),
616 {'_method': 'delete', 'del_auth_token': keys[0].api_key,
617 'csrf_token': self.csrf_token})
618 assert_session_flash(response, 'Auth token successfully deleted')
619 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
620 assert 2 == len(keys)
@@ -1,1808 +1,1806 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import collections
21 import collections
22 import datetime
22 import datetime
23 import hashlib
23 import hashlib
24 import os
24 import os
25 import re
25 import re
26 import pprint
26 import pprint
27 import shutil
27 import shutil
28 import socket
28 import socket
29 import subprocess32
29 import subprocess32
30 import time
30 import time
31 import uuid
31 import uuid
32 import dateutil.tz
32 import dateutil.tz
33
33
34 import mock
34 import mock
35 import pyramid.testing
35 import pyramid.testing
36 import pytest
36 import pytest
37 import colander
37 import colander
38 import requests
38 import requests
39
39
40 import rhodecode
40 import rhodecode
41 from rhodecode.lib.utils2 import AttributeDict
41 from rhodecode.lib.utils2 import AttributeDict
42 from rhodecode.model.changeset_status import ChangesetStatusModel
42 from rhodecode.model.changeset_status import ChangesetStatusModel
43 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.comment import CommentsModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
45 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
46 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
46 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
51 from rhodecode.model.user import UserModel
51 from rhodecode.model.user import UserModel
52 from rhodecode.model.settings import VcsSettingsModel
52 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.user_group import UserGroupModel
53 from rhodecode.model.user_group import UserGroupModel
54 from rhodecode.model.integration import IntegrationModel
54 from rhodecode.model.integration import IntegrationModel
55 from rhodecode.integrations import integration_type_registry
55 from rhodecode.integrations import integration_type_registry
56 from rhodecode.integrations.types.base import IntegrationTypeBase
56 from rhodecode.integrations.types.base import IntegrationTypeBase
57 from rhodecode.lib.utils import repo2db_mapper
57 from rhodecode.lib.utils import repo2db_mapper
58 from rhodecode.lib.vcs import create_vcsserver_proxy
58 from rhodecode.lib.vcs import create_vcsserver_proxy
59 from rhodecode.lib.vcs.backends import get_backend
59 from rhodecode.lib.vcs.backends import get_backend
60 from rhodecode.lib.vcs.nodes import FileNode
60 from rhodecode.lib.vcs.nodes import FileNode
61 from rhodecode.tests import (
61 from rhodecode.tests import (
62 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
62 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
63 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
63 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
64 TEST_USER_REGULAR_PASS)
64 TEST_USER_REGULAR_PASS)
65 from rhodecode.tests.utils import CustomTestApp
65 from rhodecode.tests.utils import CustomTestApp
66 from rhodecode.tests.fixture import Fixture
66 from rhodecode.tests.fixture import Fixture
67
67
68
68
69 def _split_comma(value):
69 def _split_comma(value):
70 return value.split(',')
70 return value.split(',')
71
71
72
72
73 def pytest_addoption(parser):
73 def pytest_addoption(parser):
74 parser.addoption(
74 parser.addoption(
75 '--keep-tmp-path', action='store_true',
75 '--keep-tmp-path', action='store_true',
76 help="Keep the test temporary directories")
76 help="Keep the test temporary directories")
77 parser.addoption(
77 parser.addoption(
78 '--backends', action='store', type=_split_comma,
78 '--backends', action='store', type=_split_comma,
79 default=['git', 'hg', 'svn'],
79 default=['git', 'hg', 'svn'],
80 help="Select which backends to test for backend specific tests.")
80 help="Select which backends to test for backend specific tests.")
81 parser.addoption(
81 parser.addoption(
82 '--dbs', action='store', type=_split_comma,
82 '--dbs', action='store', type=_split_comma,
83 default=['sqlite'],
83 default=['sqlite'],
84 help="Select which database to test for database specific tests. "
84 help="Select which database to test for database specific tests. "
85 "Possible options are sqlite,postgres,mysql")
85 "Possible options are sqlite,postgres,mysql")
86 parser.addoption(
86 parser.addoption(
87 '--appenlight', '--ae', action='store_true',
87 '--appenlight', '--ae', action='store_true',
88 help="Track statistics in appenlight.")
88 help="Track statistics in appenlight.")
89 parser.addoption(
89 parser.addoption(
90 '--appenlight-api-key', '--ae-key',
90 '--appenlight-api-key', '--ae-key',
91 help="API key for Appenlight.")
91 help="API key for Appenlight.")
92 parser.addoption(
92 parser.addoption(
93 '--appenlight-url', '--ae-url',
93 '--appenlight-url', '--ae-url',
94 default="https://ae.rhodecode.com",
94 default="https://ae.rhodecode.com",
95 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
95 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
96 parser.addoption(
96 parser.addoption(
97 '--sqlite-connection-string', action='store',
97 '--sqlite-connection-string', action='store',
98 default='', help="Connection string for the dbs tests with SQLite")
98 default='', help="Connection string for the dbs tests with SQLite")
99 parser.addoption(
99 parser.addoption(
100 '--postgres-connection-string', action='store',
100 '--postgres-connection-string', action='store',
101 default='', help="Connection string for the dbs tests with Postgres")
101 default='', help="Connection string for the dbs tests with Postgres")
102 parser.addoption(
102 parser.addoption(
103 '--mysql-connection-string', action='store',
103 '--mysql-connection-string', action='store',
104 default='', help="Connection string for the dbs tests with MySQL")
104 default='', help="Connection string for the dbs tests with MySQL")
105 parser.addoption(
105 parser.addoption(
106 '--repeat', type=int, default=100,
106 '--repeat', type=int, default=100,
107 help="Number of repetitions in performance tests.")
107 help="Number of repetitions in performance tests.")
108
108
109
109
110 def pytest_configure(config):
110 def pytest_configure(config):
111 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
111 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
112 from rhodecode.config import patches
112 from rhodecode.config import patches
113 patches.kombu_1_5_1_python_2_7_11()
113 patches.kombu_1_5_1_python_2_7_11()
114
114
115
115
116 def pytest_collection_modifyitems(session, config, items):
116 def pytest_collection_modifyitems(session, config, items):
117 # nottest marked, compare nose, used for transition from nose to pytest
117 # nottest marked, compare nose, used for transition from nose to pytest
118 remaining = [
118 remaining = [
119 i for i in items if getattr(i.obj, '__test__', True)]
119 i for i in items if getattr(i.obj, '__test__', True)]
120 items[:] = remaining
120 items[:] = remaining
121
121
122
122
123 def pytest_generate_tests(metafunc):
123 def pytest_generate_tests(metafunc):
124 # Support test generation based on --backend parameter
124 # Support test generation based on --backend parameter
125 if 'backend_alias' in metafunc.fixturenames:
125 if 'backend_alias' in metafunc.fixturenames:
126 backends = get_backends_from_metafunc(metafunc)
126 backends = get_backends_from_metafunc(metafunc)
127 scope = None
127 scope = None
128 if not backends:
128 if not backends:
129 pytest.skip("Not enabled for any of selected backends")
129 pytest.skip("Not enabled for any of selected backends")
130 metafunc.parametrize('backend_alias', backends, scope=scope)
130 metafunc.parametrize('backend_alias', backends, scope=scope)
131 elif hasattr(metafunc.function, 'backends'):
131 elif hasattr(metafunc.function, 'backends'):
132 backends = get_backends_from_metafunc(metafunc)
132 backends = get_backends_from_metafunc(metafunc)
133 if not backends:
133 if not backends:
134 pytest.skip("Not enabled for any of selected backends")
134 pytest.skip("Not enabled for any of selected backends")
135
135
136
136
137 def get_backends_from_metafunc(metafunc):
137 def get_backends_from_metafunc(metafunc):
138 requested_backends = set(metafunc.config.getoption('--backends'))
138 requested_backends = set(metafunc.config.getoption('--backends'))
139 if hasattr(metafunc.function, 'backends'):
139 if hasattr(metafunc.function, 'backends'):
140 # Supported backends by this test function, created from
140 # Supported backends by this test function, created from
141 # pytest.mark.backends
141 # pytest.mark.backends
142 backends = metafunc.function.backends.args
142 backends = metafunc.function.backends.args
143 elif hasattr(metafunc.cls, 'backend_alias'):
143 elif hasattr(metafunc.cls, 'backend_alias'):
144 # Support class attribute "backend_alias", this is mainly
144 # Support class attribute "backend_alias", this is mainly
145 # for legacy reasons for tests not yet using pytest.mark.backends
145 # for legacy reasons for tests not yet using pytest.mark.backends
146 backends = [metafunc.cls.backend_alias]
146 backends = [metafunc.cls.backend_alias]
147 else:
147 else:
148 backends = metafunc.config.getoption('--backends')
148 backends = metafunc.config.getoption('--backends')
149 return requested_backends.intersection(backends)
149 return requested_backends.intersection(backends)
150
150
151
151
152 @pytest.fixture(scope='session', autouse=True)
152 @pytest.fixture(scope='session', autouse=True)
153 def activate_example_rcextensions(request):
153 def activate_example_rcextensions(request):
154 """
154 """
155 Patch in an example rcextensions module which verifies passed in kwargs.
155 Patch in an example rcextensions module which verifies passed in kwargs.
156 """
156 """
157 from rhodecode.tests.other import example_rcextensions
157 from rhodecode.tests.other import example_rcextensions
158
158
159 old_extensions = rhodecode.EXTENSIONS
159 old_extensions = rhodecode.EXTENSIONS
160 rhodecode.EXTENSIONS = example_rcextensions
160 rhodecode.EXTENSIONS = example_rcextensions
161
161
162 @request.addfinalizer
162 @request.addfinalizer
163 def cleanup():
163 def cleanup():
164 rhodecode.EXTENSIONS = old_extensions
164 rhodecode.EXTENSIONS = old_extensions
165
165
166
166
167 @pytest.fixture
167 @pytest.fixture
168 def capture_rcextensions():
168 def capture_rcextensions():
169 """
169 """
170 Returns the recorded calls to entry points in rcextensions.
170 Returns the recorded calls to entry points in rcextensions.
171 """
171 """
172 calls = rhodecode.EXTENSIONS.calls
172 calls = rhodecode.EXTENSIONS.calls
173 calls.clear()
173 calls.clear()
174 # Note: At this moment, it is still the empty dict, but that will
174 # Note: At this moment, it is still the empty dict, but that will
175 # be filled during the test run and since it is a reference this
175 # be filled during the test run and since it is a reference this
176 # is enough to make it work.
176 # is enough to make it work.
177 return calls
177 return calls
178
178
179
179
180 @pytest.fixture(scope='session')
180 @pytest.fixture(scope='session')
181 def http_environ_session():
181 def http_environ_session():
182 """
182 """
183 Allow to use "http_environ" in session scope.
183 Allow to use "http_environ" in session scope.
184 """
184 """
185 return http_environ(
185 return http_environ(
186 http_host_stub=http_host_stub())
186 http_host_stub=http_host_stub())
187
187
188
188
189 @pytest.fixture
189 @pytest.fixture
190 def http_host_stub():
190 def http_host_stub():
191 """
191 """
192 Value of HTTP_HOST in the test run.
192 Value of HTTP_HOST in the test run.
193 """
193 """
194 return 'test.example.com:80'
194 return 'test.example.com:80'
195
195
196
196
197 @pytest.fixture
197 @pytest.fixture
198 def http_environ(http_host_stub):
198 def http_environ(http_host_stub):
199 """
199 """
200 HTTP extra environ keys.
200 HTTP extra environ keys.
201
201
202 User by the test application and as well for setting up the pylons
202 User by the test application and as well for setting up the pylons
203 environment. In the case of the fixture "app" it should be possible
203 environment. In the case of the fixture "app" it should be possible
204 to override this for a specific test case.
204 to override this for a specific test case.
205 """
205 """
206 return {
206 return {
207 'SERVER_NAME': http_host_stub.split(':')[0],
207 'SERVER_NAME': http_host_stub.split(':')[0],
208 'SERVER_PORT': http_host_stub.split(':')[1],
208 'SERVER_PORT': http_host_stub.split(':')[1],
209 'HTTP_HOST': http_host_stub,
209 'HTTP_HOST': http_host_stub,
210 }
210 }
211
211
212
212
213 @pytest.fixture(scope='function')
213 @pytest.fixture(scope='function')
214 def app(request, pylonsapp, http_environ):
214 def app(request, pylonsapp, http_environ):
215
216
217 app = CustomTestApp(
215 app = CustomTestApp(
218 pylonsapp,
216 pylonsapp,
219 extra_environ=http_environ)
217 extra_environ=http_environ)
220 if request.cls:
218 if request.cls:
221 request.cls.app = app
219 request.cls.app = app
222 return app
220 return app
223
221
224
222
225 @pytest.fixture(scope='session')
223 @pytest.fixture(scope='session')
226 def app_settings(pylonsapp, pylons_config):
224 def app_settings(pylonsapp, pylons_config):
227 """
225 """
228 Settings dictionary used to create the app.
226 Settings dictionary used to create the app.
229
227
230 Parses the ini file and passes the result through the sanitize and apply
228 Parses the ini file and passes the result through the sanitize and apply
231 defaults mechanism in `rhodecode.config.middleware`.
229 defaults mechanism in `rhodecode.config.middleware`.
232 """
230 """
233 from paste.deploy.loadwsgi import loadcontext, APP
231 from paste.deploy.loadwsgi import loadcontext, APP
234 from rhodecode.config.middleware import (
232 from rhodecode.config.middleware import (
235 sanitize_settings_and_apply_defaults)
233 sanitize_settings_and_apply_defaults)
236 context = loadcontext(APP, 'config:' + pylons_config)
234 context = loadcontext(APP, 'config:' + pylons_config)
237 settings = sanitize_settings_and_apply_defaults(context.config())
235 settings = sanitize_settings_and_apply_defaults(context.config())
238 return settings
236 return settings
239
237
240
238
241 @pytest.fixture(scope='session')
239 @pytest.fixture(scope='session')
242 def db(app_settings):
240 def db(app_settings):
243 """
241 """
244 Initializes the database connection.
242 Initializes the database connection.
245
243
246 It uses the same settings which are used to create the ``pylonsapp`` or
244 It uses the same settings which are used to create the ``pylonsapp`` or
247 ``app`` fixtures.
245 ``app`` fixtures.
248 """
246 """
249 from rhodecode.config.utils import initialize_database
247 from rhodecode.config.utils import initialize_database
250 initialize_database(app_settings)
248 initialize_database(app_settings)
251
249
252
250
253 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
251 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
254
252
255
253
256 def _autologin_user(app, *args):
254 def _autologin_user(app, *args):
257 session = login_user_session(app, *args)
255 session = login_user_session(app, *args)
258 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
256 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
259 return LoginData(csrf_token, session['rhodecode_user'])
257 return LoginData(csrf_token, session['rhodecode_user'])
260
258
261
259
262 @pytest.fixture
260 @pytest.fixture
263 def autologin_user(app):
261 def autologin_user(app):
264 """
262 """
265 Utility fixture which makes sure that the admin user is logged in
263 Utility fixture which makes sure that the admin user is logged in
266 """
264 """
267 return _autologin_user(app)
265 return _autologin_user(app)
268
266
269
267
270 @pytest.fixture
268 @pytest.fixture
271 def autologin_regular_user(app):
269 def autologin_regular_user(app):
272 """
270 """
273 Utility fixture which makes sure that the regular user is logged in
271 Utility fixture which makes sure that the regular user is logged in
274 """
272 """
275 return _autologin_user(
273 return _autologin_user(
276 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
274 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
277
275
278
276
279 @pytest.fixture(scope='function')
277 @pytest.fixture(scope='function')
280 def csrf_token(request, autologin_user):
278 def csrf_token(request, autologin_user):
281 return autologin_user.csrf_token
279 return autologin_user.csrf_token
282
280
283
281
284 @pytest.fixture(scope='function')
282 @pytest.fixture(scope='function')
285 def xhr_header(request):
283 def xhr_header(request):
286 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
284 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
287
285
288
286
289 @pytest.fixture
287 @pytest.fixture
290 def real_crypto_backend(monkeypatch):
288 def real_crypto_backend(monkeypatch):
291 """
289 """
292 Switch the production crypto backend on for this test.
290 Switch the production crypto backend on for this test.
293
291
294 During the test run the crypto backend is replaced with a faster
292 During the test run the crypto backend is replaced with a faster
295 implementation based on the MD5 algorithm.
293 implementation based on the MD5 algorithm.
296 """
294 """
297 monkeypatch.setattr(rhodecode, 'is_test', False)
295 monkeypatch.setattr(rhodecode, 'is_test', False)
298
296
299
297
300 @pytest.fixture(scope='class')
298 @pytest.fixture(scope='class')
301 def index_location(request, pylonsapp):
299 def index_location(request, pylonsapp):
302 index_location = pylonsapp.config['app_conf']['search.location']
300 index_location = pylonsapp.config['app_conf']['search.location']
303 if request.cls:
301 if request.cls:
304 request.cls.index_location = index_location
302 request.cls.index_location = index_location
305 return index_location
303 return index_location
306
304
307
305
308 @pytest.fixture(scope='session', autouse=True)
306 @pytest.fixture(scope='session', autouse=True)
309 def tests_tmp_path(request):
307 def tests_tmp_path(request):
310 """
308 """
311 Create temporary directory to be used during the test session.
309 Create temporary directory to be used during the test session.
312 """
310 """
313 if not os.path.exists(TESTS_TMP_PATH):
311 if not os.path.exists(TESTS_TMP_PATH):
314 os.makedirs(TESTS_TMP_PATH)
312 os.makedirs(TESTS_TMP_PATH)
315
313
316 if not request.config.getoption('--keep-tmp-path'):
314 if not request.config.getoption('--keep-tmp-path'):
317 @request.addfinalizer
315 @request.addfinalizer
318 def remove_tmp_path():
316 def remove_tmp_path():
319 shutil.rmtree(TESTS_TMP_PATH)
317 shutil.rmtree(TESTS_TMP_PATH)
320
318
321 return TESTS_TMP_PATH
319 return TESTS_TMP_PATH
322
320
323
321
324 @pytest.fixture
322 @pytest.fixture
325 def test_repo_group(request):
323 def test_repo_group(request):
326 """
324 """
327 Create a temporary repository group, and destroy it after
325 Create a temporary repository group, and destroy it after
328 usage automatically
326 usage automatically
329 """
327 """
330 fixture = Fixture()
328 fixture = Fixture()
331 repogroupid = 'test_repo_group_%s' % int(time.time())
329 repogroupid = 'test_repo_group_%s' % int(time.time())
332 repo_group = fixture.create_repo_group(repogroupid)
330 repo_group = fixture.create_repo_group(repogroupid)
333
331
334 def _cleanup():
332 def _cleanup():
335 fixture.destroy_repo_group(repogroupid)
333 fixture.destroy_repo_group(repogroupid)
336
334
337 request.addfinalizer(_cleanup)
335 request.addfinalizer(_cleanup)
338 return repo_group
336 return repo_group
339
337
340
338
341 @pytest.fixture
339 @pytest.fixture
342 def test_user_group(request):
340 def test_user_group(request):
343 """
341 """
344 Create a temporary user group, and destroy it after
342 Create a temporary user group, and destroy it after
345 usage automatically
343 usage automatically
346 """
344 """
347 fixture = Fixture()
345 fixture = Fixture()
348 usergroupid = 'test_user_group_%s' % int(time.time())
346 usergroupid = 'test_user_group_%s' % int(time.time())
349 user_group = fixture.create_user_group(usergroupid)
347 user_group = fixture.create_user_group(usergroupid)
350
348
351 def _cleanup():
349 def _cleanup():
352 fixture.destroy_user_group(user_group)
350 fixture.destroy_user_group(user_group)
353
351
354 request.addfinalizer(_cleanup)
352 request.addfinalizer(_cleanup)
355 return user_group
353 return user_group
356
354
357
355
358 @pytest.fixture(scope='session')
356 @pytest.fixture(scope='session')
359 def test_repo(request):
357 def test_repo(request):
360 container = TestRepoContainer()
358 container = TestRepoContainer()
361 request.addfinalizer(container._cleanup)
359 request.addfinalizer(container._cleanup)
362 return container
360 return container
363
361
364
362
365 class TestRepoContainer(object):
363 class TestRepoContainer(object):
366 """
364 """
367 Container for test repositories which are used read only.
365 Container for test repositories which are used read only.
368
366
369 Repositories will be created on demand and re-used during the lifetime
367 Repositories will be created on demand and re-used during the lifetime
370 of this object.
368 of this object.
371
369
372 Usage to get the svn test repository "minimal"::
370 Usage to get the svn test repository "minimal"::
373
371
374 test_repo = TestContainer()
372 test_repo = TestContainer()
375 repo = test_repo('minimal', 'svn')
373 repo = test_repo('minimal', 'svn')
376
374
377 """
375 """
378
376
379 dump_extractors = {
377 dump_extractors = {
380 'git': utils.extract_git_repo_from_dump,
378 'git': utils.extract_git_repo_from_dump,
381 'hg': utils.extract_hg_repo_from_dump,
379 'hg': utils.extract_hg_repo_from_dump,
382 'svn': utils.extract_svn_repo_from_dump,
380 'svn': utils.extract_svn_repo_from_dump,
383 }
381 }
384
382
385 def __init__(self):
383 def __init__(self):
386 self._cleanup_repos = []
384 self._cleanup_repos = []
387 self._fixture = Fixture()
385 self._fixture = Fixture()
388 self._repos = {}
386 self._repos = {}
389
387
390 def __call__(self, dump_name, backend_alias):
388 def __call__(self, dump_name, backend_alias):
391 key = (dump_name, backend_alias)
389 key = (dump_name, backend_alias)
392 if key not in self._repos:
390 if key not in self._repos:
393 repo = self._create_repo(dump_name, backend_alias)
391 repo = self._create_repo(dump_name, backend_alias)
394 self._repos[key] = repo.repo_id
392 self._repos[key] = repo.repo_id
395 return Repository.get(self._repos[key])
393 return Repository.get(self._repos[key])
396
394
397 def _create_repo(self, dump_name, backend_alias):
395 def _create_repo(self, dump_name, backend_alias):
398 repo_name = '%s-%s' % (backend_alias, dump_name)
396 repo_name = '%s-%s' % (backend_alias, dump_name)
399 backend_class = get_backend(backend_alias)
397 backend_class = get_backend(backend_alias)
400 dump_extractor = self.dump_extractors[backend_alias]
398 dump_extractor = self.dump_extractors[backend_alias]
401 repo_path = dump_extractor(dump_name, repo_name)
399 repo_path = dump_extractor(dump_name, repo_name)
402 vcs_repo = backend_class(repo_path)
400 vcs_repo = backend_class(repo_path)
403 repo2db_mapper({repo_name: vcs_repo})
401 repo2db_mapper({repo_name: vcs_repo})
404 repo = RepoModel().get_by_repo_name(repo_name)
402 repo = RepoModel().get_by_repo_name(repo_name)
405 self._cleanup_repos.append(repo_name)
403 self._cleanup_repos.append(repo_name)
406 return repo
404 return repo
407
405
408 def _cleanup(self):
406 def _cleanup(self):
409 for repo_name in reversed(self._cleanup_repos):
407 for repo_name in reversed(self._cleanup_repos):
410 self._fixture.destroy_repo(repo_name)
408 self._fixture.destroy_repo(repo_name)
411
409
412
410
413 @pytest.fixture
411 @pytest.fixture
414 def backend(request, backend_alias, pylonsapp, test_repo):
412 def backend(request, backend_alias, pylonsapp, test_repo):
415 """
413 """
416 Parametrized fixture which represents a single backend implementation.
414 Parametrized fixture which represents a single backend implementation.
417
415
418 It respects the option `--backends` to focus the test run on specific
416 It respects the option `--backends` to focus the test run on specific
419 backend implementations.
417 backend implementations.
420
418
421 It also supports `pytest.mark.xfail_backends` to mark tests as failing
419 It also supports `pytest.mark.xfail_backends` to mark tests as failing
422 for specific backends. This is intended as a utility for incremental
420 for specific backends. This is intended as a utility for incremental
423 development of a new backend implementation.
421 development of a new backend implementation.
424 """
422 """
425 if backend_alias not in request.config.getoption('--backends'):
423 if backend_alias not in request.config.getoption('--backends'):
426 pytest.skip("Backend %s not selected." % (backend_alias, ))
424 pytest.skip("Backend %s not selected." % (backend_alias, ))
427
425
428 utils.check_xfail_backends(request.node, backend_alias)
426 utils.check_xfail_backends(request.node, backend_alias)
429 utils.check_skip_backends(request.node, backend_alias)
427 utils.check_skip_backends(request.node, backend_alias)
430
428
431 repo_name = 'vcs_test_%s' % (backend_alias, )
429 repo_name = 'vcs_test_%s' % (backend_alias, )
432 backend = Backend(
430 backend = Backend(
433 alias=backend_alias,
431 alias=backend_alias,
434 repo_name=repo_name,
432 repo_name=repo_name,
435 test_name=request.node.name,
433 test_name=request.node.name,
436 test_repo_container=test_repo)
434 test_repo_container=test_repo)
437 request.addfinalizer(backend.cleanup)
435 request.addfinalizer(backend.cleanup)
438 return backend
436 return backend
439
437
440
438
441 @pytest.fixture
439 @pytest.fixture
442 def backend_git(request, pylonsapp, test_repo):
440 def backend_git(request, pylonsapp, test_repo):
443 return backend(request, 'git', pylonsapp, test_repo)
441 return backend(request, 'git', pylonsapp, test_repo)
444
442
445
443
446 @pytest.fixture
444 @pytest.fixture
447 def backend_hg(request, pylonsapp, test_repo):
445 def backend_hg(request, pylonsapp, test_repo):
448 return backend(request, 'hg', pylonsapp, test_repo)
446 return backend(request, 'hg', pylonsapp, test_repo)
449
447
450
448
451 @pytest.fixture
449 @pytest.fixture
452 def backend_svn(request, pylonsapp, test_repo):
450 def backend_svn(request, pylonsapp, test_repo):
453 return backend(request, 'svn', pylonsapp, test_repo)
451 return backend(request, 'svn', pylonsapp, test_repo)
454
452
455
453
456 @pytest.fixture
454 @pytest.fixture
457 def backend_random(backend_git):
455 def backend_random(backend_git):
458 """
456 """
459 Use this to express that your tests need "a backend.
457 Use this to express that your tests need "a backend.
460
458
461 A few of our tests need a backend, so that we can run the code. This
459 A few of our tests need a backend, so that we can run the code. This
462 fixture is intended to be used for such cases. It will pick one of the
460 fixture is intended to be used for such cases. It will pick one of the
463 backends and run the tests.
461 backends and run the tests.
464
462
465 The fixture `backend` would run the test multiple times for each
463 The fixture `backend` would run the test multiple times for each
466 available backend which is a pure waste of time if the test is
464 available backend which is a pure waste of time if the test is
467 independent of the backend type.
465 independent of the backend type.
468 """
466 """
469 # TODO: johbo: Change this to pick a random backend
467 # TODO: johbo: Change this to pick a random backend
470 return backend_git
468 return backend_git
471
469
472
470
473 @pytest.fixture
471 @pytest.fixture
474 def backend_stub(backend_git):
472 def backend_stub(backend_git):
475 """
473 """
476 Use this to express that your tests need a backend stub
474 Use this to express that your tests need a backend stub
477
475
478 TODO: mikhail: Implement a real stub logic instead of returning
476 TODO: mikhail: Implement a real stub logic instead of returning
479 a git backend
477 a git backend
480 """
478 """
481 return backend_git
479 return backend_git
482
480
483
481
484 @pytest.fixture
482 @pytest.fixture
485 def repo_stub(backend_stub):
483 def repo_stub(backend_stub):
486 """
484 """
487 Use this to express that your tests need a repository stub
485 Use this to express that your tests need a repository stub
488 """
486 """
489 return backend_stub.create_repo()
487 return backend_stub.create_repo()
490
488
491
489
492 class Backend(object):
490 class Backend(object):
493 """
491 """
494 Represents the test configuration for one supported backend
492 Represents the test configuration for one supported backend
495
493
496 Provides easy access to different test repositories based on
494 Provides easy access to different test repositories based on
497 `__getitem__`. Such repositories will only be created once per test
495 `__getitem__`. Such repositories will only be created once per test
498 session.
496 session.
499 """
497 """
500
498
501 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
499 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
502 _master_repo = None
500 _master_repo = None
503 _commit_ids = {}
501 _commit_ids = {}
504
502
505 def __init__(self, alias, repo_name, test_name, test_repo_container):
503 def __init__(self, alias, repo_name, test_name, test_repo_container):
506 self.alias = alias
504 self.alias = alias
507 self.repo_name = repo_name
505 self.repo_name = repo_name
508 self._cleanup_repos = []
506 self._cleanup_repos = []
509 self._test_name = test_name
507 self._test_name = test_name
510 self._test_repo_container = test_repo_container
508 self._test_repo_container = test_repo_container
511 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
509 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
512 # Fixture will survive in the end.
510 # Fixture will survive in the end.
513 self._fixture = Fixture()
511 self._fixture = Fixture()
514
512
515 def __getitem__(self, key):
513 def __getitem__(self, key):
516 return self._test_repo_container(key, self.alias)
514 return self._test_repo_container(key, self.alias)
517
515
518 @property
516 @property
519 def repo(self):
517 def repo(self):
520 """
518 """
521 Returns the "current" repository. This is the vcs_test repo or the
519 Returns the "current" repository. This is the vcs_test repo or the
522 last repo which has been created with `create_repo`.
520 last repo which has been created with `create_repo`.
523 """
521 """
524 from rhodecode.model.db import Repository
522 from rhodecode.model.db import Repository
525 return Repository.get_by_repo_name(self.repo_name)
523 return Repository.get_by_repo_name(self.repo_name)
526
524
527 @property
525 @property
528 def default_branch_name(self):
526 def default_branch_name(self):
529 VcsRepository = get_backend(self.alias)
527 VcsRepository = get_backend(self.alias)
530 return VcsRepository.DEFAULT_BRANCH_NAME
528 return VcsRepository.DEFAULT_BRANCH_NAME
531
529
532 @property
530 @property
533 def default_head_id(self):
531 def default_head_id(self):
534 """
532 """
535 Returns the default head id of the underlying backend.
533 Returns the default head id of the underlying backend.
536
534
537 This will be the default branch name in case the backend does have a
535 This will be the default branch name in case the backend does have a
538 default branch. In the other cases it will point to a valid head
536 default branch. In the other cases it will point to a valid head
539 which can serve as the base to create a new commit on top of it.
537 which can serve as the base to create a new commit on top of it.
540 """
538 """
541 vcsrepo = self.repo.scm_instance()
539 vcsrepo = self.repo.scm_instance()
542 head_id = (
540 head_id = (
543 vcsrepo.DEFAULT_BRANCH_NAME or
541 vcsrepo.DEFAULT_BRANCH_NAME or
544 vcsrepo.commit_ids[-1])
542 vcsrepo.commit_ids[-1])
545 return head_id
543 return head_id
546
544
547 @property
545 @property
548 def commit_ids(self):
546 def commit_ids(self):
549 """
547 """
550 Returns the list of commits for the last created repository
548 Returns the list of commits for the last created repository
551 """
549 """
552 return self._commit_ids
550 return self._commit_ids
553
551
554 def create_master_repo(self, commits):
552 def create_master_repo(self, commits):
555 """
553 """
556 Create a repository and remember it as a template.
554 Create a repository and remember it as a template.
557
555
558 This allows to easily create derived repositories to construct
556 This allows to easily create derived repositories to construct
559 more complex scenarios for diff, compare and pull requests.
557 more complex scenarios for diff, compare and pull requests.
560
558
561 Returns a commit map which maps from commit message to raw_id.
559 Returns a commit map which maps from commit message to raw_id.
562 """
560 """
563 self._master_repo = self.create_repo(commits=commits)
561 self._master_repo = self.create_repo(commits=commits)
564 return self._commit_ids
562 return self._commit_ids
565
563
566 def create_repo(
564 def create_repo(
567 self, commits=None, number_of_commits=0, heads=None,
565 self, commits=None, number_of_commits=0, heads=None,
568 name_suffix=u'', **kwargs):
566 name_suffix=u'', **kwargs):
569 """
567 """
570 Create a repository and record it for later cleanup.
568 Create a repository and record it for later cleanup.
571
569
572 :param commits: Optional. A sequence of dict instances.
570 :param commits: Optional. A sequence of dict instances.
573 Will add a commit per entry to the new repository.
571 Will add a commit per entry to the new repository.
574 :param number_of_commits: Optional. If set to a number, this number of
572 :param number_of_commits: Optional. If set to a number, this number of
575 commits will be added to the new repository.
573 commits will be added to the new repository.
576 :param heads: Optional. Can be set to a sequence of of commit
574 :param heads: Optional. Can be set to a sequence of of commit
577 names which shall be pulled in from the master repository.
575 names which shall be pulled in from the master repository.
578
576
579 """
577 """
580 self.repo_name = self._next_repo_name() + name_suffix
578 self.repo_name = self._next_repo_name() + name_suffix
581 repo = self._fixture.create_repo(
579 repo = self._fixture.create_repo(
582 self.repo_name, repo_type=self.alias, **kwargs)
580 self.repo_name, repo_type=self.alias, **kwargs)
583 self._cleanup_repos.append(repo.repo_name)
581 self._cleanup_repos.append(repo.repo_name)
584
582
585 commits = commits or [
583 commits = commits or [
586 {'message': 'Commit %s of %s' % (x, self.repo_name)}
584 {'message': 'Commit %s of %s' % (x, self.repo_name)}
587 for x in xrange(number_of_commits)]
585 for x in xrange(number_of_commits)]
588 self._add_commits_to_repo(repo.scm_instance(), commits)
586 self._add_commits_to_repo(repo.scm_instance(), commits)
589 if heads:
587 if heads:
590 self.pull_heads(repo, heads)
588 self.pull_heads(repo, heads)
591
589
592 return repo
590 return repo
593
591
594 def pull_heads(self, repo, heads):
592 def pull_heads(self, repo, heads):
595 """
593 """
596 Make sure that repo contains all commits mentioned in `heads`
594 Make sure that repo contains all commits mentioned in `heads`
597 """
595 """
598 vcsmaster = self._master_repo.scm_instance()
596 vcsmaster = self._master_repo.scm_instance()
599 vcsrepo = repo.scm_instance()
597 vcsrepo = repo.scm_instance()
600 vcsrepo.config.clear_section('hooks')
598 vcsrepo.config.clear_section('hooks')
601 commit_ids = [self._commit_ids[h] for h in heads]
599 commit_ids = [self._commit_ids[h] for h in heads]
602 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
600 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
603
601
604 def create_fork(self):
602 def create_fork(self):
605 repo_to_fork = self.repo_name
603 repo_to_fork = self.repo_name
606 self.repo_name = self._next_repo_name()
604 self.repo_name = self._next_repo_name()
607 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
605 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
608 self._cleanup_repos.append(self.repo_name)
606 self._cleanup_repos.append(self.repo_name)
609 return repo
607 return repo
610
608
611 def new_repo_name(self, suffix=u''):
609 def new_repo_name(self, suffix=u''):
612 self.repo_name = self._next_repo_name() + suffix
610 self.repo_name = self._next_repo_name() + suffix
613 self._cleanup_repos.append(self.repo_name)
611 self._cleanup_repos.append(self.repo_name)
614 return self.repo_name
612 return self.repo_name
615
613
616 def _next_repo_name(self):
614 def _next_repo_name(self):
617 return u"%s_%s" % (
615 return u"%s_%s" % (
618 self.invalid_repo_name.sub(u'_', self._test_name),
616 self.invalid_repo_name.sub(u'_', self._test_name),
619 len(self._cleanup_repos))
617 len(self._cleanup_repos))
620
618
621 def ensure_file(self, filename, content='Test content\n'):
619 def ensure_file(self, filename, content='Test content\n'):
622 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
620 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
623 commits = [
621 commits = [
624 {'added': [
622 {'added': [
625 FileNode(filename, content=content),
623 FileNode(filename, content=content),
626 ]},
624 ]},
627 ]
625 ]
628 self._add_commits_to_repo(self.repo.scm_instance(), commits)
626 self._add_commits_to_repo(self.repo.scm_instance(), commits)
629
627
630 def enable_downloads(self):
628 def enable_downloads(self):
631 repo = self.repo
629 repo = self.repo
632 repo.enable_downloads = True
630 repo.enable_downloads = True
633 Session().add(repo)
631 Session().add(repo)
634 Session().commit()
632 Session().commit()
635
633
636 def cleanup(self):
634 def cleanup(self):
637 for repo_name in reversed(self._cleanup_repos):
635 for repo_name in reversed(self._cleanup_repos):
638 self._fixture.destroy_repo(repo_name)
636 self._fixture.destroy_repo(repo_name)
639
637
640 def _add_commits_to_repo(self, repo, commits):
638 def _add_commits_to_repo(self, repo, commits):
641 commit_ids = _add_commits_to_repo(repo, commits)
639 commit_ids = _add_commits_to_repo(repo, commits)
642 if not commit_ids:
640 if not commit_ids:
643 return
641 return
644 self._commit_ids = commit_ids
642 self._commit_ids = commit_ids
645
643
646 # Creating refs for Git to allow fetching them from remote repository
644 # Creating refs for Git to allow fetching them from remote repository
647 if self.alias == 'git':
645 if self.alias == 'git':
648 refs = {}
646 refs = {}
649 for message in self._commit_ids:
647 for message in self._commit_ids:
650 # TODO: mikhail: do more special chars replacements
648 # TODO: mikhail: do more special chars replacements
651 ref_name = 'refs/test-refs/{}'.format(
649 ref_name = 'refs/test-refs/{}'.format(
652 message.replace(' ', ''))
650 message.replace(' ', ''))
653 refs[ref_name] = self._commit_ids[message]
651 refs[ref_name] = self._commit_ids[message]
654 self._create_refs(repo, refs)
652 self._create_refs(repo, refs)
655
653
656 def _create_refs(self, repo, refs):
654 def _create_refs(self, repo, refs):
657 for ref_name in refs:
655 for ref_name in refs:
658 repo.set_refs(ref_name, refs[ref_name])
656 repo.set_refs(ref_name, refs[ref_name])
659
657
660
658
661 @pytest.fixture
659 @pytest.fixture
662 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
660 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
663 """
661 """
664 Parametrized fixture which represents a single vcs backend implementation.
662 Parametrized fixture which represents a single vcs backend implementation.
665
663
666 See the fixture `backend` for more details. This one implements the same
664 See the fixture `backend` for more details. This one implements the same
667 concept, but on vcs level. So it does not provide model instances etc.
665 concept, but on vcs level. So it does not provide model instances etc.
668
666
669 Parameters are generated dynamically, see :func:`pytest_generate_tests`
667 Parameters are generated dynamically, see :func:`pytest_generate_tests`
670 for how this works.
668 for how this works.
671 """
669 """
672 if backend_alias not in request.config.getoption('--backends'):
670 if backend_alias not in request.config.getoption('--backends'):
673 pytest.skip("Backend %s not selected." % (backend_alias, ))
671 pytest.skip("Backend %s not selected." % (backend_alias, ))
674
672
675 utils.check_xfail_backends(request.node, backend_alias)
673 utils.check_xfail_backends(request.node, backend_alias)
676 utils.check_skip_backends(request.node, backend_alias)
674 utils.check_skip_backends(request.node, backend_alias)
677
675
678 repo_name = 'vcs_test_%s' % (backend_alias, )
676 repo_name = 'vcs_test_%s' % (backend_alias, )
679 repo_path = os.path.join(tests_tmp_path, repo_name)
677 repo_path = os.path.join(tests_tmp_path, repo_name)
680 backend = VcsBackend(
678 backend = VcsBackend(
681 alias=backend_alias,
679 alias=backend_alias,
682 repo_path=repo_path,
680 repo_path=repo_path,
683 test_name=request.node.name,
681 test_name=request.node.name,
684 test_repo_container=test_repo)
682 test_repo_container=test_repo)
685 request.addfinalizer(backend.cleanup)
683 request.addfinalizer(backend.cleanup)
686 return backend
684 return backend
687
685
688
686
689 @pytest.fixture
687 @pytest.fixture
690 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
688 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
691 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
689 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
692
690
693
691
694 @pytest.fixture
692 @pytest.fixture
695 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
693 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
696 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
694 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
697
695
698
696
699 @pytest.fixture
697 @pytest.fixture
700 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
698 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
701 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
699 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
702
700
703
701
704 @pytest.fixture
702 @pytest.fixture
705 def vcsbackend_random(vcsbackend_git):
703 def vcsbackend_random(vcsbackend_git):
706 """
704 """
707 Use this to express that your tests need "a vcsbackend".
705 Use this to express that your tests need "a vcsbackend".
708
706
709 The fixture `vcsbackend` would run the test multiple times for each
707 The fixture `vcsbackend` would run the test multiple times for each
710 available vcs backend which is a pure waste of time if the test is
708 available vcs backend which is a pure waste of time if the test is
711 independent of the vcs backend type.
709 independent of the vcs backend type.
712 """
710 """
713 # TODO: johbo: Change this to pick a random backend
711 # TODO: johbo: Change this to pick a random backend
714 return vcsbackend_git
712 return vcsbackend_git
715
713
716
714
717 @pytest.fixture
715 @pytest.fixture
718 def vcsbackend_stub(vcsbackend_git):
716 def vcsbackend_stub(vcsbackend_git):
719 """
717 """
720 Use this to express that your test just needs a stub of a vcsbackend.
718 Use this to express that your test just needs a stub of a vcsbackend.
721
719
722 Plan is to eventually implement an in-memory stub to speed tests up.
720 Plan is to eventually implement an in-memory stub to speed tests up.
723 """
721 """
724 return vcsbackend_git
722 return vcsbackend_git
725
723
726
724
727 class VcsBackend(object):
725 class VcsBackend(object):
728 """
726 """
729 Represents the test configuration for one supported vcs backend.
727 Represents the test configuration for one supported vcs backend.
730 """
728 """
731
729
732 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
730 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
733
731
734 def __init__(self, alias, repo_path, test_name, test_repo_container):
732 def __init__(self, alias, repo_path, test_name, test_repo_container):
735 self.alias = alias
733 self.alias = alias
736 self._repo_path = repo_path
734 self._repo_path = repo_path
737 self._cleanup_repos = []
735 self._cleanup_repos = []
738 self._test_name = test_name
736 self._test_name = test_name
739 self._test_repo_container = test_repo_container
737 self._test_repo_container = test_repo_container
740
738
741 def __getitem__(self, key):
739 def __getitem__(self, key):
742 return self._test_repo_container(key, self.alias).scm_instance()
740 return self._test_repo_container(key, self.alias).scm_instance()
743
741
744 @property
742 @property
745 def repo(self):
743 def repo(self):
746 """
744 """
747 Returns the "current" repository. This is the vcs_test repo of the last
745 Returns the "current" repository. This is the vcs_test repo of the last
748 repo which has been created.
746 repo which has been created.
749 """
747 """
750 Repository = get_backend(self.alias)
748 Repository = get_backend(self.alias)
751 return Repository(self._repo_path)
749 return Repository(self._repo_path)
752
750
753 @property
751 @property
754 def backend(self):
752 def backend(self):
755 """
753 """
756 Returns the backend implementation class.
754 Returns the backend implementation class.
757 """
755 """
758 return get_backend(self.alias)
756 return get_backend(self.alias)
759
757
760 def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None):
758 def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None):
761 repo_name = self._next_repo_name()
759 repo_name = self._next_repo_name()
762 self._repo_path = get_new_dir(repo_name)
760 self._repo_path = get_new_dir(repo_name)
763 repo_class = get_backend(self.alias)
761 repo_class = get_backend(self.alias)
764 src_url = None
762 src_url = None
765 if _clone_repo:
763 if _clone_repo:
766 src_url = _clone_repo.path
764 src_url = _clone_repo.path
767 repo = repo_class(self._repo_path, create=True, src_url=src_url)
765 repo = repo_class(self._repo_path, create=True, src_url=src_url)
768 self._cleanup_repos.append(repo)
766 self._cleanup_repos.append(repo)
769
767
770 commits = commits or [
768 commits = commits or [
771 {'message': 'Commit %s of %s' % (x, repo_name)}
769 {'message': 'Commit %s of %s' % (x, repo_name)}
772 for x in xrange(number_of_commits)]
770 for x in xrange(number_of_commits)]
773 _add_commits_to_repo(repo, commits)
771 _add_commits_to_repo(repo, commits)
774 return repo
772 return repo
775
773
776 def clone_repo(self, repo):
774 def clone_repo(self, repo):
777 return self.create_repo(_clone_repo=repo)
775 return self.create_repo(_clone_repo=repo)
778
776
779 def cleanup(self):
777 def cleanup(self):
780 for repo in self._cleanup_repos:
778 for repo in self._cleanup_repos:
781 shutil.rmtree(repo.path)
779 shutil.rmtree(repo.path)
782
780
783 def new_repo_path(self):
781 def new_repo_path(self):
784 repo_name = self._next_repo_name()
782 repo_name = self._next_repo_name()
785 self._repo_path = get_new_dir(repo_name)
783 self._repo_path = get_new_dir(repo_name)
786 return self._repo_path
784 return self._repo_path
787
785
788 def _next_repo_name(self):
786 def _next_repo_name(self):
789 return "%s_%s" % (
787 return "%s_%s" % (
790 self.invalid_repo_name.sub('_', self._test_name),
788 self.invalid_repo_name.sub('_', self._test_name),
791 len(self._cleanup_repos))
789 len(self._cleanup_repos))
792
790
793 def add_file(self, repo, filename, content='Test content\n'):
791 def add_file(self, repo, filename, content='Test content\n'):
794 imc = repo.in_memory_commit
792 imc = repo.in_memory_commit
795 imc.add(FileNode(filename, content=content))
793 imc.add(FileNode(filename, content=content))
796 imc.commit(
794 imc.commit(
797 message=u'Automatic commit from vcsbackend fixture',
795 message=u'Automatic commit from vcsbackend fixture',
798 author=u'Automatic')
796 author=u'Automatic')
799
797
800 def ensure_file(self, filename, content='Test content\n'):
798 def ensure_file(self, filename, content='Test content\n'):
801 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
799 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
802 self.add_file(self.repo, filename, content)
800 self.add_file(self.repo, filename, content)
803
801
804
802
805 def _add_commits_to_repo(vcs_repo, commits):
803 def _add_commits_to_repo(vcs_repo, commits):
806 commit_ids = {}
804 commit_ids = {}
807 if not commits:
805 if not commits:
808 return commit_ids
806 return commit_ids
809
807
810 imc = vcs_repo.in_memory_commit
808 imc = vcs_repo.in_memory_commit
811 commit = None
809 commit = None
812
810
813 for idx, commit in enumerate(commits):
811 for idx, commit in enumerate(commits):
814 message = unicode(commit.get('message', 'Commit %s' % idx))
812 message = unicode(commit.get('message', 'Commit %s' % idx))
815
813
816 for node in commit.get('added', []):
814 for node in commit.get('added', []):
817 imc.add(FileNode(node.path, content=node.content))
815 imc.add(FileNode(node.path, content=node.content))
818 for node in commit.get('changed', []):
816 for node in commit.get('changed', []):
819 imc.change(FileNode(node.path, content=node.content))
817 imc.change(FileNode(node.path, content=node.content))
820 for node in commit.get('removed', []):
818 for node in commit.get('removed', []):
821 imc.remove(FileNode(node.path))
819 imc.remove(FileNode(node.path))
822
820
823 parents = [
821 parents = [
824 vcs_repo.get_commit(commit_id=commit_ids[p])
822 vcs_repo.get_commit(commit_id=commit_ids[p])
825 for p in commit.get('parents', [])]
823 for p in commit.get('parents', [])]
826
824
827 operations = ('added', 'changed', 'removed')
825 operations = ('added', 'changed', 'removed')
828 if not any((commit.get(o) for o in operations)):
826 if not any((commit.get(o) for o in operations)):
829 imc.add(FileNode('file_%s' % idx, content=message))
827 imc.add(FileNode('file_%s' % idx, content=message))
830
828
831 commit = imc.commit(
829 commit = imc.commit(
832 message=message,
830 message=message,
833 author=unicode(commit.get('author', 'Automatic')),
831 author=unicode(commit.get('author', 'Automatic')),
834 date=commit.get('date'),
832 date=commit.get('date'),
835 branch=commit.get('branch'),
833 branch=commit.get('branch'),
836 parents=parents)
834 parents=parents)
837
835
838 commit_ids[commit.message] = commit.raw_id
836 commit_ids[commit.message] = commit.raw_id
839
837
840 return commit_ids
838 return commit_ids
841
839
842
840
843 @pytest.fixture
841 @pytest.fixture
844 def reposerver(request):
842 def reposerver(request):
845 """
843 """
846 Allows to serve a backend repository
844 Allows to serve a backend repository
847 """
845 """
848
846
849 repo_server = RepoServer()
847 repo_server = RepoServer()
850 request.addfinalizer(repo_server.cleanup)
848 request.addfinalizer(repo_server.cleanup)
851 return repo_server
849 return repo_server
852
850
853
851
854 class RepoServer(object):
852 class RepoServer(object):
855 """
853 """
856 Utility to serve a local repository for the duration of a test case.
854 Utility to serve a local repository for the duration of a test case.
857
855
858 Supports only Subversion so far.
856 Supports only Subversion so far.
859 """
857 """
860
858
861 url = None
859 url = None
862
860
863 def __init__(self):
861 def __init__(self):
864 self._cleanup_servers = []
862 self._cleanup_servers = []
865
863
866 def serve(self, vcsrepo):
864 def serve(self, vcsrepo):
867 if vcsrepo.alias != 'svn':
865 if vcsrepo.alias != 'svn':
868 raise TypeError("Backend %s not supported" % vcsrepo.alias)
866 raise TypeError("Backend %s not supported" % vcsrepo.alias)
869
867
870 proc = subprocess32.Popen(
868 proc = subprocess32.Popen(
871 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
869 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
872 '--root', vcsrepo.path])
870 '--root', vcsrepo.path])
873 self._cleanup_servers.append(proc)
871 self._cleanup_servers.append(proc)
874 self.url = 'svn://localhost'
872 self.url = 'svn://localhost'
875
873
876 def cleanup(self):
874 def cleanup(self):
877 for proc in self._cleanup_servers:
875 for proc in self._cleanup_servers:
878 proc.terminate()
876 proc.terminate()
879
877
880
878
881 @pytest.fixture
879 @pytest.fixture
882 def pr_util(backend, request):
880 def pr_util(backend, request):
883 """
881 """
884 Utility for tests of models and for functional tests around pull requests.
882 Utility for tests of models and for functional tests around pull requests.
885
883
886 It gives an instance of :class:`PRTestUtility` which provides various
884 It gives an instance of :class:`PRTestUtility` which provides various
887 utility methods around one pull request.
885 utility methods around one pull request.
888
886
889 This fixture uses `backend` and inherits its parameterization.
887 This fixture uses `backend` and inherits its parameterization.
890 """
888 """
891
889
892 util = PRTestUtility(backend)
890 util = PRTestUtility(backend)
893
891
894 @request.addfinalizer
892 @request.addfinalizer
895 def cleanup():
893 def cleanup():
896 util.cleanup()
894 util.cleanup()
897
895
898 return util
896 return util
899
897
900
898
901 class PRTestUtility(object):
899 class PRTestUtility(object):
902
900
903 pull_request = None
901 pull_request = None
904 pull_request_id = None
902 pull_request_id = None
905 mergeable_patcher = None
903 mergeable_patcher = None
906 mergeable_mock = None
904 mergeable_mock = None
907 notification_patcher = None
905 notification_patcher = None
908
906
909 def __init__(self, backend):
907 def __init__(self, backend):
910 self.backend = backend
908 self.backend = backend
911
909
912 def create_pull_request(
910 def create_pull_request(
913 self, commits=None, target_head=None, source_head=None,
911 self, commits=None, target_head=None, source_head=None,
914 revisions=None, approved=False, author=None, mergeable=False,
912 revisions=None, approved=False, author=None, mergeable=False,
915 enable_notifications=True, name_suffix=u'', reviewers=None,
913 enable_notifications=True, name_suffix=u'', reviewers=None,
916 title=u"Test", description=u"Description"):
914 title=u"Test", description=u"Description"):
917 self.set_mergeable(mergeable)
915 self.set_mergeable(mergeable)
918 if not enable_notifications:
916 if not enable_notifications:
919 # mock notification side effect
917 # mock notification side effect
920 self.notification_patcher = mock.patch(
918 self.notification_patcher = mock.patch(
921 'rhodecode.model.notification.NotificationModel.create')
919 'rhodecode.model.notification.NotificationModel.create')
922 self.notification_patcher.start()
920 self.notification_patcher.start()
923
921
924 if not self.pull_request:
922 if not self.pull_request:
925 if not commits:
923 if not commits:
926 commits = [
924 commits = [
927 {'message': 'c1'},
925 {'message': 'c1'},
928 {'message': 'c2'},
926 {'message': 'c2'},
929 {'message': 'c3'},
927 {'message': 'c3'},
930 ]
928 ]
931 target_head = 'c1'
929 target_head = 'c1'
932 source_head = 'c2'
930 source_head = 'c2'
933 revisions = ['c2']
931 revisions = ['c2']
934
932
935 self.commit_ids = self.backend.create_master_repo(commits)
933 self.commit_ids = self.backend.create_master_repo(commits)
936 self.target_repository = self.backend.create_repo(
934 self.target_repository = self.backend.create_repo(
937 heads=[target_head], name_suffix=name_suffix)
935 heads=[target_head], name_suffix=name_suffix)
938 self.source_repository = self.backend.create_repo(
936 self.source_repository = self.backend.create_repo(
939 heads=[source_head], name_suffix=name_suffix)
937 heads=[source_head], name_suffix=name_suffix)
940 self.author = author or UserModel().get_by_username(
938 self.author = author or UserModel().get_by_username(
941 TEST_USER_ADMIN_LOGIN)
939 TEST_USER_ADMIN_LOGIN)
942
940
943 model = PullRequestModel()
941 model = PullRequestModel()
944 self.create_parameters = {
942 self.create_parameters = {
945 'created_by': self.author,
943 'created_by': self.author,
946 'source_repo': self.source_repository.repo_name,
944 'source_repo': self.source_repository.repo_name,
947 'source_ref': self._default_branch_reference(source_head),
945 'source_ref': self._default_branch_reference(source_head),
948 'target_repo': self.target_repository.repo_name,
946 'target_repo': self.target_repository.repo_name,
949 'target_ref': self._default_branch_reference(target_head),
947 'target_ref': self._default_branch_reference(target_head),
950 'revisions': [self.commit_ids[r] for r in revisions],
948 'revisions': [self.commit_ids[r] for r in revisions],
951 'reviewers': reviewers or self._get_reviewers(),
949 'reviewers': reviewers or self._get_reviewers(),
952 'title': title,
950 'title': title,
953 'description': description,
951 'description': description,
954 }
952 }
955 self.pull_request = model.create(**self.create_parameters)
953 self.pull_request = model.create(**self.create_parameters)
956 assert model.get_versions(self.pull_request) == []
954 assert model.get_versions(self.pull_request) == []
957
955
958 self.pull_request_id = self.pull_request.pull_request_id
956 self.pull_request_id = self.pull_request.pull_request_id
959
957
960 if approved:
958 if approved:
961 self.approve()
959 self.approve()
962
960
963 Session().add(self.pull_request)
961 Session().add(self.pull_request)
964 Session().commit()
962 Session().commit()
965
963
966 return self.pull_request
964 return self.pull_request
967
965
968 def approve(self):
966 def approve(self):
969 self.create_status_votes(
967 self.create_status_votes(
970 ChangesetStatus.STATUS_APPROVED,
968 ChangesetStatus.STATUS_APPROVED,
971 *self.pull_request.reviewers)
969 *self.pull_request.reviewers)
972
970
973 def close(self):
971 def close(self):
974 PullRequestModel().close_pull_request(self.pull_request, self.author)
972 PullRequestModel().close_pull_request(self.pull_request, self.author)
975
973
976 def _default_branch_reference(self, commit_message):
974 def _default_branch_reference(self, commit_message):
977 reference = '%s:%s:%s' % (
975 reference = '%s:%s:%s' % (
978 'branch',
976 'branch',
979 self.backend.default_branch_name,
977 self.backend.default_branch_name,
980 self.commit_ids[commit_message])
978 self.commit_ids[commit_message])
981 return reference
979 return reference
982
980
983 def _get_reviewers(self):
981 def _get_reviewers(self):
984 model = UserModel()
982 model = UserModel()
985 return [
983 return [
986 model.get_by_username(TEST_USER_REGULAR_LOGIN),
984 model.get_by_username(TEST_USER_REGULAR_LOGIN),
987 model.get_by_username(TEST_USER_REGULAR2_LOGIN),
985 model.get_by_username(TEST_USER_REGULAR2_LOGIN),
988 ]
986 ]
989
987
990 def update_source_repository(self, head=None):
988 def update_source_repository(self, head=None):
991 heads = [head or 'c3']
989 heads = [head or 'c3']
992 self.backend.pull_heads(self.source_repository, heads=heads)
990 self.backend.pull_heads(self.source_repository, heads=heads)
993
991
994 def add_one_commit(self, head=None):
992 def add_one_commit(self, head=None):
995 self.update_source_repository(head=head)
993 self.update_source_repository(head=head)
996 old_commit_ids = set(self.pull_request.revisions)
994 old_commit_ids = set(self.pull_request.revisions)
997 PullRequestModel().update_commits(self.pull_request)
995 PullRequestModel().update_commits(self.pull_request)
998 commit_ids = set(self.pull_request.revisions)
996 commit_ids = set(self.pull_request.revisions)
999 new_commit_ids = commit_ids - old_commit_ids
997 new_commit_ids = commit_ids - old_commit_ids
1000 assert len(new_commit_ids) == 1
998 assert len(new_commit_ids) == 1
1001 return new_commit_ids.pop()
999 return new_commit_ids.pop()
1002
1000
1003 def remove_one_commit(self):
1001 def remove_one_commit(self):
1004 assert len(self.pull_request.revisions) == 2
1002 assert len(self.pull_request.revisions) == 2
1005 source_vcs = self.source_repository.scm_instance()
1003 source_vcs = self.source_repository.scm_instance()
1006 removed_commit_id = source_vcs.commit_ids[-1]
1004 removed_commit_id = source_vcs.commit_ids[-1]
1007
1005
1008 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
1006 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
1009 # remove the if once that's sorted out.
1007 # remove the if once that's sorted out.
1010 if self.backend.alias == "git":
1008 if self.backend.alias == "git":
1011 kwargs = {'branch_name': self.backend.default_branch_name}
1009 kwargs = {'branch_name': self.backend.default_branch_name}
1012 else:
1010 else:
1013 kwargs = {}
1011 kwargs = {}
1014 source_vcs.strip(removed_commit_id, **kwargs)
1012 source_vcs.strip(removed_commit_id, **kwargs)
1015
1013
1016 PullRequestModel().update_commits(self.pull_request)
1014 PullRequestModel().update_commits(self.pull_request)
1017 assert len(self.pull_request.revisions) == 1
1015 assert len(self.pull_request.revisions) == 1
1018 return removed_commit_id
1016 return removed_commit_id
1019
1017
1020 def create_comment(self, linked_to=None):
1018 def create_comment(self, linked_to=None):
1021 comment = CommentsModel().create(
1019 comment = CommentsModel().create(
1022 text=u"Test comment",
1020 text=u"Test comment",
1023 repo=self.target_repository.repo_name,
1021 repo=self.target_repository.repo_name,
1024 user=self.author,
1022 user=self.author,
1025 pull_request=self.pull_request)
1023 pull_request=self.pull_request)
1026 assert comment.pull_request_version_id is None
1024 assert comment.pull_request_version_id is None
1027
1025
1028 if linked_to:
1026 if linked_to:
1029 PullRequestModel()._link_comments_to_version(linked_to)
1027 PullRequestModel()._link_comments_to_version(linked_to)
1030
1028
1031 return comment
1029 return comment
1032
1030
1033 def create_inline_comment(
1031 def create_inline_comment(
1034 self, linked_to=None, line_no=u'n1', file_path='file_1'):
1032 self, linked_to=None, line_no=u'n1', file_path='file_1'):
1035 comment = CommentsModel().create(
1033 comment = CommentsModel().create(
1036 text=u"Test comment",
1034 text=u"Test comment",
1037 repo=self.target_repository.repo_name,
1035 repo=self.target_repository.repo_name,
1038 user=self.author,
1036 user=self.author,
1039 line_no=line_no,
1037 line_no=line_no,
1040 f_path=file_path,
1038 f_path=file_path,
1041 pull_request=self.pull_request)
1039 pull_request=self.pull_request)
1042 assert comment.pull_request_version_id is None
1040 assert comment.pull_request_version_id is None
1043
1041
1044 if linked_to:
1042 if linked_to:
1045 PullRequestModel()._link_comments_to_version(linked_to)
1043 PullRequestModel()._link_comments_to_version(linked_to)
1046
1044
1047 return comment
1045 return comment
1048
1046
1049 def create_version_of_pull_request(self):
1047 def create_version_of_pull_request(self):
1050 pull_request = self.create_pull_request()
1048 pull_request = self.create_pull_request()
1051 version = PullRequestModel()._create_version_from_snapshot(
1049 version = PullRequestModel()._create_version_from_snapshot(
1052 pull_request)
1050 pull_request)
1053 return version
1051 return version
1054
1052
1055 def create_status_votes(self, status, *reviewers):
1053 def create_status_votes(self, status, *reviewers):
1056 for reviewer in reviewers:
1054 for reviewer in reviewers:
1057 ChangesetStatusModel().set_status(
1055 ChangesetStatusModel().set_status(
1058 repo=self.pull_request.target_repo,
1056 repo=self.pull_request.target_repo,
1059 status=status,
1057 status=status,
1060 user=reviewer.user_id,
1058 user=reviewer.user_id,
1061 pull_request=self.pull_request)
1059 pull_request=self.pull_request)
1062
1060
1063 def set_mergeable(self, value):
1061 def set_mergeable(self, value):
1064 if not self.mergeable_patcher:
1062 if not self.mergeable_patcher:
1065 self.mergeable_patcher = mock.patch.object(
1063 self.mergeable_patcher = mock.patch.object(
1066 VcsSettingsModel, 'get_general_settings')
1064 VcsSettingsModel, 'get_general_settings')
1067 self.mergeable_mock = self.mergeable_patcher.start()
1065 self.mergeable_mock = self.mergeable_patcher.start()
1068 self.mergeable_mock.return_value = {
1066 self.mergeable_mock.return_value = {
1069 'rhodecode_pr_merge_enabled': value}
1067 'rhodecode_pr_merge_enabled': value}
1070
1068
1071 def cleanup(self):
1069 def cleanup(self):
1072 # In case the source repository is already cleaned up, the pull
1070 # In case the source repository is already cleaned up, the pull
1073 # request will already be deleted.
1071 # request will already be deleted.
1074 pull_request = PullRequest().get(self.pull_request_id)
1072 pull_request = PullRequest().get(self.pull_request_id)
1075 if pull_request:
1073 if pull_request:
1076 PullRequestModel().delete(pull_request)
1074 PullRequestModel().delete(pull_request)
1077 Session().commit()
1075 Session().commit()
1078
1076
1079 if self.notification_patcher:
1077 if self.notification_patcher:
1080 self.notification_patcher.stop()
1078 self.notification_patcher.stop()
1081
1079
1082 if self.mergeable_patcher:
1080 if self.mergeable_patcher:
1083 self.mergeable_patcher.stop()
1081 self.mergeable_patcher.stop()
1084
1082
1085
1083
1086 @pytest.fixture
1084 @pytest.fixture
1087 def user_admin(pylonsapp):
1085 def user_admin(pylonsapp):
1088 """
1086 """
1089 Provides the default admin test user as an instance of `db.User`.
1087 Provides the default admin test user as an instance of `db.User`.
1090 """
1088 """
1091 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1089 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1092 return user
1090 return user
1093
1091
1094
1092
1095 @pytest.fixture
1093 @pytest.fixture
1096 def user_regular(pylonsapp):
1094 def user_regular(pylonsapp):
1097 """
1095 """
1098 Provides the default regular test user as an instance of `db.User`.
1096 Provides the default regular test user as an instance of `db.User`.
1099 """
1097 """
1100 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1098 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1101 return user
1099 return user
1102
1100
1103
1101
1104 @pytest.fixture
1102 @pytest.fixture
1105 def user_util(request, pylonsapp):
1103 def user_util(request, pylonsapp):
1106 """
1104 """
1107 Provides a wired instance of `UserUtility` with integrated cleanup.
1105 Provides a wired instance of `UserUtility` with integrated cleanup.
1108 """
1106 """
1109 utility = UserUtility(test_name=request.node.name)
1107 utility = UserUtility(test_name=request.node.name)
1110 request.addfinalizer(utility.cleanup)
1108 request.addfinalizer(utility.cleanup)
1111 return utility
1109 return utility
1112
1110
1113
1111
1114 # TODO: johbo: Split this up into utilities per domain or something similar
1112 # TODO: johbo: Split this up into utilities per domain or something similar
1115 class UserUtility(object):
1113 class UserUtility(object):
1116
1114
1117 def __init__(self, test_name="test"):
1115 def __init__(self, test_name="test"):
1118 self._test_name = self._sanitize_name(test_name)
1116 self._test_name = self._sanitize_name(test_name)
1119 self.fixture = Fixture()
1117 self.fixture = Fixture()
1120 self.repo_group_ids = []
1118 self.repo_group_ids = []
1121 self.repos_ids = []
1119 self.repos_ids = []
1122 self.user_ids = []
1120 self.user_ids = []
1123 self.user_group_ids = []
1121 self.user_group_ids = []
1124 self.user_repo_permission_ids = []
1122 self.user_repo_permission_ids = []
1125 self.user_group_repo_permission_ids = []
1123 self.user_group_repo_permission_ids = []
1126 self.user_repo_group_permission_ids = []
1124 self.user_repo_group_permission_ids = []
1127 self.user_group_repo_group_permission_ids = []
1125 self.user_group_repo_group_permission_ids = []
1128 self.user_user_group_permission_ids = []
1126 self.user_user_group_permission_ids = []
1129 self.user_group_user_group_permission_ids = []
1127 self.user_group_user_group_permission_ids = []
1130 self.user_permissions = []
1128 self.user_permissions = []
1131
1129
1132 def _sanitize_name(self, name):
1130 def _sanitize_name(self, name):
1133 for char in ['[', ']']:
1131 for char in ['[', ']']:
1134 name = name.replace(char, '_')
1132 name = name.replace(char, '_')
1135 return name
1133 return name
1136
1134
1137 def create_repo_group(
1135 def create_repo_group(
1138 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1136 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1139 group_name = "{prefix}_repogroup_{count}".format(
1137 group_name = "{prefix}_repogroup_{count}".format(
1140 prefix=self._test_name,
1138 prefix=self._test_name,
1141 count=len(self.repo_group_ids))
1139 count=len(self.repo_group_ids))
1142 repo_group = self.fixture.create_repo_group(
1140 repo_group = self.fixture.create_repo_group(
1143 group_name, cur_user=owner)
1141 group_name, cur_user=owner)
1144 if auto_cleanup:
1142 if auto_cleanup:
1145 self.repo_group_ids.append(repo_group.group_id)
1143 self.repo_group_ids.append(repo_group.group_id)
1146 return repo_group
1144 return repo_group
1147
1145
1148 def create_repo(self, owner=TEST_USER_ADMIN_LOGIN, parent=None, auto_cleanup=True):
1146 def create_repo(self, owner=TEST_USER_ADMIN_LOGIN, parent=None, auto_cleanup=True):
1149 repo_name = "{prefix}_repository_{count}".format(
1147 repo_name = "{prefix}_repository_{count}".format(
1150 prefix=self._test_name,
1148 prefix=self._test_name,
1151 count=len(self.repos_ids))
1149 count=len(self.repos_ids))
1152
1150
1153 repository = self.fixture.create_repo(
1151 repository = self.fixture.create_repo(
1154 repo_name, cur_user=owner, repo_group=parent)
1152 repo_name, cur_user=owner, repo_group=parent)
1155 if auto_cleanup:
1153 if auto_cleanup:
1156 self.repos_ids.append(repository.repo_id)
1154 self.repos_ids.append(repository.repo_id)
1157 return repository
1155 return repository
1158
1156
1159 def create_user(self, auto_cleanup=True, **kwargs):
1157 def create_user(self, auto_cleanup=True, **kwargs):
1160 user_name = "{prefix}_user_{count}".format(
1158 user_name = "{prefix}_user_{count}".format(
1161 prefix=self._test_name,
1159 prefix=self._test_name,
1162 count=len(self.user_ids))
1160 count=len(self.user_ids))
1163 user = self.fixture.create_user(user_name, **kwargs)
1161 user = self.fixture.create_user(user_name, **kwargs)
1164 if auto_cleanup:
1162 if auto_cleanup:
1165 self.user_ids.append(user.user_id)
1163 self.user_ids.append(user.user_id)
1166 return user
1164 return user
1167
1165
1168 def create_user_with_group(self):
1166 def create_user_with_group(self):
1169 user = self.create_user()
1167 user = self.create_user()
1170 user_group = self.create_user_group(members=[user])
1168 user_group = self.create_user_group(members=[user])
1171 return user, user_group
1169 return user, user_group
1172
1170
1173 def create_user_group(self, owner=TEST_USER_ADMIN_LOGIN, members=None,
1171 def create_user_group(self, owner=TEST_USER_ADMIN_LOGIN, members=None,
1174 auto_cleanup=True, **kwargs):
1172 auto_cleanup=True, **kwargs):
1175 group_name = "{prefix}_usergroup_{count}".format(
1173 group_name = "{prefix}_usergroup_{count}".format(
1176 prefix=self._test_name,
1174 prefix=self._test_name,
1177 count=len(self.user_group_ids))
1175 count=len(self.user_group_ids))
1178 user_group = self.fixture.create_user_group(
1176 user_group = self.fixture.create_user_group(
1179 group_name, cur_user=owner, **kwargs)
1177 group_name, cur_user=owner, **kwargs)
1180
1178
1181 if auto_cleanup:
1179 if auto_cleanup:
1182 self.user_group_ids.append(user_group.users_group_id)
1180 self.user_group_ids.append(user_group.users_group_id)
1183 if members:
1181 if members:
1184 for user in members:
1182 for user in members:
1185 UserGroupModel().add_user_to_group(user_group, user)
1183 UserGroupModel().add_user_to_group(user_group, user)
1186 return user_group
1184 return user_group
1187
1185
1188 def grant_user_permission(self, user_name, permission_name):
1186 def grant_user_permission(self, user_name, permission_name):
1189 self._inherit_default_user_permissions(user_name, False)
1187 self._inherit_default_user_permissions(user_name, False)
1190 self.user_permissions.append((user_name, permission_name))
1188 self.user_permissions.append((user_name, permission_name))
1191
1189
1192 def grant_user_permission_to_repo_group(
1190 def grant_user_permission_to_repo_group(
1193 self, repo_group, user, permission_name):
1191 self, repo_group, user, permission_name):
1194 permission = RepoGroupModel().grant_user_permission(
1192 permission = RepoGroupModel().grant_user_permission(
1195 repo_group, user, permission_name)
1193 repo_group, user, permission_name)
1196 self.user_repo_group_permission_ids.append(
1194 self.user_repo_group_permission_ids.append(
1197 (repo_group.group_id, user.user_id))
1195 (repo_group.group_id, user.user_id))
1198 return permission
1196 return permission
1199
1197
1200 def grant_user_group_permission_to_repo_group(
1198 def grant_user_group_permission_to_repo_group(
1201 self, repo_group, user_group, permission_name):
1199 self, repo_group, user_group, permission_name):
1202 permission = RepoGroupModel().grant_user_group_permission(
1200 permission = RepoGroupModel().grant_user_group_permission(
1203 repo_group, user_group, permission_name)
1201 repo_group, user_group, permission_name)
1204 self.user_group_repo_group_permission_ids.append(
1202 self.user_group_repo_group_permission_ids.append(
1205 (repo_group.group_id, user_group.users_group_id))
1203 (repo_group.group_id, user_group.users_group_id))
1206 return permission
1204 return permission
1207
1205
1208 def grant_user_permission_to_repo(
1206 def grant_user_permission_to_repo(
1209 self, repo, user, permission_name):
1207 self, repo, user, permission_name):
1210 permission = RepoModel().grant_user_permission(
1208 permission = RepoModel().grant_user_permission(
1211 repo, user, permission_name)
1209 repo, user, permission_name)
1212 self.user_repo_permission_ids.append(
1210 self.user_repo_permission_ids.append(
1213 (repo.repo_id, user.user_id))
1211 (repo.repo_id, user.user_id))
1214 return permission
1212 return permission
1215
1213
1216 def grant_user_group_permission_to_repo(
1214 def grant_user_group_permission_to_repo(
1217 self, repo, user_group, permission_name):
1215 self, repo, user_group, permission_name):
1218 permission = RepoModel().grant_user_group_permission(
1216 permission = RepoModel().grant_user_group_permission(
1219 repo, user_group, permission_name)
1217 repo, user_group, permission_name)
1220 self.user_group_repo_permission_ids.append(
1218 self.user_group_repo_permission_ids.append(
1221 (repo.repo_id, user_group.users_group_id))
1219 (repo.repo_id, user_group.users_group_id))
1222 return permission
1220 return permission
1223
1221
1224 def grant_user_permission_to_user_group(
1222 def grant_user_permission_to_user_group(
1225 self, target_user_group, user, permission_name):
1223 self, target_user_group, user, permission_name):
1226 permission = UserGroupModel().grant_user_permission(
1224 permission = UserGroupModel().grant_user_permission(
1227 target_user_group, user, permission_name)
1225 target_user_group, user, permission_name)
1228 self.user_user_group_permission_ids.append(
1226 self.user_user_group_permission_ids.append(
1229 (target_user_group.users_group_id, user.user_id))
1227 (target_user_group.users_group_id, user.user_id))
1230 return permission
1228 return permission
1231
1229
1232 def grant_user_group_permission_to_user_group(
1230 def grant_user_group_permission_to_user_group(
1233 self, target_user_group, user_group, permission_name):
1231 self, target_user_group, user_group, permission_name):
1234 permission = UserGroupModel().grant_user_group_permission(
1232 permission = UserGroupModel().grant_user_group_permission(
1235 target_user_group, user_group, permission_name)
1233 target_user_group, user_group, permission_name)
1236 self.user_group_user_group_permission_ids.append(
1234 self.user_group_user_group_permission_ids.append(
1237 (target_user_group.users_group_id, user_group.users_group_id))
1235 (target_user_group.users_group_id, user_group.users_group_id))
1238 return permission
1236 return permission
1239
1237
1240 def revoke_user_permission(self, user_name, permission_name):
1238 def revoke_user_permission(self, user_name, permission_name):
1241 self._inherit_default_user_permissions(user_name, True)
1239 self._inherit_default_user_permissions(user_name, True)
1242 UserModel().revoke_perm(user_name, permission_name)
1240 UserModel().revoke_perm(user_name, permission_name)
1243
1241
1244 def _inherit_default_user_permissions(self, user_name, value):
1242 def _inherit_default_user_permissions(self, user_name, value):
1245 user = UserModel().get_by_username(user_name)
1243 user = UserModel().get_by_username(user_name)
1246 user.inherit_default_permissions = value
1244 user.inherit_default_permissions = value
1247 Session().add(user)
1245 Session().add(user)
1248 Session().commit()
1246 Session().commit()
1249
1247
1250 def cleanup(self):
1248 def cleanup(self):
1251 self._cleanup_permissions()
1249 self._cleanup_permissions()
1252 self._cleanup_repos()
1250 self._cleanup_repos()
1253 self._cleanup_repo_groups()
1251 self._cleanup_repo_groups()
1254 self._cleanup_user_groups()
1252 self._cleanup_user_groups()
1255 self._cleanup_users()
1253 self._cleanup_users()
1256
1254
1257 def _cleanup_permissions(self):
1255 def _cleanup_permissions(self):
1258 if self.user_permissions:
1256 if self.user_permissions:
1259 for user_name, permission_name in self.user_permissions:
1257 for user_name, permission_name in self.user_permissions:
1260 self.revoke_user_permission(user_name, permission_name)
1258 self.revoke_user_permission(user_name, permission_name)
1261
1259
1262 for permission in self.user_repo_permission_ids:
1260 for permission in self.user_repo_permission_ids:
1263 RepoModel().revoke_user_permission(*permission)
1261 RepoModel().revoke_user_permission(*permission)
1264
1262
1265 for permission in self.user_group_repo_permission_ids:
1263 for permission in self.user_group_repo_permission_ids:
1266 RepoModel().revoke_user_group_permission(*permission)
1264 RepoModel().revoke_user_group_permission(*permission)
1267
1265
1268 for permission in self.user_repo_group_permission_ids:
1266 for permission in self.user_repo_group_permission_ids:
1269 RepoGroupModel().revoke_user_permission(*permission)
1267 RepoGroupModel().revoke_user_permission(*permission)
1270
1268
1271 for permission in self.user_group_repo_group_permission_ids:
1269 for permission in self.user_group_repo_group_permission_ids:
1272 RepoGroupModel().revoke_user_group_permission(*permission)
1270 RepoGroupModel().revoke_user_group_permission(*permission)
1273
1271
1274 for permission in self.user_user_group_permission_ids:
1272 for permission in self.user_user_group_permission_ids:
1275 UserGroupModel().revoke_user_permission(*permission)
1273 UserGroupModel().revoke_user_permission(*permission)
1276
1274
1277 for permission in self.user_group_user_group_permission_ids:
1275 for permission in self.user_group_user_group_permission_ids:
1278 UserGroupModel().revoke_user_group_permission(*permission)
1276 UserGroupModel().revoke_user_group_permission(*permission)
1279
1277
1280 def _cleanup_repo_groups(self):
1278 def _cleanup_repo_groups(self):
1281 def _repo_group_compare(first_group_id, second_group_id):
1279 def _repo_group_compare(first_group_id, second_group_id):
1282 """
1280 """
1283 Gives higher priority to the groups with the most complex paths
1281 Gives higher priority to the groups with the most complex paths
1284 """
1282 """
1285 first_group = RepoGroup.get(first_group_id)
1283 first_group = RepoGroup.get(first_group_id)
1286 second_group = RepoGroup.get(second_group_id)
1284 second_group = RepoGroup.get(second_group_id)
1287 first_group_parts = (
1285 first_group_parts = (
1288 len(first_group.group_name.split('/')) if first_group else 0)
1286 len(first_group.group_name.split('/')) if first_group else 0)
1289 second_group_parts = (
1287 second_group_parts = (
1290 len(second_group.group_name.split('/')) if second_group else 0)
1288 len(second_group.group_name.split('/')) if second_group else 0)
1291 return cmp(second_group_parts, first_group_parts)
1289 return cmp(second_group_parts, first_group_parts)
1292
1290
1293 sorted_repo_group_ids = sorted(
1291 sorted_repo_group_ids = sorted(
1294 self.repo_group_ids, cmp=_repo_group_compare)
1292 self.repo_group_ids, cmp=_repo_group_compare)
1295 for repo_group_id in sorted_repo_group_ids:
1293 for repo_group_id in sorted_repo_group_ids:
1296 self.fixture.destroy_repo_group(repo_group_id)
1294 self.fixture.destroy_repo_group(repo_group_id)
1297
1295
1298 def _cleanup_repos(self):
1296 def _cleanup_repos(self):
1299 sorted_repos_ids = sorted(self.repos_ids)
1297 sorted_repos_ids = sorted(self.repos_ids)
1300 for repo_id in sorted_repos_ids:
1298 for repo_id in sorted_repos_ids:
1301 self.fixture.destroy_repo(repo_id)
1299 self.fixture.destroy_repo(repo_id)
1302
1300
1303 def _cleanup_user_groups(self):
1301 def _cleanup_user_groups(self):
1304 def _user_group_compare(first_group_id, second_group_id):
1302 def _user_group_compare(first_group_id, second_group_id):
1305 """
1303 """
1306 Gives higher priority to the groups with the most complex paths
1304 Gives higher priority to the groups with the most complex paths
1307 """
1305 """
1308 first_group = UserGroup.get(first_group_id)
1306 first_group = UserGroup.get(first_group_id)
1309 second_group = UserGroup.get(second_group_id)
1307 second_group = UserGroup.get(second_group_id)
1310 first_group_parts = (
1308 first_group_parts = (
1311 len(first_group.users_group_name.split('/'))
1309 len(first_group.users_group_name.split('/'))
1312 if first_group else 0)
1310 if first_group else 0)
1313 second_group_parts = (
1311 second_group_parts = (
1314 len(second_group.users_group_name.split('/'))
1312 len(second_group.users_group_name.split('/'))
1315 if second_group else 0)
1313 if second_group else 0)
1316 return cmp(second_group_parts, first_group_parts)
1314 return cmp(second_group_parts, first_group_parts)
1317
1315
1318 sorted_user_group_ids = sorted(
1316 sorted_user_group_ids = sorted(
1319 self.user_group_ids, cmp=_user_group_compare)
1317 self.user_group_ids, cmp=_user_group_compare)
1320 for user_group_id in sorted_user_group_ids:
1318 for user_group_id in sorted_user_group_ids:
1321 self.fixture.destroy_user_group(user_group_id)
1319 self.fixture.destroy_user_group(user_group_id)
1322
1320
1323 def _cleanup_users(self):
1321 def _cleanup_users(self):
1324 for user_id in self.user_ids:
1322 for user_id in self.user_ids:
1325 self.fixture.destroy_user(user_id)
1323 self.fixture.destroy_user(user_id)
1326
1324
1327
1325
1328 # TODO: Think about moving this into a pytest-pyro package and make it a
1326 # TODO: Think about moving this into a pytest-pyro package and make it a
1329 # pytest plugin
1327 # pytest plugin
1330 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1328 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1331 def pytest_runtest_makereport(item, call):
1329 def pytest_runtest_makereport(item, call):
1332 """
1330 """
1333 Adding the remote traceback if the exception has this information.
1331 Adding the remote traceback if the exception has this information.
1334
1332
1335 VCSServer attaches this information as the attribute `_vcs_server_traceback`
1333 VCSServer attaches this information as the attribute `_vcs_server_traceback`
1336 to the exception instance.
1334 to the exception instance.
1337 """
1335 """
1338 outcome = yield
1336 outcome = yield
1339 report = outcome.get_result()
1337 report = outcome.get_result()
1340 if call.excinfo:
1338 if call.excinfo:
1341 _add_vcsserver_remote_traceback(report, call.excinfo.value)
1339 _add_vcsserver_remote_traceback(report, call.excinfo.value)
1342
1340
1343
1341
1344 def _add_vcsserver_remote_traceback(report, exc):
1342 def _add_vcsserver_remote_traceback(report, exc):
1345 vcsserver_traceback = getattr(exc, '_vcs_server_traceback', None)
1343 vcsserver_traceback = getattr(exc, '_vcs_server_traceback', None)
1346
1344
1347 if vcsserver_traceback:
1345 if vcsserver_traceback:
1348 section = 'VCSServer remote traceback ' + report.when
1346 section = 'VCSServer remote traceback ' + report.when
1349 report.sections.append((section, vcsserver_traceback))
1347 report.sections.append((section, vcsserver_traceback))
1350
1348
1351
1349
1352 @pytest.fixture(scope='session')
1350 @pytest.fixture(scope='session')
1353 def testrun():
1351 def testrun():
1354 return {
1352 return {
1355 'uuid': uuid.uuid4(),
1353 'uuid': uuid.uuid4(),
1356 'start': datetime.datetime.utcnow().isoformat(),
1354 'start': datetime.datetime.utcnow().isoformat(),
1357 'timestamp': int(time.time()),
1355 'timestamp': int(time.time()),
1358 }
1356 }
1359
1357
1360
1358
1361 @pytest.fixture(autouse=True)
1359 @pytest.fixture(autouse=True)
1362 def collect_appenlight_stats(request, testrun):
1360 def collect_appenlight_stats(request, testrun):
1363 """
1361 """
1364 This fixture reports memory consumtion of single tests.
1362 This fixture reports memory consumtion of single tests.
1365
1363
1366 It gathers data based on `psutil` and sends them to Appenlight. The option
1364 It gathers data based on `psutil` and sends them to Appenlight. The option
1367 ``--ae`` has te be used to enable this fixture and the API key for your
1365 ``--ae`` has te be used to enable this fixture and the API key for your
1368 application has to be provided in ``--ae-key``.
1366 application has to be provided in ``--ae-key``.
1369 """
1367 """
1370 try:
1368 try:
1371 # cygwin cannot have yet psutil support.
1369 # cygwin cannot have yet psutil support.
1372 import psutil
1370 import psutil
1373 except ImportError:
1371 except ImportError:
1374 return
1372 return
1375
1373
1376 if not request.config.getoption('--appenlight'):
1374 if not request.config.getoption('--appenlight'):
1377 return
1375 return
1378 else:
1376 else:
1379 # Only request the pylonsapp fixture if appenlight tracking is
1377 # Only request the pylonsapp fixture if appenlight tracking is
1380 # enabled. This will speed up a test run of unit tests by 2 to 3
1378 # enabled. This will speed up a test run of unit tests by 2 to 3
1381 # seconds if appenlight is not enabled.
1379 # seconds if appenlight is not enabled.
1382 pylonsapp = request.getfuncargvalue("pylonsapp")
1380 pylonsapp = request.getfuncargvalue("pylonsapp")
1383 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1381 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1384 client = AppenlightClient(
1382 client = AppenlightClient(
1385 url=url,
1383 url=url,
1386 api_key=request.config.getoption('--appenlight-api-key'),
1384 api_key=request.config.getoption('--appenlight-api-key'),
1387 namespace=request.node.nodeid,
1385 namespace=request.node.nodeid,
1388 request=str(testrun['uuid']),
1386 request=str(testrun['uuid']),
1389 testrun=testrun)
1387 testrun=testrun)
1390
1388
1391 client.collect({
1389 client.collect({
1392 'message': "Starting",
1390 'message': "Starting",
1393 })
1391 })
1394
1392
1395 server_and_port = pylonsapp.config['vcs.server']
1393 server_and_port = pylonsapp.config['vcs.server']
1396 protocol = pylonsapp.config['vcs.server.protocol']
1394 protocol = pylonsapp.config['vcs.server.protocol']
1397 server = create_vcsserver_proxy(server_and_port, protocol)
1395 server = create_vcsserver_proxy(server_and_port, protocol)
1398 with server:
1396 with server:
1399 vcs_pid = server.get_pid()
1397 vcs_pid = server.get_pid()
1400 server.run_gc()
1398 server.run_gc()
1401 vcs_process = psutil.Process(vcs_pid)
1399 vcs_process = psutil.Process(vcs_pid)
1402 mem = vcs_process.memory_info()
1400 mem = vcs_process.memory_info()
1403 client.tag_before('vcsserver.rss', mem.rss)
1401 client.tag_before('vcsserver.rss', mem.rss)
1404 client.tag_before('vcsserver.vms', mem.vms)
1402 client.tag_before('vcsserver.vms', mem.vms)
1405
1403
1406 test_process = psutil.Process()
1404 test_process = psutil.Process()
1407 mem = test_process.memory_info()
1405 mem = test_process.memory_info()
1408 client.tag_before('test.rss', mem.rss)
1406 client.tag_before('test.rss', mem.rss)
1409 client.tag_before('test.vms', mem.vms)
1407 client.tag_before('test.vms', mem.vms)
1410
1408
1411 client.tag_before('time', time.time())
1409 client.tag_before('time', time.time())
1412
1410
1413 @request.addfinalizer
1411 @request.addfinalizer
1414 def send_stats():
1412 def send_stats():
1415 client.tag_after('time', time.time())
1413 client.tag_after('time', time.time())
1416 with server:
1414 with server:
1417 gc_stats = server.run_gc()
1415 gc_stats = server.run_gc()
1418 for tag, value in gc_stats.items():
1416 for tag, value in gc_stats.items():
1419 client.tag_after(tag, value)
1417 client.tag_after(tag, value)
1420 mem = vcs_process.memory_info()
1418 mem = vcs_process.memory_info()
1421 client.tag_after('vcsserver.rss', mem.rss)
1419 client.tag_after('vcsserver.rss', mem.rss)
1422 client.tag_after('vcsserver.vms', mem.vms)
1420 client.tag_after('vcsserver.vms', mem.vms)
1423
1421
1424 mem = test_process.memory_info()
1422 mem = test_process.memory_info()
1425 client.tag_after('test.rss', mem.rss)
1423 client.tag_after('test.rss', mem.rss)
1426 client.tag_after('test.vms', mem.vms)
1424 client.tag_after('test.vms', mem.vms)
1427
1425
1428 client.collect({
1426 client.collect({
1429 'message': "Finished",
1427 'message': "Finished",
1430 })
1428 })
1431 client.send_stats()
1429 client.send_stats()
1432
1430
1433 return client
1431 return client
1434
1432
1435
1433
1436 class AppenlightClient():
1434 class AppenlightClient():
1437
1435
1438 url_template = '{url}?protocol_version=0.5'
1436 url_template = '{url}?protocol_version=0.5'
1439
1437
1440 def __init__(
1438 def __init__(
1441 self, url, api_key, add_server=True, add_timestamp=True,
1439 self, url, api_key, add_server=True, add_timestamp=True,
1442 namespace=None, request=None, testrun=None):
1440 namespace=None, request=None, testrun=None):
1443 self.url = self.url_template.format(url=url)
1441 self.url = self.url_template.format(url=url)
1444 self.api_key = api_key
1442 self.api_key = api_key
1445 self.add_server = add_server
1443 self.add_server = add_server
1446 self.add_timestamp = add_timestamp
1444 self.add_timestamp = add_timestamp
1447 self.namespace = namespace
1445 self.namespace = namespace
1448 self.request = request
1446 self.request = request
1449 self.server = socket.getfqdn(socket.gethostname())
1447 self.server = socket.getfqdn(socket.gethostname())
1450 self.tags_before = {}
1448 self.tags_before = {}
1451 self.tags_after = {}
1449 self.tags_after = {}
1452 self.stats = []
1450 self.stats = []
1453 self.testrun = testrun or {}
1451 self.testrun = testrun or {}
1454
1452
1455 def tag_before(self, tag, value):
1453 def tag_before(self, tag, value):
1456 self.tags_before[tag] = value
1454 self.tags_before[tag] = value
1457
1455
1458 def tag_after(self, tag, value):
1456 def tag_after(self, tag, value):
1459 self.tags_after[tag] = value
1457 self.tags_after[tag] = value
1460
1458
1461 def collect(self, data):
1459 def collect(self, data):
1462 if self.add_server:
1460 if self.add_server:
1463 data.setdefault('server', self.server)
1461 data.setdefault('server', self.server)
1464 if self.add_timestamp:
1462 if self.add_timestamp:
1465 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1463 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1466 if self.namespace:
1464 if self.namespace:
1467 data.setdefault('namespace', self.namespace)
1465 data.setdefault('namespace', self.namespace)
1468 if self.request:
1466 if self.request:
1469 data.setdefault('request', self.request)
1467 data.setdefault('request', self.request)
1470 self.stats.append(data)
1468 self.stats.append(data)
1471
1469
1472 def send_stats(self):
1470 def send_stats(self):
1473 tags = [
1471 tags = [
1474 ('testrun', self.request),
1472 ('testrun', self.request),
1475 ('testrun.start', self.testrun['start']),
1473 ('testrun.start', self.testrun['start']),
1476 ('testrun.timestamp', self.testrun['timestamp']),
1474 ('testrun.timestamp', self.testrun['timestamp']),
1477 ('test', self.namespace),
1475 ('test', self.namespace),
1478 ]
1476 ]
1479 for key, value in self.tags_before.items():
1477 for key, value in self.tags_before.items():
1480 tags.append((key + '.before', value))
1478 tags.append((key + '.before', value))
1481 try:
1479 try:
1482 delta = self.tags_after[key] - value
1480 delta = self.tags_after[key] - value
1483 tags.append((key + '.delta', delta))
1481 tags.append((key + '.delta', delta))
1484 except Exception:
1482 except Exception:
1485 pass
1483 pass
1486 for key, value in self.tags_after.items():
1484 for key, value in self.tags_after.items():
1487 tags.append((key + '.after', value))
1485 tags.append((key + '.after', value))
1488 self.collect({
1486 self.collect({
1489 'message': "Collected tags",
1487 'message': "Collected tags",
1490 'tags': tags,
1488 'tags': tags,
1491 })
1489 })
1492
1490
1493 response = requests.post(
1491 response = requests.post(
1494 self.url,
1492 self.url,
1495 headers={
1493 headers={
1496 'X-appenlight-api-key': self.api_key},
1494 'X-appenlight-api-key': self.api_key},
1497 json=self.stats,
1495 json=self.stats,
1498 )
1496 )
1499
1497
1500 if not response.status_code == 200:
1498 if not response.status_code == 200:
1501 pprint.pprint(self.stats)
1499 pprint.pprint(self.stats)
1502 print response.headers
1500 print response.headers
1503 print response.text
1501 print response.text
1504 raise Exception('Sending to appenlight failed')
1502 raise Exception('Sending to appenlight failed')
1505
1503
1506
1504
1507 @pytest.fixture
1505 @pytest.fixture
1508 def gist_util(request, pylonsapp):
1506 def gist_util(request, pylonsapp):
1509 """
1507 """
1510 Provides a wired instance of `GistUtility` with integrated cleanup.
1508 Provides a wired instance of `GistUtility` with integrated cleanup.
1511 """
1509 """
1512 utility = GistUtility()
1510 utility = GistUtility()
1513 request.addfinalizer(utility.cleanup)
1511 request.addfinalizer(utility.cleanup)
1514 return utility
1512 return utility
1515
1513
1516
1514
1517 class GistUtility(object):
1515 class GistUtility(object):
1518 def __init__(self):
1516 def __init__(self):
1519 self.fixture = Fixture()
1517 self.fixture = Fixture()
1520 self.gist_ids = []
1518 self.gist_ids = []
1521
1519
1522 def create_gist(self, **kwargs):
1520 def create_gist(self, **kwargs):
1523 gist = self.fixture.create_gist(**kwargs)
1521 gist = self.fixture.create_gist(**kwargs)
1524 self.gist_ids.append(gist.gist_id)
1522 self.gist_ids.append(gist.gist_id)
1525 return gist
1523 return gist
1526
1524
1527 def cleanup(self):
1525 def cleanup(self):
1528 for id_ in self.gist_ids:
1526 for id_ in self.gist_ids:
1529 self.fixture.destroy_gists(str(id_))
1527 self.fixture.destroy_gists(str(id_))
1530
1528
1531
1529
1532 @pytest.fixture
1530 @pytest.fixture
1533 def enabled_backends(request):
1531 def enabled_backends(request):
1534 backends = request.config.option.backends
1532 backends = request.config.option.backends
1535 return backends[:]
1533 return backends[:]
1536
1534
1537
1535
1538 @pytest.fixture
1536 @pytest.fixture
1539 def settings_util(request):
1537 def settings_util(request):
1540 """
1538 """
1541 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1539 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1542 """
1540 """
1543 utility = SettingsUtility()
1541 utility = SettingsUtility()
1544 request.addfinalizer(utility.cleanup)
1542 request.addfinalizer(utility.cleanup)
1545 return utility
1543 return utility
1546
1544
1547
1545
1548 class SettingsUtility(object):
1546 class SettingsUtility(object):
1549 def __init__(self):
1547 def __init__(self):
1550 self.rhodecode_ui_ids = []
1548 self.rhodecode_ui_ids = []
1551 self.rhodecode_setting_ids = []
1549 self.rhodecode_setting_ids = []
1552 self.repo_rhodecode_ui_ids = []
1550 self.repo_rhodecode_ui_ids = []
1553 self.repo_rhodecode_setting_ids = []
1551 self.repo_rhodecode_setting_ids = []
1554
1552
1555 def create_repo_rhodecode_ui(
1553 def create_repo_rhodecode_ui(
1556 self, repo, section, value, key=None, active=True, cleanup=True):
1554 self, repo, section, value, key=None, active=True, cleanup=True):
1557 key = key or hashlib.sha1(
1555 key = key or hashlib.sha1(
1558 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1556 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1559
1557
1560 setting = RepoRhodeCodeUi()
1558 setting = RepoRhodeCodeUi()
1561 setting.repository_id = repo.repo_id
1559 setting.repository_id = repo.repo_id
1562 setting.ui_section = section
1560 setting.ui_section = section
1563 setting.ui_value = value
1561 setting.ui_value = value
1564 setting.ui_key = key
1562 setting.ui_key = key
1565 setting.ui_active = active
1563 setting.ui_active = active
1566 Session().add(setting)
1564 Session().add(setting)
1567 Session().commit()
1565 Session().commit()
1568
1566
1569 if cleanup:
1567 if cleanup:
1570 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1568 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1571 return setting
1569 return setting
1572
1570
1573 def create_rhodecode_ui(
1571 def create_rhodecode_ui(
1574 self, section, value, key=None, active=True, cleanup=True):
1572 self, section, value, key=None, active=True, cleanup=True):
1575 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1573 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1576
1574
1577 setting = RhodeCodeUi()
1575 setting = RhodeCodeUi()
1578 setting.ui_section = section
1576 setting.ui_section = section
1579 setting.ui_value = value
1577 setting.ui_value = value
1580 setting.ui_key = key
1578 setting.ui_key = key
1581 setting.ui_active = active
1579 setting.ui_active = active
1582 Session().add(setting)
1580 Session().add(setting)
1583 Session().commit()
1581 Session().commit()
1584
1582
1585 if cleanup:
1583 if cleanup:
1586 self.rhodecode_ui_ids.append(setting.ui_id)
1584 self.rhodecode_ui_ids.append(setting.ui_id)
1587 return setting
1585 return setting
1588
1586
1589 def create_repo_rhodecode_setting(
1587 def create_repo_rhodecode_setting(
1590 self, repo, name, value, type_, cleanup=True):
1588 self, repo, name, value, type_, cleanup=True):
1591 setting = RepoRhodeCodeSetting(
1589 setting = RepoRhodeCodeSetting(
1592 repo.repo_id, key=name, val=value, type=type_)
1590 repo.repo_id, key=name, val=value, type=type_)
1593 Session().add(setting)
1591 Session().add(setting)
1594 Session().commit()
1592 Session().commit()
1595
1593
1596 if cleanup:
1594 if cleanup:
1597 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1595 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1598 return setting
1596 return setting
1599
1597
1600 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1598 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1601 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1599 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1602 Session().add(setting)
1600 Session().add(setting)
1603 Session().commit()
1601 Session().commit()
1604
1602
1605 if cleanup:
1603 if cleanup:
1606 self.rhodecode_setting_ids.append(setting.app_settings_id)
1604 self.rhodecode_setting_ids.append(setting.app_settings_id)
1607
1605
1608 return setting
1606 return setting
1609
1607
1610 def cleanup(self):
1608 def cleanup(self):
1611 for id_ in self.rhodecode_ui_ids:
1609 for id_ in self.rhodecode_ui_ids:
1612 setting = RhodeCodeUi.get(id_)
1610 setting = RhodeCodeUi.get(id_)
1613 Session().delete(setting)
1611 Session().delete(setting)
1614
1612
1615 for id_ in self.rhodecode_setting_ids:
1613 for id_ in self.rhodecode_setting_ids:
1616 setting = RhodeCodeSetting.get(id_)
1614 setting = RhodeCodeSetting.get(id_)
1617 Session().delete(setting)
1615 Session().delete(setting)
1618
1616
1619 for id_ in self.repo_rhodecode_ui_ids:
1617 for id_ in self.repo_rhodecode_ui_ids:
1620 setting = RepoRhodeCodeUi.get(id_)
1618 setting = RepoRhodeCodeUi.get(id_)
1621 Session().delete(setting)
1619 Session().delete(setting)
1622
1620
1623 for id_ in self.repo_rhodecode_setting_ids:
1621 for id_ in self.repo_rhodecode_setting_ids:
1624 setting = RepoRhodeCodeSetting.get(id_)
1622 setting = RepoRhodeCodeSetting.get(id_)
1625 Session().delete(setting)
1623 Session().delete(setting)
1626
1624
1627 Session().commit()
1625 Session().commit()
1628
1626
1629
1627
1630 @pytest.fixture
1628 @pytest.fixture
1631 def no_notifications(request):
1629 def no_notifications(request):
1632 notification_patcher = mock.patch(
1630 notification_patcher = mock.patch(
1633 'rhodecode.model.notification.NotificationModel.create')
1631 'rhodecode.model.notification.NotificationModel.create')
1634 notification_patcher.start()
1632 notification_patcher.start()
1635 request.addfinalizer(notification_patcher.stop)
1633 request.addfinalizer(notification_patcher.stop)
1636
1634
1637
1635
1638 @pytest.fixture
1636 @pytest.fixture
1639 def silence_action_logger(request):
1637 def silence_action_logger(request):
1640 notification_patcher = mock.patch(
1638 notification_patcher = mock.patch(
1641 'rhodecode.lib.utils.action_logger')
1639 'rhodecode.lib.utils.action_logger')
1642 notification_patcher.start()
1640 notification_patcher.start()
1643 request.addfinalizer(notification_patcher.stop)
1641 request.addfinalizer(notification_patcher.stop)
1644
1642
1645
1643
1646 @pytest.fixture(scope='session')
1644 @pytest.fixture(scope='session')
1647 def repeat(request):
1645 def repeat(request):
1648 """
1646 """
1649 The number of repetitions is based on this fixture.
1647 The number of repetitions is based on this fixture.
1650
1648
1651 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1649 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1652 tests are not too slow in our default test suite.
1650 tests are not too slow in our default test suite.
1653 """
1651 """
1654 return request.config.getoption('--repeat')
1652 return request.config.getoption('--repeat')
1655
1653
1656
1654
1657 @pytest.fixture
1655 @pytest.fixture
1658 def rhodecode_fixtures():
1656 def rhodecode_fixtures():
1659 return Fixture()
1657 return Fixture()
1660
1658
1661
1659
1662 @pytest.fixture
1660 @pytest.fixture
1663 def request_stub():
1661 def request_stub():
1664 """
1662 """
1665 Stub request object.
1663 Stub request object.
1666 """
1664 """
1667 request = pyramid.testing.DummyRequest()
1665 request = pyramid.testing.DummyRequest()
1668 request.scheme = 'https'
1666 request.scheme = 'https'
1669 return request
1667 return request
1670
1668
1671
1669
1672 @pytest.fixture
1670 @pytest.fixture
1673 def config_stub(request, request_stub):
1671 def config_stub(request, request_stub):
1674 """
1672 """
1675 Set up pyramid.testing and return the Configurator.
1673 Set up pyramid.testing and return the Configurator.
1676 """
1674 """
1677 config = pyramid.testing.setUp(request=request_stub)
1675 config = pyramid.testing.setUp(request=request_stub)
1678
1676
1679 @request.addfinalizer
1677 @request.addfinalizer
1680 def cleanup():
1678 def cleanup():
1681 pyramid.testing.tearDown()
1679 pyramid.testing.tearDown()
1682
1680
1683 return config
1681 return config
1684
1682
1685
1683
1686 @pytest.fixture
1684 @pytest.fixture
1687 def StubIntegrationType():
1685 def StubIntegrationType():
1688 class _StubIntegrationType(IntegrationTypeBase):
1686 class _StubIntegrationType(IntegrationTypeBase):
1689 """ Test integration type class """
1687 """ Test integration type class """
1690
1688
1691 key = 'test'
1689 key = 'test'
1692 display_name = 'Test integration type'
1690 display_name = 'Test integration type'
1693 description = 'A test integration type for testing'
1691 description = 'A test integration type for testing'
1694 icon = 'test_icon_html_image'
1692 icon = 'test_icon_html_image'
1695
1693
1696 def __init__(self, settings):
1694 def __init__(self, settings):
1697 super(_StubIntegrationType, self).__init__(settings)
1695 super(_StubIntegrationType, self).__init__(settings)
1698 self.sent_events = [] # for testing
1696 self.sent_events = [] # for testing
1699
1697
1700 def send_event(self, event):
1698 def send_event(self, event):
1701 self.sent_events.append(event)
1699 self.sent_events.append(event)
1702
1700
1703 def settings_schema(self):
1701 def settings_schema(self):
1704 class SettingsSchema(colander.Schema):
1702 class SettingsSchema(colander.Schema):
1705 test_string_field = colander.SchemaNode(
1703 test_string_field = colander.SchemaNode(
1706 colander.String(),
1704 colander.String(),
1707 missing=colander.required,
1705 missing=colander.required,
1708 title='test string field',
1706 title='test string field',
1709 )
1707 )
1710 test_int_field = colander.SchemaNode(
1708 test_int_field = colander.SchemaNode(
1711 colander.Int(),
1709 colander.Int(),
1712 title='some integer setting',
1710 title='some integer setting',
1713 )
1711 )
1714 return SettingsSchema()
1712 return SettingsSchema()
1715
1713
1716
1714
1717 integration_type_registry.register_integration_type(_StubIntegrationType)
1715 integration_type_registry.register_integration_type(_StubIntegrationType)
1718 return _StubIntegrationType
1716 return _StubIntegrationType
1719
1717
1720 @pytest.fixture
1718 @pytest.fixture
1721 def stub_integration_settings():
1719 def stub_integration_settings():
1722 return {
1720 return {
1723 'test_string_field': 'some data',
1721 'test_string_field': 'some data',
1724 'test_int_field': 100,
1722 'test_int_field': 100,
1725 }
1723 }
1726
1724
1727
1725
1728 @pytest.fixture
1726 @pytest.fixture
1729 def repo_integration_stub(request, repo_stub, StubIntegrationType,
1727 def repo_integration_stub(request, repo_stub, StubIntegrationType,
1730 stub_integration_settings):
1728 stub_integration_settings):
1731 integration = IntegrationModel().create(
1729 integration = IntegrationModel().create(
1732 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1730 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1733 name='test repo integration',
1731 name='test repo integration',
1734 repo=repo_stub, repo_group=None, child_repos_only=None)
1732 repo=repo_stub, repo_group=None, child_repos_only=None)
1735
1733
1736 @request.addfinalizer
1734 @request.addfinalizer
1737 def cleanup():
1735 def cleanup():
1738 IntegrationModel().delete(integration)
1736 IntegrationModel().delete(integration)
1739
1737
1740 return integration
1738 return integration
1741
1739
1742
1740
1743 @pytest.fixture
1741 @pytest.fixture
1744 def repogroup_integration_stub(request, test_repo_group, StubIntegrationType,
1742 def repogroup_integration_stub(request, test_repo_group, StubIntegrationType,
1745 stub_integration_settings):
1743 stub_integration_settings):
1746 integration = IntegrationModel().create(
1744 integration = IntegrationModel().create(
1747 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1745 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1748 name='test repogroup integration',
1746 name='test repogroup integration',
1749 repo=None, repo_group=test_repo_group, child_repos_only=True)
1747 repo=None, repo_group=test_repo_group, child_repos_only=True)
1750
1748
1751 @request.addfinalizer
1749 @request.addfinalizer
1752 def cleanup():
1750 def cleanup():
1753 IntegrationModel().delete(integration)
1751 IntegrationModel().delete(integration)
1754
1752
1755 return integration
1753 return integration
1756
1754
1757
1755
1758 @pytest.fixture
1756 @pytest.fixture
1759 def repogroup_recursive_integration_stub(request, test_repo_group,
1757 def repogroup_recursive_integration_stub(request, test_repo_group,
1760 StubIntegrationType, stub_integration_settings):
1758 StubIntegrationType, stub_integration_settings):
1761 integration = IntegrationModel().create(
1759 integration = IntegrationModel().create(
1762 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1760 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1763 name='test recursive repogroup integration',
1761 name='test recursive repogroup integration',
1764 repo=None, repo_group=test_repo_group, child_repos_only=False)
1762 repo=None, repo_group=test_repo_group, child_repos_only=False)
1765
1763
1766 @request.addfinalizer
1764 @request.addfinalizer
1767 def cleanup():
1765 def cleanup():
1768 IntegrationModel().delete(integration)
1766 IntegrationModel().delete(integration)
1769
1767
1770 return integration
1768 return integration
1771
1769
1772
1770
1773 @pytest.fixture
1771 @pytest.fixture
1774 def global_integration_stub(request, StubIntegrationType,
1772 def global_integration_stub(request, StubIntegrationType,
1775 stub_integration_settings):
1773 stub_integration_settings):
1776 integration = IntegrationModel().create(
1774 integration = IntegrationModel().create(
1777 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1775 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1778 name='test global integration',
1776 name='test global integration',
1779 repo=None, repo_group=None, child_repos_only=None)
1777 repo=None, repo_group=None, child_repos_only=None)
1780
1778
1781 @request.addfinalizer
1779 @request.addfinalizer
1782 def cleanup():
1780 def cleanup():
1783 IntegrationModel().delete(integration)
1781 IntegrationModel().delete(integration)
1784
1782
1785 return integration
1783 return integration
1786
1784
1787
1785
1788 @pytest.fixture
1786 @pytest.fixture
1789 def root_repos_integration_stub(request, StubIntegrationType,
1787 def root_repos_integration_stub(request, StubIntegrationType,
1790 stub_integration_settings):
1788 stub_integration_settings):
1791 integration = IntegrationModel().create(
1789 integration = IntegrationModel().create(
1792 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1790 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1793 name='test global integration',
1791 name='test global integration',
1794 repo=None, repo_group=None, child_repos_only=True)
1792 repo=None, repo_group=None, child_repos_only=True)
1795
1793
1796 @request.addfinalizer
1794 @request.addfinalizer
1797 def cleanup():
1795 def cleanup():
1798 IntegrationModel().delete(integration)
1796 IntegrationModel().delete(integration)
1799
1797
1800 return integration
1798 return integration
1801
1799
1802
1800
1803 @pytest.fixture
1801 @pytest.fixture
1804 def local_dt_to_utc():
1802 def local_dt_to_utc():
1805 def _factory(dt):
1803 def _factory(dt):
1806 return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(
1804 return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(
1807 dateutil.tz.tzutc()).replace(tzinfo=None)
1805 dateutil.tz.tzutc()).replace(tzinfo=None)
1808 return _factory
1806 return _factory
General Comments 0
You need to be logged in to leave comments. Login now