##// END OF EJS Templates
integrations: add recursive repo group scope to allow integrations...
dan -
r793:fc8d2069 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,385 +1,392 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 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 pylons
21 import pylons
22 import deform
22 import deform
23 import logging
23 import logging
24 import colander
24 import colander
25 import peppercorn
25 import peppercorn
26 import webhelpers.paginate
26 import webhelpers.paginate
27
27
28 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
28 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30 from pyramid.response import Response
30 from pyramid.response import Response
31
31
32 from rhodecode.lib import auth
32 from rhodecode.lib import auth
33 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
33 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
34 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib.helpers import Page
35 from rhodecode.lib.helpers import Page
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.integration import IntegrationModel
38 from rhodecode.model.integration import IntegrationModel
39 from rhodecode.admin.navigation import navigation_list
39 from rhodecode.admin.navigation import navigation_list
40 from rhodecode.translation import _
40 from rhodecode.translation import _
41 from rhodecode.integrations import integration_type_registry
41 from rhodecode.integrations import integration_type_registry
42 from rhodecode.model.validation_schema.schemas.integration_schema import (
42 from rhodecode.model.validation_schema.schemas.integration_schema import (
43 make_integration_schema)
43 make_integration_schema, IntegrationScopeType)
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class IntegrationSettingsViewBase(object):
48 class IntegrationSettingsViewBase(object):
49 """ Base Integration settings view used by both repo / global settings """
49 """ Base Integration settings view used by both repo / global settings """
50
50
51 def __init__(self, context, request):
51 def __init__(self, context, request):
52 self.context = context
52 self.context = context
53 self.request = request
53 self.request = request
54 self._load_general_context()
54 self._load_general_context()
55
55
56 if not self.perm_check(request.user):
56 if not self.perm_check(request.user):
57 raise HTTPForbidden()
57 raise HTTPForbidden()
58
58
59 def _load_general_context(self):
59 def _load_general_context(self):
60 """
60 """
61 This avoids boilerplate for repo/global+list/edit+views/templates
61 This avoids boilerplate for repo/global+list/edit+views/templates
62 by doing all possible contexts at the same time however it should
62 by doing all possible contexts at the same time however it should
63 be split up into separate functions once more "contexts" exist
63 be split up into separate functions once more "contexts" exist
64 """
64 """
65
65
66 self.IntegrationType = None
66 self.IntegrationType = None
67 self.repo = None
67 self.repo = None
68 self.repo_group = None
68 self.repo_group = None
69 self.integration = None
69 self.integration = None
70 self.integrations = {}
70 self.integrations = {}
71
71
72 request = self.request
72 request = self.request
73
73
74 if 'repo_name' in request.matchdict: # in repo settings context
74 if 'repo_name' in request.matchdict: # in repo settings context
75 repo_name = request.matchdict['repo_name']
75 repo_name = request.matchdict['repo_name']
76 self.repo = Repository.get_by_repo_name(repo_name)
76 self.repo = Repository.get_by_repo_name(repo_name)
77
77
78 if 'repo_group_name' in request.matchdict: # in group settings context
78 if 'repo_group_name' in request.matchdict: # in group settings context
79 repo_group_name = request.matchdict['repo_group_name']
79 repo_group_name = request.matchdict['repo_group_name']
80 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
80 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
81
81
82
82
83 if 'integration' in request.matchdict: # integration type context
83 if 'integration' in request.matchdict: # integration type context
84 integration_type = request.matchdict['integration']
84 integration_type = request.matchdict['integration']
85 self.IntegrationType = integration_type_registry[integration_type]
85 self.IntegrationType = integration_type_registry[integration_type]
86
86
87 if 'integration_id' in request.matchdict: # single integration context
87 if 'integration_id' in request.matchdict: # single integration context
88 integration_id = request.matchdict['integration_id']
88 integration_id = request.matchdict['integration_id']
89 self.integration = Integration.get(integration_id)
89 self.integration = Integration.get(integration_id)
90
90
91 # extra perms check just in case
91 # extra perms check just in case
92 if not self._has_perms_for_integration(self.integration):
92 if not self._has_perms_for_integration(self.integration):
93 raise HTTPForbidden()
93 raise HTTPForbidden()
94
94
95 self.settings = self.integration and self.integration.settings or {}
95 self.settings = self.integration and self.integration.settings or {}
96 self.admin_view = not (self.repo or self.repo_group)
96 self.admin_view = not (self.repo or self.repo_group)
97
97
98 def _has_perms_for_integration(self, integration):
98 def _has_perms_for_integration(self, integration):
99 perms = self.request.user.permissions
99 perms = self.request.user.permissions
100
100
101 if 'hg.admin' in perms['global']:
101 if 'hg.admin' in perms['global']:
102 return True
102 return True
103
103
104 if integration.repo:
104 if integration.repo:
105 return perms['repositories'].get(
105 return perms['repositories'].get(
106 integration.repo.repo_name) == 'repository.admin'
106 integration.repo.repo_name) == 'repository.admin'
107
107
108 if integration.repo_group:
108 if integration.repo_group:
109 return perms['repositories_groups'].get(
109 return perms['repositories_groups'].get(
110 integration.repo_group.group_name) == 'group.admin'
110 integration.repo_group.group_name) == 'group.admin'
111
111
112 return False
112 return False
113
113
114 def _template_c_context(self):
114 def _template_c_context(self):
115 # TODO: dan: this is a stopgap in order to inherit from current pylons
115 # TODO: dan: this is a stopgap in order to inherit from current pylons
116 # based admin/repo settings templates - this should be removed entirely
116 # based admin/repo settings templates - this should be removed entirely
117 # after port to pyramid
117 # after port to pyramid
118
118
119 c = pylons.tmpl_context
119 c = pylons.tmpl_context
120 c.active = 'integrations'
120 c.active = 'integrations'
121 c.rhodecode_user = self.request.user
121 c.rhodecode_user = self.request.user
122 c.repo = self.repo
122 c.repo = self.repo
123 c.repo_group = self.repo_group
123 c.repo_group = self.repo_group
124 c.repo_name = self.repo and self.repo.repo_name or None
124 c.repo_name = self.repo and self.repo.repo_name or None
125 c.repo_group_name = self.repo_group and self.repo_group.group_name or None
125 c.repo_group_name = self.repo_group and self.repo_group.group_name or None
126
126
127 if self.repo:
127 if self.repo:
128 c.repo_info = self.repo
128 c.repo_info = self.repo
129 c.rhodecode_db_repo = self.repo
129 c.rhodecode_db_repo = self.repo
130 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
130 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
131 else:
131 else:
132 c.navlist = navigation_list(self.request)
132 c.navlist = navigation_list(self.request)
133
133
134 return c
134 return c
135
135
136 def _form_schema(self):
136 def _form_schema(self):
137 schema = make_integration_schema(IntegrationType=self.IntegrationType,
137 schema = make_integration_schema(IntegrationType=self.IntegrationType,
138 settings=self.settings)
138 settings=self.settings)
139
139
140 # returns a clone, important if mutating the schema later
140 # returns a clone, important if mutating the schema later
141 return schema.bind(
141 return schema.bind(
142 permissions=self.request.user.permissions,
142 permissions=self.request.user.permissions,
143 no_scope=not self.admin_view)
143 no_scope=not self.admin_view)
144
144
145
145
146 def _form_defaults(self):
146 def _form_defaults(self):
147 defaults = {}
147 defaults = {}
148
148
149 if self.integration:
149 if self.integration:
150 defaults['settings'] = self.integration.settings or {}
150 defaults['settings'] = self.integration.settings or {}
151 defaults['options'] = {
151 defaults['options'] = {
152 'name': self.integration.name,
152 'name': self.integration.name,
153 'enabled': self.integration.enabled,
153 'enabled': self.integration.enabled,
154 'scope': self.integration.scope,
154 'scope': {
155 'repo': self.integration.repo,
156 'repo_group': self.integration.repo_group,
157 'child_repos_only': self.integration.child_repos_only,
158 },
155 }
159 }
156 else:
160 else:
157 if self.repo:
161 if self.repo:
158 scope = _('{repo_name} repository').format(
162 scope = _('{repo_name} repository').format(
159 repo_name=self.repo.repo_name)
163 repo_name=self.repo.repo_name)
160 elif self.repo_group:
164 elif self.repo_group:
161 scope = _('{repo_group_name} repo group').format(
165 scope = _('{repo_group_name} repo group').format(
162 repo_group_name=self.repo_group.group_name)
166 repo_group_name=self.repo_group.group_name)
163 else:
167 else:
164 scope = _('Global')
168 scope = _('Global')
165
169
166 defaults['options'] = {
170 defaults['options'] = {
167 'enabled': True,
171 'enabled': True,
168 'name': _('{name} integration').format(
172 'name': _('{name} integration').format(
169 name=self.IntegrationType.display_name),
173 name=self.IntegrationType.display_name),
170 }
174 }
171 if self.repo:
175 defaults['options']['scope'] = {
172 defaults['options']['scope'] = self.repo
176 'repo': self.repo,
173 elif self.repo_group:
177 'repo_group': self.repo_group,
174 defaults['options']['scope'] = self.repo_group
178 }
175
179
176 return defaults
180 return defaults
177
181
178 def _delete_integration(self, integration):
182 def _delete_integration(self, integration):
179 Session().delete(self.integration)
183 Session().delete(self.integration)
180 Session().commit()
184 Session().commit()
181 self.request.session.flash(
185 self.request.session.flash(
182 _('Integration {integration_name} deleted successfully.').format(
186 _('Integration {integration_name} deleted successfully.').format(
183 integration_name=self.integration.name),
187 integration_name=self.integration.name),
184 queue='success')
188 queue='success')
185
189
186 if self.repo:
190 if self.repo:
187 redirect_to = self.request.route_url(
191 redirect_to = self.request.route_url(
188 'repo_integrations_home', repo_name=self.repo.repo_name)
192 'repo_integrations_home', repo_name=self.repo.repo_name)
189 elif self.repo_group:
193 elif self.repo_group:
190 redirect_to = self.request.route_url(
194 redirect_to = self.request.route_url(
191 'repo_group_integrations_home',
195 'repo_group_integrations_home',
192 repo_group_name=self.repo_group.group_name)
196 repo_group_name=self.repo_group.group_name)
193 else:
197 else:
194 redirect_to = self.request.route_url('global_integrations_home')
198 redirect_to = self.request.route_url('global_integrations_home')
195 raise HTTPFound(redirect_to)
199 raise HTTPFound(redirect_to)
196
200
197 def settings_get(self, defaults=None, form=None):
201 def settings_get(self, defaults=None, form=None):
198 """
202 """
199 View that displays the integration settings as a form.
203 View that displays the integration settings as a form.
200 """
204 """
201
205
202 defaults = defaults or self._form_defaults()
206 defaults = defaults or self._form_defaults()
203 schema = self._form_schema()
207 schema = self._form_schema()
204
208
205 if self.integration:
209 if self.integration:
206 buttons = ('submit', 'delete')
210 buttons = ('submit', 'delete')
207 else:
211 else:
208 buttons = ('submit',)
212 buttons = ('submit',)
209
213
210 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
214 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
211
215
212 template_context = {
216 template_context = {
213 'form': form,
217 'form': form,
214 'current_IntegrationType': self.IntegrationType,
218 'current_IntegrationType': self.IntegrationType,
215 'integration': self.integration,
219 'integration': self.integration,
216 'c': self._template_c_context(),
220 'c': self._template_c_context(),
217 }
221 }
218
222
219 return template_context
223 return template_context
220
224
221 @auth.CSRFRequired()
225 @auth.CSRFRequired()
222 def settings_post(self):
226 def settings_post(self):
223 """
227 """
224 View that validates and stores the integration settings.
228 View that validates and stores the integration settings.
225 """
229 """
226 controls = self.request.POST.items()
230 controls = self.request.POST.items()
227 pstruct = peppercorn.parse(controls)
231 pstruct = peppercorn.parse(controls)
228
232
229 if self.integration and pstruct.get('delete'):
233 if self.integration and pstruct.get('delete'):
230 return self._delete_integration(self.integration)
234 return self._delete_integration(self.integration)
231
235
232 schema = self._form_schema()
236 schema = self._form_schema()
233
237
234 skip_settings_validation = False
238 skip_settings_validation = False
235 if self.integration and 'enabled' not in pstruct.get('options', {}):
239 if self.integration and 'enabled' not in pstruct.get('options', {}):
236 skip_settings_validation = True
240 skip_settings_validation = True
237 schema['settings'].validator = None
241 schema['settings'].validator = None
238 for field in schema['settings'].children:
242 for field in schema['settings'].children:
239 field.validator = None
243 field.validator = None
240 field.missing = ''
244 field.missing = ''
241
245
242 if self.integration:
246 if self.integration:
243 buttons = ('submit', 'delete')
247 buttons = ('submit', 'delete')
244 else:
248 else:
245 buttons = ('submit',)
249 buttons = ('submit',)
246
250
247 form = deform.Form(schema, buttons=buttons)
251 form = deform.Form(schema, buttons=buttons)
248
252
249 if not self.admin_view:
253 if not self.admin_view:
250 # scope is read only field in these cases, and has to be added
254 # scope is read only field in these cases, and has to be added
251 options = pstruct.setdefault('options', {})
255 options = pstruct.setdefault('options', {})
252 if 'scope' not in options:
256 if 'scope' not in options:
253 if self.repo:
257 options['scope'] = IntegrationScopeType().serialize(None, {
254 options['scope'] = 'repo:{}'.format(self.repo.repo_name)
258 'repo': self.repo,
255 elif self.repo_group:
259 'repo_group': self.repo_group,
256 options['scope'] = 'repogroup:{}'.format(
260 })
257 self.repo_group.group_name)
258
261
259 try:
262 try:
260 valid_data = form.validate_pstruct(pstruct)
263 valid_data = form.validate_pstruct(pstruct)
261 except deform.ValidationFailure as e:
264 except deform.ValidationFailure as e:
262 self.request.session.flash(
265 self.request.session.flash(
263 _('Errors exist when saving integration settings. '
266 _('Errors exist when saving integration settings. '
264 'Please check the form inputs.'),
267 'Please check the form inputs.'),
265 queue='error')
268 queue='error')
266 return self.settings_get(form=e)
269 return self.settings_get(form=e)
267
270
268 if not self.integration:
271 if not self.integration:
269 self.integration = Integration()
272 self.integration = Integration()
270 self.integration.integration_type = self.IntegrationType.key
273 self.integration.integration_type = self.IntegrationType.key
271 Session().add(self.integration)
274 Session().add(self.integration)
272
275
273 scope = valid_data['options']['scope']
276 scope = valid_data['options']['scope']
274
277
275 IntegrationModel().update_integration(self.integration,
278 IntegrationModel().update_integration(self.integration,
276 name=valid_data['options']['name'],
279 name=valid_data['options']['name'],
277 enabled=valid_data['options']['enabled'],
280 enabled=valid_data['options']['enabled'],
278 settings=valid_data['settings'],
281 settings=valid_data['settings'],
279 scope=scope)
282 repo=scope['repo'],
283 repo_group=scope['repo_group'],
284 child_repos_only=scope['child_repos_only'],
285 )
286
280
287
281 self.integration.settings = valid_data['settings']
288 self.integration.settings = valid_data['settings']
282 Session().commit()
289 Session().commit()
283 # Display success message and redirect.
290 # Display success message and redirect.
284 self.request.session.flash(
291 self.request.session.flash(
285 _('Integration {integration_name} updated successfully.').format(
292 _('Integration {integration_name} updated successfully.').format(
286 integration_name=self.IntegrationType.display_name),
293 integration_name=self.IntegrationType.display_name),
287 queue='success')
294 queue='success')
288
295
289
296
290 # if integration scope changes, we must redirect to the right place
297 # if integration scope changes, we must redirect to the right place
291 # keeping in mind if the original view was for /repo/ or /_admin/
298 # keeping in mind if the original view was for /repo/ or /_admin/
292 admin_view = not (self.repo or self.repo_group)
299 admin_view = not (self.repo or self.repo_group)
293
300
294 if isinstance(self.integration.scope, Repository) and not admin_view:
301 if self.integration.repo and not admin_view:
295 redirect_to = self.request.route_path(
302 redirect_to = self.request.route_path(
296 'repo_integrations_edit',
303 'repo_integrations_edit',
297 repo_name=self.integration.scope.repo_name,
304 repo_name=self.integration.repo.repo_name,
298 integration=self.integration.integration_type,
305 integration=self.integration.integration_type,
299 integration_id=self.integration.integration_id)
306 integration_id=self.integration.integration_id)
300 elif isinstance(self.integration.scope, RepoGroup) and not admin_view:
307 elif self.integration.repo_group and not admin_view:
301 redirect_to = self.request.route_path(
308 redirect_to = self.request.route_path(
302 'repo_group_integrations_edit',
309 'repo_group_integrations_edit',
303 repo_group_name=self.integration.scope.group_name,
310 repo_group_name=self.integration.repo_group.group_name,
304 integration=self.integration.integration_type,
311 integration=self.integration.integration_type,
305 integration_id=self.integration.integration_id)
312 integration_id=self.integration.integration_id)
306 else:
313 else:
307 redirect_to = self.request.route_path(
314 redirect_to = self.request.route_path(
308 'global_integrations_edit',
315 'global_integrations_edit',
309 integration=self.integration.integration_type,
316 integration=self.integration.integration_type,
310 integration_id=self.integration.integration_id)
317 integration_id=self.integration.integration_id)
311
318
312 return HTTPFound(redirect_to)
319 return HTTPFound(redirect_to)
313
320
314 def index(self):
321 def index(self):
315 """ List integrations """
322 """ List integrations """
316 if self.repo:
323 if self.repo:
317 scope = self.repo
324 scope = self.repo
318 elif self.repo_group:
325 elif self.repo_group:
319 scope = self.repo_group
326 scope = self.repo_group
320 else:
327 else:
321 scope = 'all'
328 scope = 'all'
322
329
323 integrations = []
330 integrations = []
324
331
325 for integration in IntegrationModel().get_integrations(
332 for integration in IntegrationModel().get_integrations(
326 scope=scope, IntegrationType=self.IntegrationType):
333 scope=scope, IntegrationType=self.IntegrationType):
327
334
328 # extra permissions check *just in case*
335 # extra permissions check *just in case*
329 if not self._has_perms_for_integration(integration):
336 if not self._has_perms_for_integration(integration):
330 continue
337 continue
331 integrations.append(integration)
338 integrations.append(integration)
332
339
333 sort_arg = self.request.GET.get('sort', 'name:asc')
340 sort_arg = self.request.GET.get('sort', 'name:asc')
334 if ':' in sort_arg:
341 if ':' in sort_arg:
335 sort_field, sort_dir = sort_arg.split(':')
342 sort_field, sort_dir = sort_arg.split(':')
336 else:
343 else:
337 sort_field = sort_arg, 'asc'
344 sort_field = sort_arg, 'asc'
338
345
339 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
346 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
340
347
341 integrations.sort(
348 integrations.sort(
342 key=lambda x: getattr(x[1], sort_field), reverse=(sort_dir=='desc'))
349 key=lambda x: getattr(x[1], sort_field), reverse=(sort_dir=='desc'))
343
350
344
351
345 page_url = webhelpers.paginate.PageURL(
352 page_url = webhelpers.paginate.PageURL(
346 self.request.path, self.request.GET)
353 self.request.path, self.request.GET)
347 page = safe_int(self.request.GET.get('page', 1), 1)
354 page = safe_int(self.request.GET.get('page', 1), 1)
348
355
349 integrations = Page(integrations, page=page, items_per_page=10,
356 integrations = Page(integrations, page=page, items_per_page=10,
350 url=page_url)
357 url=page_url)
351
358
352 template_context = {
359 template_context = {
353 'sort_field': sort_field,
360 'sort_field': sort_field,
354 'rev_sort_dir': sort_dir != 'desc' and 'desc' or 'asc',
361 'rev_sort_dir': sort_dir != 'desc' and 'desc' or 'asc',
355 'current_IntegrationType': self.IntegrationType,
362 'current_IntegrationType': self.IntegrationType,
356 'integrations_list': integrations,
363 'integrations_list': integrations,
357 'available_integrations': integration_type_registry,
364 'available_integrations': integration_type_registry,
358 'c': self._template_c_context(),
365 'c': self._template_c_context(),
359 'request': self.request,
366 'request': self.request,
360 }
367 }
361 return template_context
368 return template_context
362
369
363 def new_integration(self):
370 def new_integration(self):
364 template_context = {
371 template_context = {
365 'available_integrations': integration_type_registry,
372 'available_integrations': integration_type_registry,
366 'c': self._template_c_context(),
373 'c': self._template_c_context(),
367 }
374 }
368 return template_context
375 return template_context
369
376
370 class GlobalIntegrationsView(IntegrationSettingsViewBase):
377 class GlobalIntegrationsView(IntegrationSettingsViewBase):
371 def perm_check(self, user):
378 def perm_check(self, user):
372 return auth.HasPermissionAll('hg.admin').check_permissions(user=user)
379 return auth.HasPermissionAll('hg.admin').check_permissions(user=user)
373
380
374
381
375 class RepoIntegrationsView(IntegrationSettingsViewBase):
382 class RepoIntegrationsView(IntegrationSettingsViewBase):
376 def perm_check(self, user):
383 def perm_check(self, user):
377 return auth.HasRepoPermissionAll('repository.admin'
384 return auth.HasRepoPermissionAll('repository.admin'
378 )(repo_name=self.repo.repo_name, user=user)
385 )(repo_name=self.repo.repo_name, user=user)
379
386
380
387
381 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
388 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
382 def perm_check(self, user):
389 def perm_check(self, user):
383 return auth.HasRepoGroupPermissionAll('group.admin'
390 return auth.HasRepoGroupPermissionAll('group.admin'
384 )(group_name=self.repo_group.group_name, user=user)
391 )(group_name=self.repo_group.group_name, user=user)
385
392
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,213 +1,222 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 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
21
22 """
22 """
23 Model for integrations
23 Model for integrations
24 """
24 """
25
25
26
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from pylons import tmpl_context as c
30 from pylons import tmpl_context as c
31 from pylons.i18n.translation import _, ungettext
31 from pylons.i18n.translation import _, ungettext
32 from sqlalchemy import or_, and_
32 from sqlalchemy import or_, and_
33 from sqlalchemy.sql.expression import false, true
33 from sqlalchemy.sql.expression import false, true
34 from mako import exceptions
34 from mako import exceptions
35
35
36 import rhodecode
36 import rhodecode
37 from rhodecode import events
37 from rhodecode import events
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.caching_query import FromCache
39 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.lib.utils import PartialRenderer
40 from rhodecode.lib.utils import PartialRenderer
41 from rhodecode.model import BaseModel
41 from rhodecode.model import BaseModel
42 from rhodecode.model.db import Integration, User, Repository, RepoGroup
42 from rhodecode.model.db import Integration, User, Repository, RepoGroup
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.integrations import integration_type_registry
44 from rhodecode.integrations import integration_type_registry
45 from rhodecode.integrations.types.base import IntegrationTypeBase
45 from rhodecode.integrations.types.base import IntegrationTypeBase
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class IntegrationModel(BaseModel):
50 class IntegrationModel(BaseModel):
51
51
52 cls = Integration
52 cls = Integration
53
53
54 def __get_integration(self, integration):
54 def __get_integration(self, integration):
55 if isinstance(integration, Integration):
55 if isinstance(integration, Integration):
56 return integration
56 return integration
57 elif isinstance(integration, (int, long)):
57 elif isinstance(integration, (int, long)):
58 return self.sa.query(Integration).get(integration)
58 return self.sa.query(Integration).get(integration)
59 else:
59 else:
60 if integration:
60 if integration:
61 raise Exception('integration must be int, long or Instance'
61 raise Exception('integration must be int, long or Instance'
62 ' of Integration got %s' % type(integration))
62 ' of Integration got %s' % type(integration))
63
63
64 def create(self, IntegrationType, name, enabled, scope, settings):
64 def create(self, IntegrationType, name, enabled, repo, repo_group,
65 child_repos_only, settings):
65 """ Create an IntegrationType integration """
66 """ Create an IntegrationType integration """
66 integration = Integration()
67 integration = Integration()
67 integration.integration_type = IntegrationType.key
68 integration.integration_type = IntegrationType.key
68 self.sa.add(integration)
69 self.sa.add(integration)
69 self.update_integration(integration, name, enabled, scope, settings)
70 self.update_integration(integration, name, enabled, repo, repo_group,
71 child_repos_only, settings)
70 self.sa.commit()
72 self.sa.commit()
71 return integration
73 return integration
72
74
73 def update_integration(self, integration, name, enabled, scope, settings):
75 def update_integration(self, integration, name, enabled, repo, repo_group,
74 """
76 child_repos_only, settings):
75 :param scope: one of ['global', 'root_repos', <RepoGroup>. <Repository>]
76 """
77
78 integration = self.__get_integration(integration)
77 integration = self.__get_integration(integration)
79
78
80 integration.scope = scope
79 integration.repo = repo
80 integration.repo_group = repo_group
81 integration.child_repos_only = child_repos_only
81 integration.name = name
82 integration.name = name
82 integration.enabled = enabled
83 integration.enabled = enabled
83 integration.settings = settings
84 integration.settings = settings
84
85
85 return integration
86 return integration
86
87
87 def delete(self, integration):
88 def delete(self, integration):
88 integration = self.__get_integration(integration)
89 integration = self.__get_integration(integration)
89 if integration:
90 if integration:
90 self.sa.delete(integration)
91 self.sa.delete(integration)
91 return True
92 return True
92 return False
93 return False
93
94
94 def get_integration_handler(self, integration):
95 def get_integration_handler(self, integration):
95 TypeClass = integration_type_registry.get(integration.integration_type)
96 TypeClass = integration_type_registry.get(integration.integration_type)
96 if not TypeClass:
97 if not TypeClass:
97 log.error('No class could be found for integration type: {}'.format(
98 log.error('No class could be found for integration type: {}'.format(
98 integration.integration_type))
99 integration.integration_type))
99 return None
100 return None
100
101
101 return TypeClass(integration.settings)
102 return TypeClass(integration.settings)
102
103
103 def send_event(self, integration, event):
104 def send_event(self, integration, event):
104 """ Send an event to an integration """
105 """ Send an event to an integration """
105 handler = self.get_integration_handler(integration)
106 handler = self.get_integration_handler(integration)
106 if handler:
107 if handler:
107 handler.send_event(event)
108 handler.send_event(event)
108
109
109 def get_integrations(self, scope, IntegrationType=None):
110 def get_integrations(self, scope, IntegrationType=None):
110 """
111 """
111 Return integrations for a scope, which must be one of:
112 Return integrations for a scope, which must be one of:
112
113
113 'all' - every integration, global/repogroup/repo
114 'all' - every integration, global/repogroup/repo
114 'global' - global integrations only
115 'global' - global integrations only
115 <Repository> instance - integrations for this repo only
116 <Repository> instance - integrations for this repo only
116 <RepoGroup> instance - integrations for this repogroup only
117 <RepoGroup> instance - integrations for this repogroup only
117 """
118 """
118
119
119 if isinstance(scope, Repository):
120 if isinstance(scope, Repository):
120 query = self.sa.query(Integration).filter(
121 query = self.sa.query(Integration).filter(
121 Integration.repo==scope)
122 Integration.repo==scope)
122 elif isinstance(scope, RepoGroup):
123 elif isinstance(scope, RepoGroup):
123 query = self.sa.query(Integration).filter(
124 query = self.sa.query(Integration).filter(
124 Integration.repo_group==scope)
125 Integration.repo_group==scope)
125 elif scope == 'global':
126 elif scope == 'global':
126 # global integrations
127 # global integrations
127 query = self.sa.query(Integration).filter(
128 query = self.sa.query(Integration).filter(
128 and_(Integration.repo_id==None, Integration.repo_group_id==None)
129 and_(Integration.repo_id==None, Integration.repo_group_id==None)
129 )
130 )
130 elif scope == 'root_repos':
131 elif scope == 'root-repos':
131 query = self.sa.query(Integration).filter(
132 query = self.sa.query(Integration).filter(
132 and_(Integration.repo_id==None,
133 and_(Integration.repo_id==None,
133 Integration.repo_group_id==None,
134 Integration.repo_group_id==None,
134 Integration.child_repos_only==True)
135 Integration.child_repos_only==True)
135 )
136 )
136 elif scope == 'all':
137 elif scope == 'all':
137 query = self.sa.query(Integration)
138 query = self.sa.query(Integration)
138 else:
139 else:
139 raise Exception(
140 raise Exception(
140 "invalid `scope`, must be one of: "
141 "invalid `scope`, must be one of: "
141 "['global', 'all', <Repository>, <RepoGroup>]")
142 "['global', 'all', <Repository>, <RepoGroup>]")
142
143
143 if IntegrationType is not None:
144 if IntegrationType is not None:
144 query = query.filter(
145 query = query.filter(
145 Integration.integration_type==IntegrationType.key)
146 Integration.integration_type==IntegrationType.key)
146
147
147 result = []
148 result = []
148 for integration in query.all():
149 for integration in query.all():
149 IntType = integration_type_registry.get(integration.integration_type)
150 IntType = integration_type_registry.get(integration.integration_type)
150 result.append((IntType, integration))
151 result.append((IntType, integration))
151 return result
152 return result
152
153
153 def get_for_event(self, event, cache=False):
154 def get_for_event(self, event, cache=False):
154 """
155 """
155 Get integrations that match an event
156 Get integrations that match an event
156 """
157 """
157 query = self.sa.query(
158 query = self.sa.query(
158 Integration
159 Integration
159 ).filter(
160 ).filter(
160 Integration.enabled==True
161 Integration.enabled==True
161 )
162 )
162
163
163 global_integrations_filter = and_(
164 global_integrations_filter = and_(
164 Integration.repo_id==None,
165 Integration.repo_id==None,
165 Integration.repo_group_id==None,
166 Integration.repo_group_id==None,
166 Integration.child_repos_only==False,
167 Integration.child_repos_only==False,
167 )
168 )
168
169
169 if isinstance(event, events.RepoEvent):
170 if isinstance(event, events.RepoEvent):
170 root_repos_integrations_filter = and_(
171 root_repos_integrations_filter = and_(
171 Integration.repo_id==None,
172 Integration.repo_id==None,
172 Integration.repo_group_id==None,
173 Integration.repo_group_id==None,
173 Integration.child_repos_only==True,
174 Integration.child_repos_only==True,
174 )
175 )
175
176
176 clauses = [
177 clauses = [
177 global_integrations_filter,
178 global_integrations_filter,
178 ]
179 ]
179
180
180 # repo integrations
181 # repo integrations
181 if event.repo.repo_id: # pre create events dont have a repo_id yet
182 if event.repo.repo_id: # pre create events dont have a repo_id yet
182 clauses.append(
183 clauses.append(
183 Integration.repo_id==event.repo.repo_id
184 Integration.repo_id==event.repo.repo_id
184 )
185 )
185
186
186 if event.repo.group:
187 if event.repo.group:
187 clauses.append(
188 clauses.append(
188 Integration.repo_group_id == event.repo.group.group_id
189 and_(
190 Integration.repo_group_id==event.repo.group.group_id,
191 Integration.child_repos_only==True
192 )
189 )
193 )
190 # repo group cascade to kids (maybe implement this sometime?)
194 # repo group cascade to kids
191 # clauses.append(Integration.repo_group_id.in_(
195 clauses.append(
192 # [group.group_id for group in
196 and_(
193 # event.repo.groups_with_parents]
197 Integration.repo_group_id.in_(
194 # ))
198 [group.group_id for group in
199 event.repo.groups_with_parents]
200 ),
201 Integration.child_repos_only==False
202 )
203 )
195
204
196
205
197 if not event.repo.group: # root repo
206 if not event.repo.group: # root repo
198 clauses.append(root_repos_integrations_filter)
207 clauses.append(root_repos_integrations_filter)
199
208
200 query = query.filter(or_(*clauses))
209 query = query.filter(or_(*clauses))
201
210
202 if cache:
211 if cache:
203 query = query.options(FromCache(
212 query = query.options(FromCache(
204 "sql_cache_short",
213 "sql_cache_short",
205 "get_enabled_repo_integrations_%i" % event.repo.repo_id))
214 "get_enabled_repo_integrations_%i" % event.repo.repo_id))
206 else: # only global integrations
215 else: # only global integrations
207 query = query.filter(global_integrations_filter)
216 query = query.filter(global_integrations_filter)
208 if cache:
217 if cache:
209 query = query.options(FromCache(
218 query = query.options(FromCache(
210 "sql_cache_short", "get_enabled_global_integrations"))
219 "sql_cache_short", "get_enabled_global_integrations"))
211
220
212 result = query.all()
221 result = query.all()
213 return result No newline at end of file
222 return result
@@ -1,187 +1,226 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-2016 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 os
21 import os
22
22
23 import deform
23 import deform
24 import colander
24 import colander
25
25
26 from rhodecode.translation import _
26 from rhodecode.translation import _
27 from rhodecode.model.db import Repository, RepoGroup
27 from rhodecode.model.db import Repository, RepoGroup
28 from rhodecode.model.validation_schema import validators, preparers
28 from rhodecode.model.validation_schema import validators, preparers
29
29
30
30
31 def integration_scope_choices(permissions):
31 def integration_scope_choices(permissions):
32 """
32 """
33 Return list of (value, label) choices for integration scopes depending on
33 Return list of (value, label) choices for integration scopes depending on
34 the permissions
34 the permissions
35 """
35 """
36 result = [('', _('Pick a scope:'))]
36 result = [('', _('Pick a scope:'))]
37 if 'hg.admin' in permissions['global']:
37 if 'hg.admin' in permissions['global']:
38 result.extend([
38 result.extend([
39 ('global', _('Global (all repositories)')),
39 ('global', _('Global (all repositories)')),
40 ('root_repos', _('Top level repositories only')),
40 ('root-repos', _('Top level repositories only')),
41 ])
41 ])
42
42
43 repo_choices = [
43 repo_choices = [
44 ('repo:%s' % repo_name, '/' + repo_name)
44 ('repo:%s' % repo_name, '/' + repo_name)
45 for repo_name, repo_perm
45 for repo_name, repo_perm
46 in permissions['repositories'].items()
46 in permissions['repositories'].items()
47 if repo_perm == 'repository.admin'
47 if repo_perm == 'repository.admin'
48 ]
48 ]
49 repogroup_choices = [
49 repogroup_choices = [
50 ('repogroup:%s' % repo_group_name, '/' + repo_group_name + ' (group)')
50 ('repogroup:%s' % repo_group_name, '/' + repo_group_name + '/ (child repos only)')
51 for repo_group_name, repo_group_perm
52 in permissions['repositories_groups'].items()
53 if repo_group_perm == 'group.admin'
54 ]
55 repogroup_recursive_choices = [
56 ('repogroup-recursive:%s' % repo_group_name, '/' + repo_group_name + '/ (recursive)')
51 for repo_group_name, repo_group_perm
57 for repo_group_name, repo_group_perm
52 in permissions['repositories_groups'].items()
58 in permissions['repositories_groups'].items()
53 if repo_group_perm == 'group.admin'
59 if repo_group_perm == 'group.admin'
54 ]
60 ]
55 result.extend(
61 result.extend(
56 sorted(repogroup_choices + repo_choices,
62 sorted(repogroup_recursive_choices + repogroup_choices + repo_choices,
57 key=lambda (choice, label): choice.split(':', 1)[1]
63 key=lambda (choice, label): choice.split(':', 1)[1]
58 )
64 )
59 )
65 )
60 return result
66 return result
61
67
62
68
63 @colander.deferred
69 @colander.deferred
64 def deferred_integration_scopes_validator(node, kw):
70 def deferred_integration_scopes_validator(node, kw):
65 perms = kw.get('permissions')
71 perms = kw.get('permissions')
66 def _scope_validator(_node, scope):
72 def _scope_validator(_node, scope):
67 is_super_admin = 'hg.admin' in perms['global']
73 is_super_admin = 'hg.admin' in perms['global']
68
74
69 if scope in ('global', 'root_repos'):
75 if scope.get('repo'):
76 if (is_super_admin or perms['repositories'].get(
77 scope['repo'].repo_name) == 'repository.admin'):
78 return True
79 msg = _('Only repo admins can create integrations')
80 raise colander.Invalid(_node, msg)
81 elif scope.get('repo_group'):
82 if (is_super_admin or perms['repositories_groups'].get(
83 scope['repo_group'].group_name) == 'group.admin'):
84 return True
85
86 msg = _('Only repogroup admins can create integrations')
87 raise colander.Invalid(_node, msg)
88 else:
70 if is_super_admin:
89 if is_super_admin:
71 return True
90 return True
72 msg = _('Only superadmins can create global integrations')
91 msg = _('Only superadmins can create global integrations')
73 raise colander.Invalid(_node, msg)
92 raise colander.Invalid(_node, msg)
74 elif isinstance(scope, Repository):
75 if (is_super_admin or perms['repositories'].get(
76 scope.repo_name) == 'repository.admin'):
77 return True
78 msg = _('Only repo admins can create integrations')
79 raise colander.Invalid(_node, msg)
80 elif isinstance(scope, RepoGroup):
81 if (is_super_admin or perms['repositories_groups'].get(
82 scope.group_name) == 'group.admin'):
83 return True
84
85 msg = _('Only repogroup admins can create integrations')
86 raise colander.Invalid(_node, msg)
87
88 msg = _('Invalid integration scope: %s' % scope)
89 raise colander.Invalid(node, msg)
90
93
91 return _scope_validator
94 return _scope_validator
92
95
93
96
94 @colander.deferred
97 @colander.deferred
95 def deferred_integration_scopes_widget(node, kw):
98 def deferred_integration_scopes_widget(node, kw):
96 if kw.get('no_scope'):
99 if kw.get('no_scope'):
97 return deform.widget.TextInputWidget(readonly=True)
100 return deform.widget.TextInputWidget(readonly=True)
98
101
99 choices = integration_scope_choices(kw.get('permissions'))
102 choices = integration_scope_choices(kw.get('permissions'))
100 widget = deform.widget.Select2Widget(values=choices)
103 widget = deform.widget.Select2Widget(values=choices)
101 return widget
104 return widget
102
105
103 class IntegrationScope(colander.SchemaType):
106
107 class IntegrationScopeType(colander.SchemaType):
104 def serialize(self, node, appstruct):
108 def serialize(self, node, appstruct):
105 if appstruct is colander.null:
109 if appstruct is colander.null:
106 return colander.null
110 return colander.null
107
111
108 if isinstance(appstruct, Repository):
112 if appstruct.get('repo'):
109 return 'repo:%s' % appstruct.repo_name
113 return 'repo:%s' % appstruct['repo'].repo_name
110 elif isinstance(appstruct, RepoGroup):
114 elif appstruct.get('repo_group'):
111 return 'repogroup:%s' % appstruct.group_name
115 if appstruct.get('child_repos_only'):
112 elif appstruct in ('global', 'root_repos'):
116 return 'repogroup:%s' % appstruct['repo_group'].group_name
113 return appstruct
117 else:
118 return 'repogroup-recursive:%s' % (
119 appstruct['repo_group'].group_name)
120 else:
121 if appstruct.get('child_repos_only'):
122 return 'root-repos'
123 else:
124 return 'global'
125
114 raise colander.Invalid(node, '%r is not a valid scope' % appstruct)
126 raise colander.Invalid(node, '%r is not a valid scope' % appstruct)
115
127
116 def deserialize(self, node, cstruct):
128 def deserialize(self, node, cstruct):
117 if cstruct is colander.null:
129 if cstruct is colander.null:
118 return colander.null
130 return colander.null
119
131
120 if cstruct.startswith('repo:'):
132 if cstruct.startswith('repo:'):
121 repo = Repository.get_by_repo_name(cstruct.split(':')[1])
133 repo = Repository.get_by_repo_name(cstruct.split(':')[1])
122 if repo:
134 if repo:
123 return repo
135 return {
136 'repo': repo,
137 'repo_group': None,
138 'child_repos_only': None,
139 }
140 elif cstruct.startswith('repogroup-recursive:'):
141 repo_group = RepoGroup.get_by_group_name(cstruct.split(':')[1])
142 if repo_group:
143 return {
144 'repo': None,
145 'repo_group': repo_group,
146 'child_repos_only': False
147 }
124 elif cstruct.startswith('repogroup:'):
148 elif cstruct.startswith('repogroup:'):
125 repo_group = RepoGroup.get_by_group_name(cstruct.split(':')[1])
149 repo_group = RepoGroup.get_by_group_name(cstruct.split(':')[1])
126 if repo_group:
150 if repo_group:
127 return repo_group
151 return {
128 elif cstruct in ('global', 'root_repos'):
152 'repo': None,
129 return cstruct
153 'repo_group': repo_group,
154 'child_repos_only': True
155 }
156 elif cstruct == 'global':
157 return {
158 'repo': None,
159 'repo_group': None,
160 'child_repos_only': False
161 }
162 elif cstruct == 'root-repos':
163 return {
164 'repo': None,
165 'repo_group': None,
166 'child_repos_only': True
167 }
130
168
131 raise colander.Invalid(node, '%r is not a valid scope' % cstruct)
169 raise colander.Invalid(node, '%r is not a valid scope' % cstruct)
132
170
171
133 class IntegrationOptionsSchemaBase(colander.MappingSchema):
172 class IntegrationOptionsSchemaBase(colander.MappingSchema):
134
173
135 name = colander.SchemaNode(
174 name = colander.SchemaNode(
136 colander.String(),
175 colander.String(),
137 description=_('Short name for this integration.'),
176 description=_('Short name for this integration.'),
138 missing=colander.required,
177 missing=colander.required,
139 title=_('Integration name'),
178 title=_('Integration name'),
140 )
179 )
141
180
142 scope = colander.SchemaNode(
181 scope = colander.SchemaNode(
143 IntegrationScope(),
182 IntegrationScopeType(),
144 description=_(
183 description=_(
145 'Scope of the integration. Group scope means the integration '
184 'Scope of the integration. Recursive means the integration '
146 ' runs on all child repos of that group.'),
185 ' runs on all repos of that group and children recursively.'),
147 title=_('Integration scope'),
186 title=_('Integration scope'),
148 validator=deferred_integration_scopes_validator,
187 validator=deferred_integration_scopes_validator,
149 widget=deferred_integration_scopes_widget,
188 widget=deferred_integration_scopes_widget,
150 missing=colander.required,
189 missing=colander.required,
151 )
190 )
152
191
153 enabled = colander.SchemaNode(
192 enabled = colander.SchemaNode(
154 colander.Bool(),
193 colander.Bool(),
155 default=True,
194 default=True,
156 description=_('Enable or disable this integration.'),
195 description=_('Enable or disable this integration.'),
157 missing=False,
196 missing=False,
158 title=_('Enabled'),
197 title=_('Enabled'),
159 )
198 )
160
199
161
200
162
201
163 def make_integration_schema(IntegrationType, settings=None):
202 def make_integration_schema(IntegrationType, settings=None):
164 """
203 """
165 Return a colander schema for an integration type
204 Return a colander schema for an integration type
166
205
167 :param IntegrationType: the integration type class
206 :param IntegrationType: the integration type class
168 :param settings: existing integration settings dict (optional)
207 :param settings: existing integration settings dict (optional)
169 """
208 """
170
209
171 settings = settings or {}
210 settings = settings or {}
172 settings_schema = IntegrationType(settings=settings).settings_schema()
211 settings_schema = IntegrationType(settings=settings).settings_schema()
173
212
174 class IntegrationSchema(colander.Schema):
213 class IntegrationSchema(colander.Schema):
175 options = IntegrationOptionsSchemaBase()
214 options = IntegrationOptionsSchemaBase()
176
215
177 schema = IntegrationSchema()
216 schema = IntegrationSchema()
178 schema['options'].title = _('General integration options')
217 schema['options'].title = _('General integration options')
179
218
180 settings_schema.name = 'settings'
219 settings_schema.name = 'settings'
181 settings_schema.title = _('{integration_type} settings').format(
220 settings_schema.title = _('{integration_type} settings').format(
182 integration_type=IntegrationType.display_name)
221 integration_type=IntegrationType.display_name)
183 schema.add(settings_schema)
222 schema.add(settings_schema)
184
223
185 return schema
224 return schema
186
225
187
226
@@ -1,249 +1,252 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.html"/>
2 <%inherit file="base.html"/>
3
3
4 <%def name="breadcrumbs_links()">
4 <%def name="breadcrumbs_links()">
5 %if c.repo:
5 %if c.repo:
6 ${h.link_to('Settings',h.url('edit_repo', repo_name=c.repo.repo_name))}
6 ${h.link_to('Settings',h.url('edit_repo', repo_name=c.repo.repo_name))}
7 %elif c.repo_group:
7 %elif c.repo_group:
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
12 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
13 %else:
13 %else:
14 ${h.link_to(_('Admin'),h.url('admin_home'))}
14 ${h.link_to(_('Admin'),h.url('admin_home'))}
15 &raquo;
15 &raquo;
16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
17 %endif
17 %endif
18 %if current_IntegrationType:
18 %if current_IntegrationType:
19 &raquo;
19 &raquo;
20 %if c.repo:
20 %if c.repo:
21 ${h.link_to(_('Integrations'),
21 ${h.link_to(_('Integrations'),
22 request.route_url(route_name='repo_integrations_home',
22 request.route_url(route_name='repo_integrations_home',
23 repo_name=c.repo.repo_name))}
23 repo_name=c.repo.repo_name))}
24 %elif c.repo_group:
24 %elif c.repo_group:
25 ${h.link_to(_('Integrations'),
25 ${h.link_to(_('Integrations'),
26 request.route_url(route_name='repo_group_integrations_home',
26 request.route_url(route_name='repo_group_integrations_home',
27 repo_group_name=c.repo_group.group_name))}
27 repo_group_name=c.repo_group.group_name))}
28 %else:
28 %else:
29 ${h.link_to(_('Integrations'),
29 ${h.link_to(_('Integrations'),
30 request.route_url(route_name='global_integrations_home'))}
30 request.route_url(route_name='global_integrations_home'))}
31 %endif
31 %endif
32 &raquo;
32 &raquo;
33 ${current_IntegrationType.display_name}
33 ${current_IntegrationType.display_name}
34 %else:
34 %else:
35 &raquo;
35 &raquo;
36 ${_('Integrations')}
36 ${_('Integrations')}
37 %endif
37 %endif
38 </%def>
38 </%def>
39
39
40 <div class="panel panel-default">
40 <div class="panel panel-default">
41 <div class="panel-heading">
41 <div class="panel-heading">
42 <h3 class="panel-title">
42 <h3 class="panel-title">
43 %if c.repo:
43 %if c.repo:
44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
45 %elif c.repo_group:
45 %elif c.repo_group:
46 ${_('Current Integrations for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
46 ${_('Current Integrations for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
47 %else:
47 %else:
48 ${_('Current Integrations')}
48 ${_('Current Integrations')}
49 %endif
49 %endif
50 </h3>
50 </h3>
51 </div>
51 </div>
52 <div class="panel-body">
52 <div class="panel-body">
53 <%
53 <%
54 if c.repo:
54 if c.repo:
55 home_url = request.route_path('repo_integrations_home',
55 home_url = request.route_path('repo_integrations_home',
56 repo_name=c.repo.repo_name)
56 repo_name=c.repo.repo_name)
57 elif c.repo_group:
57 elif c.repo_group:
58 home_url = request.route_path('repo_group_integrations_home',
58 home_url = request.route_path('repo_group_integrations_home',
59 repo_group_name=c.repo_group.group_name)
59 repo_group_name=c.repo_group.group_name)
60 else:
60 else:
61 home_url = request.route_path('global_integrations_home')
61 home_url = request.route_path('global_integrations_home')
62 %>
62 %>
63
63
64 <a href="${home_url}" class="btn ${not current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a>
64 <a href="${home_url}" class="btn ${not current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a>
65
65
66 %for integration_key, IntegrationType in available_integrations.items():
66 %for integration_key, IntegrationType in available_integrations.items():
67 <%
67 <%
68 if c.repo:
68 if c.repo:
69 list_url = request.route_path('repo_integrations_list',
69 list_url = request.route_path('repo_integrations_list',
70 repo_name=c.repo.repo_name,
70 repo_name=c.repo.repo_name,
71 integration=integration_key)
71 integration=integration_key)
72 elif c.repo_group:
72 elif c.repo_group:
73 list_url = request.route_path('repo_group_integrations_list',
73 list_url = request.route_path('repo_group_integrations_list',
74 repo_group_name=c.repo_group.group_name,
74 repo_group_name=c.repo_group.group_name,
75 integration=integration_key)
75 integration=integration_key)
76 else:
76 else:
77 list_url = request.route_path('global_integrations_list',
77 list_url = request.route_path('global_integrations_list',
78 integration=integration_key)
78 integration=integration_key)
79 %>
79 %>
80 <a href="${list_url}"
80 <a href="${list_url}"
81 class="btn ${current_IntegrationType and integration_key == current_IntegrationType.key and 'btn-primary' or ''}">
81 class="btn ${current_IntegrationType and integration_key == current_IntegrationType.key and 'btn-primary' or ''}">
82 ${IntegrationType.display_name}
82 ${IntegrationType.display_name}
83 </a>
83 </a>
84 %endfor
84 %endfor
85
85
86 <%
86 <%
87 if c.repo:
87 if c.repo:
88 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
88 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
89 elif c.repo_group:
89 elif c.repo_group:
90 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
90 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
91 else:
91 else:
92 create_url = h.route_path('global_integrations_new')
92 create_url = h.route_path('global_integrations_new')
93 %>
93 %>
94 <p class="pull-right">
94 <p class="pull-right">
95 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
95 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
96 </p>
96 </p>
97
97
98 <table class="rctable integrations">
98 <table class="rctable integrations">
99 <thead>
99 <thead>
100 <tr>
100 <tr>
101 <th><a href="?sort=enabled:${rev_sort_dir}">${_('Enabled')}</a></th>
101 <th><a href="?sort=enabled:${rev_sort_dir}">${_('Enabled')}</a></th>
102 <th><a href="?sort=name:${rev_sort_dir}">${_('Name')}</a></th>
102 <th><a href="?sort=name:${rev_sort_dir}">${_('Name')}</a></th>
103 <th colspan="2"><a href="?sort=integration_type:${rev_sort_dir}">${_('Type')}</a></th>
103 <th colspan="2"><a href="?sort=integration_type:${rev_sort_dir}">${_('Type')}</a></th>
104 <th><a href="?sort=scope:${rev_sort_dir}">${_('Scope')}</a></th>
104 <th><a href="?sort=scope:${rev_sort_dir}">${_('Scope')}</a></th>
105 <th>${_('Actions')}</th>
105 <th>${_('Actions')}</th>
106 <th></th>
106 <th></th>
107 </tr>
107 </tr>
108 </thead>
108 </thead>
109 <tbody>
109 <tbody>
110 %if not integrations_list:
110 %if not integrations_list:
111 <tr>
111 <tr>
112 <td colspan="7">
112 <td colspan="7">
113 <% integration_type = current_IntegrationType and current_IntegrationType.display_name or '' %>
113 <% integration_type = current_IntegrationType and current_IntegrationType.display_name or '' %>
114 %if c.repo:
114 %if c.repo:
115 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
115 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
116 %elif c.repo_group:
116 %elif c.repo_group:
117 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
117 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
118 %else:
118 %else:
119 ${_('No {type} integrations exist yet.').format(type=integration_type)}
119 ${_('No {type} integrations exist yet.').format(type=integration_type)}
120 %endif
120 %endif
121
121
122 %if current_IntegrationType:
122 %if current_IntegrationType:
123 <%
123 <%
124 if c.repo:
124 if c.repo:
125 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=current_IntegrationType.key)
125 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=current_IntegrationType.key)
126 elif c.repo_group:
126 elif c.repo_group:
127 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=current_IntegrationType.key)
127 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=current_IntegrationType.key)
128 else:
128 else:
129 create_url = h.route_path('global_integrations_create', integration=current_IntegrationType.key)
129 create_url = h.route_path('global_integrations_create', integration=current_IntegrationType.key)
130 %>
130 %>
131 %endif
131 %endif
132
132
133 <a href="${create_url}">${_(u'Create one')}</a>
133 <a href="${create_url}">${_(u'Create one')}</a>
134 </td>
134 </td>
135 </tr>
135 </tr>
136 %endif
136 %endif
137 %for IntegrationType, integration in integrations_list:
137 %for IntegrationType, integration in integrations_list:
138 <tr id="integration_${integration.integration_id}">
138 <tr id="integration_${integration.integration_id}">
139 <td class="td-enabled">
139 <td class="td-enabled">
140 %if integration.enabled:
140 %if integration.enabled:
141 <div class="flag_status approved pull-left"></div>
141 <div class="flag_status approved pull-left"></div>
142 %else:
142 %else:
143 <div class="flag_status rejected pull-left"></div>
143 <div class="flag_status rejected pull-left"></div>
144 %endif
144 %endif
145 </td>
145 </td>
146 <td class="td-description">
146 <td class="td-description">
147 ${integration.name}
147 ${integration.name}
148 </td>
148 </td>
149 <td class="td-icon">
149 <td class="td-icon">
150 %if integration.integration_type in available_integrations:
150 %if integration.integration_type in available_integrations:
151 <div class="integration-icon">
151 <div class="integration-icon">
152 ${available_integrations[integration.integration_type].icon|n}
152 ${available_integrations[integration.integration_type].icon|n}
153 </div>
153 </div>
154 %else:
154 %else:
155 ?
155 ?
156 %endif
156 %endif
157 </td>
157 </td>
158 <td class="td-type">
158 <td class="td-type">
159 ${integration.integration_type}
159 ${integration.integration_type}
160 </td>
160 </td>
161 <td class="td-scope">
161 <td class="td-scope">
162 %if integration.repo:
162 %if integration.repo:
163 <a href="${h.url('summary_home', repo_name=integration.repo.repo_name)}">
163 <a href="${h.url('summary_home', repo_name=integration.repo.repo_name)}">
164 ${_('repo')}:${integration.repo.repo_name}
164 ${_('repo')}:${integration.repo.repo_name}
165 </a>
165 </a>
166 %elif integration.repo_group:
166 %elif integration.repo_group:
167 <a href="${h.url('repo_group_home', group_name=integration.repo_group.group_name)}">
167 <a href="${h.url('repo_group_home', group_name=integration.repo_group.group_name)}">
168 ${_('repogroup')}:${integration.repo_group.group_name}
168 ${_('repogroup')}:${integration.repo_group.group_name}
169 %if integration.child_repos_only:
170 ${_('child repos only')}
171 %else:
172 ${_('cascade to all')}
173 %endif
169 </a>
174 </a>
170 %else:
175 %else:
171 %if integration.scope == 'root_repos':
176 %if integration.child_repos_only:
172 ${_('top level repos only')}
177 ${_('top level repos only')}
173 %elif integration.scope == 'global':
178 %else:
174 ${_('global')}
179 ${_('global')}
175 %else:
176 ${_('unknown scope')}: ${integration.scope}
177 %endif
180 %endif
178 </td>
181 </td>
179 %endif
182 %endif
180 <td class="td-action">
183 <td class="td-action">
181 %if not IntegrationType:
184 %if not IntegrationType:
182 ${_('unknown integration')}
185 ${_('unknown integration')}
183 %else:
186 %else:
184 <%
187 <%
185 if c.repo:
188 if c.repo:
186 edit_url = request.route_path('repo_integrations_edit',
189 edit_url = request.route_path('repo_integrations_edit',
187 repo_name=c.repo.repo_name,
190 repo_name=c.repo.repo_name,
188 integration=integration.integration_type,
191 integration=integration.integration_type,
189 integration_id=integration.integration_id)
192 integration_id=integration.integration_id)
190 elif c.repo_group:
193 elif c.repo_group:
191 edit_url = request.route_path('repo_group_integrations_edit',
194 edit_url = request.route_path('repo_group_integrations_edit',
192 repo_group_name=c.repo_group.group_name,
195 repo_group_name=c.repo_group.group_name,
193 integration=integration.integration_type,
196 integration=integration.integration_type,
194 integration_id=integration.integration_id)
197 integration_id=integration.integration_id)
195 else:
198 else:
196 edit_url = request.route_path('global_integrations_edit',
199 edit_url = request.route_path('global_integrations_edit',
197 integration=integration.integration_type,
200 integration=integration.integration_type,
198 integration_id=integration.integration_id)
201 integration_id=integration.integration_id)
199 %>
202 %>
200 <div class="grid_edit">
203 <div class="grid_edit">
201 <a href="${edit_url}">${_('Edit')}</a>
204 <a href="${edit_url}">${_('Edit')}</a>
202 </div>
205 </div>
203 <div class="grid_delete">
206 <div class="grid_delete">
204 <a href="${edit_url}"
207 <a href="${edit_url}"
205 class="btn btn-link btn-danger delete_integration_entry"
208 class="btn btn-link btn-danger delete_integration_entry"
206 data-desc="${integration.name}"
209 data-desc="${integration.name}"
207 data-uid="${integration.integration_id}">
210 data-uid="${integration.integration_id}">
208 ${_('Delete')}
211 ${_('Delete')}
209 </a>
212 </a>
210 </div>
213 </div>
211 %endif
214 %endif
212 </td>
215 </td>
213 </tr>
216 </tr>
214 %endfor
217 %endfor
215 <tr id="last-row"></tr>
218 <tr id="last-row"></tr>
216 </tbody>
219 </tbody>
217 </table>
220 </table>
218 <div class="integrations-paginator">
221 <div class="integrations-paginator">
219 <div class="pagination-wh pagination-left">
222 <div class="pagination-wh pagination-left">
220 ${integrations_list.pager('$link_previous ~2~ $link_next')}
223 ${integrations_list.pager('$link_previous ~2~ $link_next')}
221 </div>
224 </div>
222 </div>
225 </div>
223 </div>
226 </div>
224 </div>
227 </div>
225 <script type="text/javascript">
228 <script type="text/javascript">
226 var delete_integration = function(entry) {
229 var delete_integration = function(entry) {
227 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
230 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
228 var request = $.ajax({
231 var request = $.ajax({
229 type: "POST",
232 type: "POST",
230 url: $(entry).attr('href'),
233 url: $(entry).attr('href'),
231 data: {
234 data: {
232 'delete': 'delete',
235 'delete': 'delete',
233 'csrf_token': CSRF_TOKEN
236 'csrf_token': CSRF_TOKEN
234 },
237 },
235 success: function(){
238 success: function(){
236 location.reload();
239 location.reload();
237 },
240 },
238 error: function(data, textStatus, errorThrown){
241 error: function(data, textStatus, errorThrown){
239 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
242 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
240 }
243 }
241 });
244 });
242 };
245 };
243 }
246 }
244
247
245 $('.delete_integration_entry').on('click', function(e){
248 $('.delete_integration_entry').on('click', function(e){
246 e.preventDefault();
249 e.preventDefault();
247 delete_integration(this);
250 delete_integration(this);
248 });
251 });
249 </script> No newline at end of file
252 </script>
@@ -1,262 +1,264 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 mock
21 import mock
22 import pytest
22 import pytest
23 from webob.exc import HTTPNotFound
23 from webob.exc import HTTPNotFound
24
24
25 import rhodecode
25 import rhodecode
26 from rhodecode.model.db import Integration
26 from rhodecode.model.db import Integration
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.tests import assert_session_flash, url, TEST_USER_ADMIN_LOGIN
28 from rhodecode.tests import assert_session_flash, url, TEST_USER_ADMIN_LOGIN
29 from rhodecode.tests.utils import AssertResponse
29 from rhodecode.tests.utils import AssertResponse
30 from rhodecode.integrations import integration_type_registry
30 from rhodecode.integrations import integration_type_registry
31 from rhodecode.config.routing import ADMIN_PREFIX
31 from rhodecode.config.routing import ADMIN_PREFIX
32
32
33
33
34 @pytest.mark.usefixtures('app', 'autologin_user')
34 @pytest.mark.usefixtures('app', 'autologin_user')
35 class TestIntegrationsView(object):
35 class TestIntegrationsView(object):
36 pass
36 pass
37
37
38
38
39 class TestGlobalIntegrationsView(TestIntegrationsView):
39 class TestGlobalIntegrationsView(TestIntegrationsView):
40 def test_index_no_integrations(self, app):
40 def test_index_no_integrations(self, app):
41 url = ADMIN_PREFIX + '/integrations'
41 url = ADMIN_PREFIX + '/integrations'
42 response = app.get(url)
42 response = app.get(url)
43
43
44 assert response.status_code == 200
44 assert response.status_code == 200
45 assert 'exist yet' in response.body
45 assert 'exist yet' in response.body
46
46
47 def test_index_with_integrations(self, app, global_integration_stub):
47 def test_index_with_integrations(self, app, global_integration_stub):
48 url = ADMIN_PREFIX + '/integrations'
48 url = ADMIN_PREFIX + '/integrations'
49 response = app.get(url)
49 response = app.get(url)
50
50
51 assert response.status_code == 200
51 assert response.status_code == 200
52 assert 'exist yet' not in response.body
52 assert 'exist yet' not in response.body
53 assert global_integration_stub.name in response.body
53 assert global_integration_stub.name in response.body
54
54
55 def test_new_integration_page(self, app):
55 def test_new_integration_page(self, app):
56 url = ADMIN_PREFIX + '/integrations/new'
56 url = ADMIN_PREFIX + '/integrations/new'
57
57
58 response = app.get(url)
58 response = app.get(url)
59
59
60 assert response.status_code == 200
60 assert response.status_code == 200
61
61
62 for integration_key in integration_type_registry:
62 for integration_key in integration_type_registry:
63 nurl = (ADMIN_PREFIX + '/integrations/{integration}/new').format(
63 nurl = (ADMIN_PREFIX + '/integrations/{integration}/new').format(
64 integration=integration_key)
64 integration=integration_key)
65 assert nurl in response.body
65 assert nurl in response.body
66
66
67 @pytest.mark.parametrize(
67 @pytest.mark.parametrize(
68 'IntegrationType', integration_type_registry.values())
68 'IntegrationType', integration_type_registry.values())
69 def test_get_create_integration_page(self, app, IntegrationType):
69 def test_get_create_integration_page(self, app, IntegrationType):
70 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
70 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
71 integration_key=IntegrationType.key)
71 integration_key=IntegrationType.key)
72
72
73 response = app.get(url)
73 response = app.get(url)
74
74
75 assert response.status_code == 200
75 assert response.status_code == 200
76 assert IntegrationType.display_name in response.body
76 assert IntegrationType.display_name in response.body
77
77
78 def test_post_integration_page(self, app, StubIntegrationType, csrf_token,
78 def test_post_integration_page(self, app, StubIntegrationType, csrf_token,
79 test_repo_group, backend_random):
79 test_repo_group, backend_random):
80 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
80 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
81 integration_key=StubIntegrationType.key)
81 integration_key=StubIntegrationType.key)
82
82
83 _post_integration_test_helper(app, url, csrf_token, admin_view=True,
83 _post_integration_test_helper(app, url, csrf_token, admin_view=True,
84 repo=backend_random.repo, repo_group=test_repo_group)
84 repo=backend_random.repo, repo_group=test_repo_group)
85
85
86
86
87 class TestRepoGroupIntegrationsView(TestIntegrationsView):
87 class TestRepoGroupIntegrationsView(TestIntegrationsView):
88 def test_index_no_integrations(self, app, test_repo_group):
88 def test_index_no_integrations(self, app, test_repo_group):
89 url = '/{repo_group_name}/settings/integrations'.format(
89 url = '/{repo_group_name}/settings/integrations'.format(
90 repo_group_name=test_repo_group.group_name)
90 repo_group_name=test_repo_group.group_name)
91 response = app.get(url)
91 response = app.get(url)
92
92
93 assert response.status_code == 200
93 assert response.status_code == 200
94 assert 'exist yet' in response.body
94 assert 'exist yet' in response.body
95
95
96 def test_index_with_integrations(self, app, test_repo_group,
96 def test_index_with_integrations(self, app, test_repo_group,
97 repogroup_integration_stub):
97 repogroup_integration_stub):
98 url = '/{repo_group_name}/settings/integrations'.format(
98 url = '/{repo_group_name}/settings/integrations'.format(
99 repo_group_name=test_repo_group.group_name)
99 repo_group_name=test_repo_group.group_name)
100
100
101 stub_name = repogroup_integration_stub.name
101 stub_name = repogroup_integration_stub.name
102 response = app.get(url)
102 response = app.get(url)
103
103
104 assert response.status_code == 200
104 assert response.status_code == 200
105 assert 'exist yet' not in response.body
105 assert 'exist yet' not in response.body
106 assert stub_name in response.body
106 assert stub_name in response.body
107
107
108 def test_new_integration_page(self, app, test_repo_group):
108 def test_new_integration_page(self, app, test_repo_group):
109 repo_group_name = test_repo_group.group_name
109 repo_group_name = test_repo_group.group_name
110 url = '/{repo_group_name}/settings/integrations/new'.format(
110 url = '/{repo_group_name}/settings/integrations/new'.format(
111 repo_group_name=test_repo_group.group_name)
111 repo_group_name=test_repo_group.group_name)
112
112
113 response = app.get(url)
113 response = app.get(url)
114
114
115 assert response.status_code == 200
115 assert response.status_code == 200
116
116
117 for integration_key in integration_type_registry:
117 for integration_key in integration_type_registry:
118 nurl = ('/{repo_group_name}/settings/integrations'
118 nurl = ('/{repo_group_name}/settings/integrations'
119 '/{integration}/new').format(
119 '/{integration}/new').format(
120 repo_group_name=repo_group_name,
120 repo_group_name=repo_group_name,
121 integration=integration_key)
121 integration=integration_key)
122
122
123 assert nurl in response.body
123 assert nurl in response.body
124
124
125 @pytest.mark.parametrize(
125 @pytest.mark.parametrize(
126 'IntegrationType', integration_type_registry.values())
126 'IntegrationType', integration_type_registry.values())
127 def test_get_create_integration_page(self, app, test_repo_group,
127 def test_get_create_integration_page(self, app, test_repo_group,
128 IntegrationType):
128 IntegrationType):
129 repo_group_name = test_repo_group.group_name
129 repo_group_name = test_repo_group.group_name
130 url = ('/{repo_group_name}/settings/integrations/{integration_key}/new'
130 url = ('/{repo_group_name}/settings/integrations/{integration_key}/new'
131 ).format(repo_group_name=repo_group_name,
131 ).format(repo_group_name=repo_group_name,
132 integration_key=IntegrationType.key)
132 integration_key=IntegrationType.key)
133
133
134 response = app.get(url)
134 response = app.get(url)
135
135
136 assert response.status_code == 200
136 assert response.status_code == 200
137 assert IntegrationType.display_name in response.body
137 assert IntegrationType.display_name in response.body
138
138
139 def test_post_integration_page(self, app, test_repo_group, backend_random,
139 def test_post_integration_page(self, app, test_repo_group, backend_random,
140 StubIntegrationType, csrf_token):
140 StubIntegrationType, csrf_token):
141 repo_group_name = test_repo_group.group_name
141 repo_group_name = test_repo_group.group_name
142 url = ('/{repo_group_name}/settings/integrations/{integration_key}/new'
142 url = ('/{repo_group_name}/settings/integrations/{integration_key}/new'
143 ).format(repo_group_name=repo_group_name,
143 ).format(repo_group_name=repo_group_name,
144 integration_key=StubIntegrationType.key)
144 integration_key=StubIntegrationType.key)
145
145
146 _post_integration_test_helper(app, url, csrf_token, admin_view=False,
146 _post_integration_test_helper(app, url, csrf_token, admin_view=False,
147 repo=backend_random.repo, repo_group=test_repo_group)
147 repo=backend_random.repo, repo_group=test_repo_group)
148
148
149
149
150 class TestRepoIntegrationsView(TestIntegrationsView):
150 class TestRepoIntegrationsView(TestIntegrationsView):
151 def test_index_no_integrations(self, app, backend_random):
151 def test_index_no_integrations(self, app, backend_random):
152 url = '/{repo_name}/settings/integrations'.format(
152 url = '/{repo_name}/settings/integrations'.format(
153 repo_name=backend_random.repo.repo_name)
153 repo_name=backend_random.repo.repo_name)
154 response = app.get(url)
154 response = app.get(url)
155
155
156 assert response.status_code == 200
156 assert response.status_code == 200
157 assert 'exist yet' in response.body
157 assert 'exist yet' in response.body
158
158
159 def test_index_with_integrations(self, app, repo_integration_stub):
159 def test_index_with_integrations(self, app, repo_integration_stub):
160 url = '/{repo_name}/settings/integrations'.format(
160 url = '/{repo_name}/settings/integrations'.format(
161 repo_name=repo_integration_stub.repo.repo_name)
161 repo_name=repo_integration_stub.repo.repo_name)
162 stub_name = repo_integration_stub.name
162 stub_name = repo_integration_stub.name
163
163
164 response = app.get(url)
164 response = app.get(url)
165
165
166 assert response.status_code == 200
166 assert response.status_code == 200
167 assert stub_name in response.body
167 assert stub_name in response.body
168 assert 'exist yet' not in response.body
168 assert 'exist yet' not in response.body
169
169
170 def test_new_integration_page(self, app, backend_random):
170 def test_new_integration_page(self, app, backend_random):
171 repo_name = backend_random.repo.repo_name
171 repo_name = backend_random.repo.repo_name
172 url = '/{repo_name}/settings/integrations/new'.format(
172 url = '/{repo_name}/settings/integrations/new'.format(
173 repo_name=repo_name)
173 repo_name=repo_name)
174
174
175 response = app.get(url)
175 response = app.get(url)
176
176
177 assert response.status_code == 200
177 assert response.status_code == 200
178
178
179 for integration_key in integration_type_registry:
179 for integration_key in integration_type_registry:
180 nurl = ('/{repo_name}/settings/integrations'
180 nurl = ('/{repo_name}/settings/integrations'
181 '/{integration}/new').format(
181 '/{integration}/new').format(
182 repo_name=repo_name,
182 repo_name=repo_name,
183 integration=integration_key)
183 integration=integration_key)
184
184
185 assert nurl in response.body
185 assert nurl in response.body
186
186
187 @pytest.mark.parametrize(
187 @pytest.mark.parametrize(
188 'IntegrationType', integration_type_registry.values())
188 'IntegrationType', integration_type_registry.values())
189 def test_get_create_integration_page(self, app, backend_random,
189 def test_get_create_integration_page(self, app, backend_random,
190 IntegrationType):
190 IntegrationType):
191 repo_name = backend_random.repo.repo_name
191 repo_name = backend_random.repo.repo_name
192 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
192 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
193 repo_name=repo_name, integration_key=IntegrationType.key)
193 repo_name=repo_name, integration_key=IntegrationType.key)
194
194
195 response = app.get(url)
195 response = app.get(url)
196
196
197 assert response.status_code == 200
197 assert response.status_code == 200
198 assert IntegrationType.display_name in response.body
198 assert IntegrationType.display_name in response.body
199
199
200 def test_post_integration_page(self, app, backend_random, test_repo_group,
200 def test_post_integration_page(self, app, backend_random, test_repo_group,
201 StubIntegrationType, csrf_token):
201 StubIntegrationType, csrf_token):
202 repo_name = backend_random.repo.repo_name
202 repo_name = backend_random.repo.repo_name
203 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
203 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
204 repo_name=repo_name, integration_key=StubIntegrationType.key)
204 repo_name=repo_name, integration_key=StubIntegrationType.key)
205
205
206 _post_integration_test_helper(app, url, csrf_token, admin_view=False,
206 _post_integration_test_helper(app, url, csrf_token, admin_view=False,
207 repo=backend_random.repo, repo_group=test_repo_group)
207 repo=backend_random.repo, repo_group=test_repo_group)
208
208
209
209
210 def _post_integration_test_helper(app, url, csrf_token, repo, repo_group,
210 def _post_integration_test_helper(app, url, csrf_token, repo, repo_group,
211 admin_view):
211 admin_view):
212 """
212 """
213 Posts form data to create integration at the url given then deletes it and
213 Posts form data to create integration at the url given then deletes it and
214 checks if the redirect url is correct.
214 checks if the redirect url is correct.
215 """
215 """
216
216
217 app.post(url, params={}, status=403) # missing csrf check
217 app.post(url, params={}, status=403) # missing csrf check
218 response = app.post(url, params={'csrf_token': csrf_token})
218 response = app.post(url, params={'csrf_token': csrf_token})
219 assert response.status_code == 200
219 assert response.status_code == 200
220 assert 'Errors exist' in response.body
220 assert 'Errors exist' in response.body
221
221
222 scopes_destinations = [
222 scopes_destinations = [
223 ('global',
223 ('global',
224 ADMIN_PREFIX + '/integrations'),
224 ADMIN_PREFIX + '/integrations'),
225 ('root_repos',
225 ('root-repos',
226 ADMIN_PREFIX + '/integrations'),
226 ADMIN_PREFIX + '/integrations'),
227 ('repo:%s' % repo.repo_name,
227 ('repo:%s' % repo.repo_name,
228 '/%s/settings/integrations' % repo.repo_name),
228 '/%s/settings/integrations' % repo.repo_name),
229 ('repogroup:%s' % repo_group.group_name,
229 ('repogroup:%s' % repo_group.group_name,
230 '/%s/settings/integrations' % repo_group.group_name),
230 '/%s/settings/integrations' % repo_group.group_name),
231 ('repogroup-recursive:%s' % repo_group.group_name,
232 '/%s/settings/integrations' % repo_group.group_name),
231 ]
233 ]
232
234
233 for scope, destination in scopes_destinations:
235 for scope, destination in scopes_destinations:
234 if admin_view:
236 if admin_view:
235 destination = ADMIN_PREFIX + '/integrations'
237 destination = ADMIN_PREFIX + '/integrations'
236
238
237 form_data = [
239 form_data = [
238 ('csrf_token', csrf_token),
240 ('csrf_token', csrf_token),
239 ('__start__', 'options:mapping'),
241 ('__start__', 'options:mapping'),
240 ('name', 'test integration'),
242 ('name', 'test integration'),
241 ('scope', scope),
243 ('scope', scope),
242 ('enabled', 'true'),
244 ('enabled', 'true'),
243 ('__end__', 'options:mapping'),
245 ('__end__', 'options:mapping'),
244 ('__start__', 'settings:mapping'),
246 ('__start__', 'settings:mapping'),
245 ('test_int_field', '34'),
247 ('test_int_field', '34'),
246 ('test_string_field', ''), # empty value on purpose as it's required
248 ('test_string_field', ''), # empty value on purpose as it's required
247 ('__end__', 'settings:mapping'),
249 ('__end__', 'settings:mapping'),
248 ]
250 ]
249 errors_response = app.post(url, form_data)
251 errors_response = app.post(url, form_data)
250 assert 'Errors exist' in errors_response.body
252 assert 'Errors exist' in errors_response.body
251
253
252 form_data[-2] = ('test_string_field', 'data!')
254 form_data[-2] = ('test_string_field', 'data!')
253 assert Session().query(Integration).count() == 0
255 assert Session().query(Integration).count() == 0
254 created_response = app.post(url, form_data)
256 created_response = app.post(url, form_data)
255 assert Session().query(Integration).count() == 1
257 assert Session().query(Integration).count() == 1
256
258
257 delete_response = app.post(
259 delete_response = app.post(
258 created_response.location,
260 created_response.location,
259 params={'csrf_token': csrf_token, 'delete': 'delete'})
261 params={'csrf_token': csrf_token, 'delete': 'delete'})
260
262
261 assert Session().query(Integration).count() == 0
263 assert Session().query(Integration).count() == 0
262 assert delete_response.location.endswith(destination)
264 assert delete_response.location.endswith(destination)
@@ -1,192 +1,222 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 time
21 import time
22 import pytest
22 import pytest
23
23
24 from rhodecode import events
24 from rhodecode import events
25 from rhodecode.tests.fixture import Fixture
25 from rhodecode.tests.fixture import Fixture
26 from rhodecode.model.db import Session, Integration
26 from rhodecode.model.db import Session, Integration
27 from rhodecode.model.integration import IntegrationModel
27 from rhodecode.model.integration import IntegrationModel
28 from rhodecode.integrations.types.base import IntegrationTypeBase
28 from rhodecode.integrations.types.base import IntegrationTypeBase
29
29
30
30
31 class TestDeleteScopesDeletesIntegrations(object):
31 class TestDeleteScopesDeletesIntegrations(object):
32 def test_delete_repo_with_integration_deletes_integration(self,
32 def test_delete_repo_with_integration_deletes_integration(self,
33 repo_integration_stub):
33 repo_integration_stub):
34 Session().delete(repo_integration_stub.repo)
34 Session().delete(repo_integration_stub.repo)
35 Session().commit()
35 Session().commit()
36 Session().expire_all()
36 Session().expire_all()
37 integration = Integration.get(repo_integration_stub.integration_id)
37 integration = Integration.get(repo_integration_stub.integration_id)
38 assert integration is None
38 assert integration is None
39
39
40
40
41 def test_delete_repo_group_with_integration_deletes_integration(self,
41 def test_delete_repo_group_with_integration_deletes_integration(self,
42 repogroup_integration_stub):
42 repogroup_integration_stub):
43 Session().delete(repogroup_integration_stub.repo_group)
43 Session().delete(repogroup_integration_stub.repo_group)
44 Session().commit()
44 Session().commit()
45 Session().expire_all()
45 Session().expire_all()
46 integration = Integration.get(repogroup_integration_stub.integration_id)
46 integration = Integration.get(repogroup_integration_stub.integration_id)
47 assert integration is None
47 assert integration is None
48
48
49
49
50 @pytest.fixture
50 @pytest.fixture
51 def integration_repos(request, StubIntegrationType, stub_integration_settings):
51 def integration_repos(request, StubIntegrationType, stub_integration_settings):
52 """
52 """
53 Create repositories and integrations for testing, and destroy them after
53 Create repositories and integrations for testing, and destroy them after
54
55 Structure:
56 root_repo
57 parent_group/
58 parent_repo
59 child_group/
60 child_repo
61 other_group/
62 other_repo
54 """
63 """
55 fixture = Fixture()
64 fixture = Fixture()
56
65
57 repo_group_1_id = 'int_test_repo_group_1_%s' % time.time()
66
58 repo_group_1 = fixture.create_repo_group(repo_group_1_id)
67 parent_group_id = 'int_test_parent_group_%s' % time.time()
59 repo_group_2_id = 'int_test_repo_group_2_%s' % time.time()
68 parent_group = fixture.create_repo_group(parent_group_id)
60 repo_group_2 = fixture.create_repo_group(repo_group_2_id)
69
70 other_group_id = 'int_test_other_group_%s' % time.time()
71 other_group = fixture.create_repo_group(other_group_id)
61
72
62 repo_1_id = 'int_test_repo_1_%s' % time.time()
73 child_group_id = (
63 repo_1 = fixture.create_repo(repo_1_id, repo_group=repo_group_1)
74 parent_group_id + '/' + 'int_test_child_group_%s' % time.time())
64 repo_2_id = 'int_test_repo_2_%s' % time.time()
75 child_group = fixture.create_repo_group(child_group_id)
65 repo_2 = fixture.create_repo(repo_2_id, repo_group=repo_group_2)
76
77 parent_repo_id = 'int_test_parent_repo_%s' % time.time()
78 parent_repo = fixture.create_repo(parent_repo_id, repo_group=parent_group)
79
80 child_repo_id = 'int_test_child_repo_%s' % time.time()
81 child_repo = fixture.create_repo(child_repo_id, repo_group=child_group)
82
83 other_repo_id = 'int_test_other_repo_%s' % time.time()
84 other_repo = fixture.create_repo(other_repo_id, repo_group=other_group)
66
85
67 root_repo_id = 'int_test_repo_root_%s' % time.time()
86 root_repo_id = 'int_test_repo_root_%s' % time.time()
68 root_repo = fixture.create_repo(root_repo_id)
87 root_repo = fixture.create_repo(root_repo_id)
69
88
70 integration_global = IntegrationModel().create(
89 integrations = {}
71 StubIntegrationType, settings=stub_integration_settings,
90 for name, repo, repo_group, child_repos_only in [
72 enabled=True, name='test global integration', scope='global')
91 ('global', None, None, None),
73 integration_root_repos = IntegrationModel().create(
92 ('root_repos', None, None, True),
74 StubIntegrationType, settings=stub_integration_settings,
93 ('parent_repo', parent_repo, None, None),
75 enabled=True, name='test root repos integration', scope='root_repos')
94 ('child_repo', child_repo, None, None),
76 integration_repo_1 = IntegrationModel().create(
95 ('other_repo', other_repo, None, None),
77 StubIntegrationType, settings=stub_integration_settings,
96 ('root_repo', root_repo, None, None),
78 enabled=True, name='test repo 1 integration', scope=repo_1)
97 ('parent_group', None, parent_group, True),
79 integration_repo_group_1 = IntegrationModel().create(
98 ('parent_group_recursive', None, parent_group, False),
80 StubIntegrationType, settings=stub_integration_settings,
99 ('child_group', None, child_group, True),
81 enabled=True, name='test repo group 1 integration', scope=repo_group_1)
100 ('child_group_recursive', None, child_group, False),
82 integration_repo_2 = IntegrationModel().create(
101 ('other_group', None, other_group, True),
83 StubIntegrationType, settings=stub_integration_settings,
102 ('other_group_recursive', None, other_group, False),
84 enabled=True, name='test repo 2 integration', scope=repo_2)
103 ]:
85 integration_repo_group_2 = IntegrationModel().create(
104 integrations[name] = IntegrationModel().create(
86 StubIntegrationType, settings=stub_integration_settings,
105 StubIntegrationType, settings=stub_integration_settings,
87 enabled=True, name='test repo group 2 integration', scope=repo_group_2)
106 enabled=True, name='test %s integration' % name,
107 repo=repo, repo_group=repo_group, child_repos_only=child_repos_only)
88
108
89 Session().commit()
109 Session().commit()
90
110
91 def _cleanup():
111 def _cleanup():
92 Session().delete(integration_global)
112 for integration in integrations.values():
93 Session().delete(integration_root_repos)
113 Session.delete(integration)
94 Session().delete(integration_repo_1)
114
95 Session().delete(integration_repo_group_1)
96 Session().delete(integration_repo_2)
97 Session().delete(integration_repo_group_2)
98 fixture.destroy_repo(root_repo)
115 fixture.destroy_repo(root_repo)
99 fixture.destroy_repo(repo_1)
116 fixture.destroy_repo(child_repo)
100 fixture.destroy_repo(repo_2)
117 fixture.destroy_repo(parent_repo)
101 fixture.destroy_repo_group(repo_group_1)
118 fixture.destroy_repo(other_repo)
102 fixture.destroy_repo_group(repo_group_2)
119 fixture.destroy_repo_group(child_group)
120 fixture.destroy_repo_group(parent_group)
121 fixture.destroy_repo_group(other_group)
103
122
104 request.addfinalizer(_cleanup)
123 request.addfinalizer(_cleanup)
105
124
106 return {
125 return {
126 'integrations': integrations,
107 'repos': {
127 'repos': {
108 'repo_1': repo_1,
109 'repo_2': repo_2,
110 'root_repo': root_repo,
128 'root_repo': root_repo,
111 },
129 'other_repo': other_repo,
112 'repo_groups': {
130 'parent_repo': parent_repo,
113 'repo_group_1': repo_group_1,
131 'child_repo': child_repo,
114 'repo_group_2': repo_group_2,
115 },
116 'integrations': {
117 'global': integration_global,
118 'root_repos': integration_root_repos,
119 'repo_1': integration_repo_1,
120 'repo_2': integration_repo_2,
121 'repo_group_1': integration_repo_group_1,
122 'repo_group_2': integration_repo_group_2,
123 }
132 }
124 }
133 }
125
134
126
135
127 def test_enabled_integration_repo_scopes(integration_repos):
136 def test_enabled_integration_repo_scopes(integration_repos):
128 integrations = integration_repos['integrations']
137 integrations = integration_repos['integrations']
129 repos = integration_repos['repos']
138 repos = integration_repos['repos']
130
139
131 triggered_integrations = IntegrationModel().get_for_event(
140 triggered_integrations = IntegrationModel().get_for_event(
132 events.RepoEvent(repos['root_repo']))
141 events.RepoEvent(repos['root_repo']))
133
142
134 assert triggered_integrations == [
143 assert triggered_integrations == [
135 integrations['global'],
144 integrations['global'],
136 integrations['root_repos']
145 integrations['root_repos'],
146 integrations['root_repo'],
147 ]
148
149
150 triggered_integrations = IntegrationModel().get_for_event(
151 events.RepoEvent(repos['other_repo']))
152
153 assert triggered_integrations == [
154 integrations['global'],
155 integrations['other_repo'],
156 integrations['other_group'],
157 integrations['other_group_recursive'],
137 ]
158 ]
138
159
139
160
140 triggered_integrations = IntegrationModel().get_for_event(
161 triggered_integrations = IntegrationModel().get_for_event(
141 events.RepoEvent(repos['repo_1']))
162 events.RepoEvent(repos['parent_repo']))
142
163
143 assert triggered_integrations == [
164 assert triggered_integrations == [
144 integrations['global'],
165 integrations['global'],
145 integrations['repo_1'],
166 integrations['parent_repo'],
146 integrations['repo_group_1']
167 integrations['parent_group'],
168 integrations['parent_group_recursive'],
147 ]
169 ]
148
170
149
150 triggered_integrations = IntegrationModel().get_for_event(
171 triggered_integrations = IntegrationModel().get_for_event(
151 events.RepoEvent(repos['repo_2']))
172 events.RepoEvent(repos['child_repo']))
152
173
153 assert triggered_integrations == [
174 assert triggered_integrations == [
154 integrations['global'],
175 integrations['global'],
155 integrations['repo_2'],
176 integrations['child_repo'],
156 integrations['repo_group_2'],
177 integrations['parent_group_recursive'],
178 integrations['child_group'],
179 integrations['child_group_recursive'],
157 ]
180 ]
158
181
159
182
160 def test_disabled_integration_repo_scopes(integration_repos):
183 def test_disabled_integration_repo_scopes(integration_repos):
161 integrations = integration_repos['integrations']
184 integrations = integration_repos['integrations']
162 repos = integration_repos['repos']
185 repos = integration_repos['repos']
163
186
164 for integration in integrations.values():
187 for integration in integrations.values():
165 integration.enabled = False
188 integration.enabled = False
166 Session().commit()
189 Session().commit()
167
190
168 triggered_integrations = IntegrationModel().get_for_event(
191 triggered_integrations = IntegrationModel().get_for_event(
169 events.RepoEvent(repos['root_repo']))
192 events.RepoEvent(repos['root_repo']))
170
193
171 assert triggered_integrations == []
194 assert triggered_integrations == []
172
195
173
196
174 triggered_integrations = IntegrationModel().get_for_event(
197 triggered_integrations = IntegrationModel().get_for_event(
175 events.RepoEvent(repos['repo_1']))
198 events.RepoEvent(repos['parent_repo']))
176
199
177 assert triggered_integrations == []
200 assert triggered_integrations == []
178
201
179
202
180 triggered_integrations = IntegrationModel().get_for_event(
203 triggered_integrations = IntegrationModel().get_for_event(
181 events.RepoEvent(repos['repo_2']))
204 events.RepoEvent(repos['child_repo']))
182
205
183 assert triggered_integrations == []
206 assert triggered_integrations == []
184
207
185
208
209 triggered_integrations = IntegrationModel().get_for_event(
210 events.RepoEvent(repos['other_repo']))
211
212 assert triggered_integrations == []
213
214
215
186 def test_enabled_non_repo_integrations(integration_repos):
216 def test_enabled_non_repo_integrations(integration_repos):
187 integrations = integration_repos['integrations']
217 integrations = integration_repos['integrations']
188
218
189 triggered_integrations = IntegrationModel().get_for_event(
219 triggered_integrations = IntegrationModel().get_for_event(
190 events.UserPreCreate({}))
220 events.UserPreCreate({}))
191
221
192 assert triggered_integrations == [integrations['global']]
222 assert triggered_integrations == [integrations['global']]
@@ -1,120 +1,171 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-2016 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 colander
21 import colander
22 import pytest
22 import pytest
23
23
24 from rhodecode.model import validation_schema
24 from rhodecode.model import validation_schema
25
25
26 from rhodecode.integrations import integration_type_registry
26 from rhodecode.integrations import integration_type_registry
27 from rhodecode.integrations.types.base import IntegrationTypeBase
27 from rhodecode.integrations.types.base import IntegrationTypeBase
28 from rhodecode.model.validation_schema.schemas.integration_schema import (
28 from rhodecode.model.validation_schema.schemas.integration_schema import (
29 make_integration_schema
29 make_integration_schema
30 )
30 )
31
31
32
32
33 @pytest.mark.usefixtures('app', 'autologin_user')
33 @pytest.mark.usefixtures('app', 'autologin_user')
34 class TestIntegrationSchema(object):
34 class TestIntegrationSchema(object):
35
35
36 def test_deserialize_integration_schema_perms(self, backend_random,
36 def test_deserialize_integration_schema_perms(self, backend_random,
37 test_repo_group,
37 test_repo_group,
38 StubIntegrationType):
38 StubIntegrationType):
39
39
40 repo = backend_random.repo
40 repo = backend_random.repo
41 repo_group = test_repo_group
41 repo_group = test_repo_group
42
42
43
43
44 empty_perms_dict = {
44 empty_perms_dict = {
45 'global': [],
45 'global': [],
46 'repositories': {},
46 'repositories': {},
47 'repositories_groups': {},
47 'repositories_groups': {},
48 }
48 }
49
49
50 perms_tests = {
50 perms_tests = [
51 ('repo:%s' % repo.repo_name, repo): [
51 (
52 ({}, False),
52 'repo:%s' % repo.repo_name,
53 ({'global': ['hg.admin']}, True),
53 {
54 ({'global': []}, False),
54 'child_repos_only': None,
55 ({'repositories': {repo.repo_name: 'repository.admin'}}, True),
55 'repo_group': None,
56 ({'repositories': {repo.repo_name: 'repository.read'}}, False),
56 'repo': repo,
57 ({'repositories': {repo.repo_name: 'repository.write'}}, False),
57 },
58 ({'repositories': {repo.repo_name: 'repository.none'}}, False),
58 [
59 ],
59 ({}, False),
60 ('repogroup:%s' % repo_group.group_name, repo_group): [
60 ({'global': ['hg.admin']}, True),
61 ({}, False),
61 ({'global': []}, False),
62 ({'global': ['hg.admin']}, True),
62 ({'repositories': {repo.repo_name: 'repository.admin'}}, True),
63 ({'global': []}, False),
63 ({'repositories': {repo.repo_name: 'repository.read'}}, False),
64 ({'repositories_groups':
64 ({'repositories': {repo.repo_name: 'repository.write'}}, False),
65 {repo_group.group_name: 'group.admin'}}, True),
65 ({'repositories': {repo.repo_name: 'repository.none'}}, False),
66 ({'repositories_groups':
66 ]
67 {repo_group.group_name: 'group.read'}}, False),
67 ),
68 ({'repositories_groups':
68 (
69 {repo_group.group_name: 'group.write'}}, False),
69 'repogroup:%s' % repo_group.group_name,
70 ({'repositories_groups':
70 {
71 {repo_group.group_name: 'group.none'}}, False),
71 'repo': None,
72 ],
72 'repo_group': repo_group,
73 ('global', 'global'): [
73 'child_repos_only': True,
74 ({}, False),
74 },
75 ({'global': ['hg.admin']}, True),
75 [
76 ({'global': []}, False),
76 ({}, False),
77 ],
77 ({'global': ['hg.admin']}, True),
78 ('root_repos', 'root_repos'): [
78 ({'global': []}, False),
79 ({}, False),
79 ({'repositories_groups':
80 ({'global': ['hg.admin']}, True),
80 {repo_group.group_name: 'group.admin'}}, True),
81 ({'global': []}, False),
81 ({'repositories_groups':
82 ],
82 {repo_group.group_name: 'group.read'}}, False),
83 }
83 ({'repositories_groups':
84 {repo_group.group_name: 'group.write'}}, False),
85 ({'repositories_groups':
86 {repo_group.group_name: 'group.none'}}, False),
87 ]
88 ),
89 (
90 'repogroup-recursive:%s' % repo_group.group_name,
91 {
92 'repo': None,
93 'repo_group': repo_group,
94 'child_repos_only': False,
95 },
96 [
97 ({}, False),
98 ({'global': ['hg.admin']}, True),
99 ({'global': []}, False),
100 ({'repositories_groups':
101 {repo_group.group_name: 'group.admin'}}, True),
102 ({'repositories_groups':
103 {repo_group.group_name: 'group.read'}}, False),
104 ({'repositories_groups':
105 {repo_group.group_name: 'group.write'}}, False),
106 ({'repositories_groups':
107 {repo_group.group_name: 'group.none'}}, False),
108 ]
109 ),
110 (
111 'global',
112 {
113 'repo': None,
114 'repo_group': None,
115 'child_repos_only': False,
116 }, [
117 ({}, False),
118 ({'global': ['hg.admin']}, True),
119 ({'global': []}, False),
120 ]
121 ),
122 (
123 'root-repos',
124 {
125 'repo': None,
126 'repo_group': None,
127 'child_repos_only': True,
128 }, [
129 ({}, False),
130 ({'global': ['hg.admin']}, True),
131 ({'global': []}, False),
132 ]
133 ),
134 ]
84
135
85 for (scope_input, scope_output), perms_allowed in perms_tests.items():
136 for scope_input, scope_output, perms_allowed in perms_tests:
86 for perms_update, allowed in perms_allowed:
137 for perms_update, allowed in perms_allowed:
87 perms = dict(empty_perms_dict, **perms_update)
138 perms = dict(empty_perms_dict, **perms_update)
88
139
89 schema = make_integration_schema(
140 schema = make_integration_schema(
90 IntegrationType=StubIntegrationType
141 IntegrationType=StubIntegrationType
91 ).bind(permissions=perms)
142 ).bind(permissions=perms)
92
143
93 input_data = {
144 input_data = {
94 'options': {
145 'options': {
95 'enabled': 'true',
146 'enabled': 'true',
96 'scope': scope_input,
147 'scope': scope_input,
97 'name': 'test integration',
148 'name': 'test integration',
98 },
149 },
99 'settings': {
150 'settings': {
100 'test_string_field': 'stringy',
151 'test_string_field': 'stringy',
101 'test_int_field': '100',
152 'test_int_field': '100',
102 }
153 }
103 }
154 }
104
155
105 if not allowed:
156 if not allowed:
106 with pytest.raises(colander.Invalid):
157 with pytest.raises(colander.Invalid):
107 schema.deserialize(input_data)
158 schema.deserialize(input_data)
108 else:
159 else:
109 assert schema.deserialize(input_data) == {
160 assert schema.deserialize(input_data) == {
110 'options': {
161 'options': {
111 'enabled': True,
162 'enabled': True,
112 'scope': scope_output,
163 'scope': scope_output,
113 'name': 'test integration',
164 'name': 'test integration',
114 },
165 },
115 'settings': {
166 'settings': {
116 'test_string_field': 'stringy',
167 'test_string_field': 'stringy',
117 'test_int_field': 100,
168 'test_int_field': 100,
118 }
169 }
119 }
170 }
120
171
@@ -1,1760 +1,1779 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 subprocess
29 import subprocess
30 import time
30 import time
31 import uuid
31 import uuid
32
32
33 import mock
33 import mock
34 import pyramid.testing
34 import pyramid.testing
35 import pytest
35 import pytest
36 import colander
36 import colander
37 import requests
37 import requests
38 from webtest.app import TestApp
38 from webtest.app import TestApp
39
39
40 import rhodecode
40 import rhodecode
41 from rhodecode.model.changeset_status import ChangesetStatusModel
41 from rhodecode.model.changeset_status import ChangesetStatusModel
42 from rhodecode.model.comment import ChangesetCommentsModel
42 from rhodecode.model.comment import ChangesetCommentsModel
43 from rhodecode.model.db import (
43 from rhodecode.model.db import (
44 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
44 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
45 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, Integration)
45 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, Integration)
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.pull_request import PullRequestModel
47 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo_group import RepoGroupModel
49 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.settings import VcsSettingsModel
51 from rhodecode.model.settings import VcsSettingsModel
52 from rhodecode.model.user_group import UserGroupModel
52 from rhodecode.model.user_group import UserGroupModel
53 from rhodecode.model.integration import IntegrationModel
53 from rhodecode.model.integration import IntegrationModel
54 from rhodecode.integrations import integration_type_registry
54 from rhodecode.integrations import integration_type_registry
55 from rhodecode.integrations.types.base import IntegrationTypeBase
55 from rhodecode.integrations.types.base import IntegrationTypeBase
56 from rhodecode.lib.utils import repo2db_mapper
56 from rhodecode.lib.utils import repo2db_mapper
57 from rhodecode.lib.vcs import create_vcsserver_proxy
57 from rhodecode.lib.vcs import create_vcsserver_proxy
58 from rhodecode.lib.vcs.backends import get_backend
58 from rhodecode.lib.vcs.backends import get_backend
59 from rhodecode.lib.vcs.nodes import FileNode
59 from rhodecode.lib.vcs.nodes import FileNode
60 from rhodecode.tests import (
60 from rhodecode.tests import (
61 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
61 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
62 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
62 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
63 TEST_USER_REGULAR_PASS)
63 TEST_USER_REGULAR_PASS)
64 from rhodecode.tests.fixture import Fixture
64 from rhodecode.tests.fixture import Fixture
65
65
66
66
67 def _split_comma(value):
67 def _split_comma(value):
68 return value.split(',')
68 return value.split(',')
69
69
70
70
71 def pytest_addoption(parser):
71 def pytest_addoption(parser):
72 parser.addoption(
72 parser.addoption(
73 '--keep-tmp-path', action='store_true',
73 '--keep-tmp-path', action='store_true',
74 help="Keep the test temporary directories")
74 help="Keep the test temporary directories")
75 parser.addoption(
75 parser.addoption(
76 '--backends', action='store', type=_split_comma,
76 '--backends', action='store', type=_split_comma,
77 default=['git', 'hg', 'svn'],
77 default=['git', 'hg', 'svn'],
78 help="Select which backends to test for backend specific tests.")
78 help="Select which backends to test for backend specific tests.")
79 parser.addoption(
79 parser.addoption(
80 '--dbs', action='store', type=_split_comma,
80 '--dbs', action='store', type=_split_comma,
81 default=['sqlite'],
81 default=['sqlite'],
82 help="Select which database to test for database specific tests. "
82 help="Select which database to test for database specific tests. "
83 "Possible options are sqlite,postgres,mysql")
83 "Possible options are sqlite,postgres,mysql")
84 parser.addoption(
84 parser.addoption(
85 '--appenlight', '--ae', action='store_true',
85 '--appenlight', '--ae', action='store_true',
86 help="Track statistics in appenlight.")
86 help="Track statistics in appenlight.")
87 parser.addoption(
87 parser.addoption(
88 '--appenlight-api-key', '--ae-key',
88 '--appenlight-api-key', '--ae-key',
89 help="API key for Appenlight.")
89 help="API key for Appenlight.")
90 parser.addoption(
90 parser.addoption(
91 '--appenlight-url', '--ae-url',
91 '--appenlight-url', '--ae-url',
92 default="https://ae.rhodecode.com",
92 default="https://ae.rhodecode.com",
93 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
93 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
94 parser.addoption(
94 parser.addoption(
95 '--sqlite-connection-string', action='store',
95 '--sqlite-connection-string', action='store',
96 default='', help="Connection string for the dbs tests with SQLite")
96 default='', help="Connection string for the dbs tests with SQLite")
97 parser.addoption(
97 parser.addoption(
98 '--postgres-connection-string', action='store',
98 '--postgres-connection-string', action='store',
99 default='', help="Connection string for the dbs tests with Postgres")
99 default='', help="Connection string for the dbs tests with Postgres")
100 parser.addoption(
100 parser.addoption(
101 '--mysql-connection-string', action='store',
101 '--mysql-connection-string', action='store',
102 default='', help="Connection string for the dbs tests with MySQL")
102 default='', help="Connection string for the dbs tests with MySQL")
103 parser.addoption(
103 parser.addoption(
104 '--repeat', type=int, default=100,
104 '--repeat', type=int, default=100,
105 help="Number of repetitions in performance tests.")
105 help="Number of repetitions in performance tests.")
106
106
107
107
108 def pytest_configure(config):
108 def pytest_configure(config):
109 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
109 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
110 from rhodecode.config import patches
110 from rhodecode.config import patches
111 patches.kombu_1_5_1_python_2_7_11()
111 patches.kombu_1_5_1_python_2_7_11()
112
112
113
113
114 def pytest_collection_modifyitems(session, config, items):
114 def pytest_collection_modifyitems(session, config, items):
115 # nottest marked, compare nose, used for transition from nose to pytest
115 # nottest marked, compare nose, used for transition from nose to pytest
116 remaining = [
116 remaining = [
117 i for i in items if getattr(i.obj, '__test__', True)]
117 i for i in items if getattr(i.obj, '__test__', True)]
118 items[:] = remaining
118 items[:] = remaining
119
119
120
120
121 def pytest_generate_tests(metafunc):
121 def pytest_generate_tests(metafunc):
122 # Support test generation based on --backend parameter
122 # Support test generation based on --backend parameter
123 if 'backend_alias' in metafunc.fixturenames:
123 if 'backend_alias' in metafunc.fixturenames:
124 backends = get_backends_from_metafunc(metafunc)
124 backends = get_backends_from_metafunc(metafunc)
125 scope = None
125 scope = None
126 if not backends:
126 if not backends:
127 pytest.skip("Not enabled for any of selected backends")
127 pytest.skip("Not enabled for any of selected backends")
128 metafunc.parametrize('backend_alias', backends, scope=scope)
128 metafunc.parametrize('backend_alias', backends, scope=scope)
129 elif hasattr(metafunc.function, 'backends'):
129 elif hasattr(metafunc.function, 'backends'):
130 backends = get_backends_from_metafunc(metafunc)
130 backends = get_backends_from_metafunc(metafunc)
131 if not backends:
131 if not backends:
132 pytest.skip("Not enabled for any of selected backends")
132 pytest.skip("Not enabled for any of selected backends")
133
133
134
134
135 def get_backends_from_metafunc(metafunc):
135 def get_backends_from_metafunc(metafunc):
136 requested_backends = set(metafunc.config.getoption('--backends'))
136 requested_backends = set(metafunc.config.getoption('--backends'))
137 if hasattr(metafunc.function, 'backends'):
137 if hasattr(metafunc.function, 'backends'):
138 # Supported backends by this test function, created from
138 # Supported backends by this test function, created from
139 # pytest.mark.backends
139 # pytest.mark.backends
140 backends = metafunc.function.backends.args
140 backends = metafunc.function.backends.args
141 elif hasattr(metafunc.cls, 'backend_alias'):
141 elif hasattr(metafunc.cls, 'backend_alias'):
142 # Support class attribute "backend_alias", this is mainly
142 # Support class attribute "backend_alias", this is mainly
143 # for legacy reasons for tests not yet using pytest.mark.backends
143 # for legacy reasons for tests not yet using pytest.mark.backends
144 backends = [metafunc.cls.backend_alias]
144 backends = [metafunc.cls.backend_alias]
145 else:
145 else:
146 backends = metafunc.config.getoption('--backends')
146 backends = metafunc.config.getoption('--backends')
147 return requested_backends.intersection(backends)
147 return requested_backends.intersection(backends)
148
148
149
149
150 @pytest.fixture(scope='session', autouse=True)
150 @pytest.fixture(scope='session', autouse=True)
151 def activate_example_rcextensions(request):
151 def activate_example_rcextensions(request):
152 """
152 """
153 Patch in an example rcextensions module which verifies passed in kwargs.
153 Patch in an example rcextensions module which verifies passed in kwargs.
154 """
154 """
155 from rhodecode.tests.other import example_rcextensions
155 from rhodecode.tests.other import example_rcextensions
156
156
157 old_extensions = rhodecode.EXTENSIONS
157 old_extensions = rhodecode.EXTENSIONS
158 rhodecode.EXTENSIONS = example_rcextensions
158 rhodecode.EXTENSIONS = example_rcextensions
159
159
160 @request.addfinalizer
160 @request.addfinalizer
161 def cleanup():
161 def cleanup():
162 rhodecode.EXTENSIONS = old_extensions
162 rhodecode.EXTENSIONS = old_extensions
163
163
164
164
165 @pytest.fixture
165 @pytest.fixture
166 def capture_rcextensions():
166 def capture_rcextensions():
167 """
167 """
168 Returns the recorded calls to entry points in rcextensions.
168 Returns the recorded calls to entry points in rcextensions.
169 """
169 """
170 calls = rhodecode.EXTENSIONS.calls
170 calls = rhodecode.EXTENSIONS.calls
171 calls.clear()
171 calls.clear()
172 # Note: At this moment, it is still the empty dict, but that will
172 # Note: At this moment, it is still the empty dict, but that will
173 # be filled during the test run and since it is a reference this
173 # be filled during the test run and since it is a reference this
174 # is enough to make it work.
174 # is enough to make it work.
175 return calls
175 return calls
176
176
177
177
178 @pytest.fixture(scope='session')
178 @pytest.fixture(scope='session')
179 def http_environ_session():
179 def http_environ_session():
180 """
180 """
181 Allow to use "http_environ" in session scope.
181 Allow to use "http_environ" in session scope.
182 """
182 """
183 return http_environ(
183 return http_environ(
184 http_host_stub=http_host_stub())
184 http_host_stub=http_host_stub())
185
185
186
186
187 @pytest.fixture
187 @pytest.fixture
188 def http_host_stub():
188 def http_host_stub():
189 """
189 """
190 Value of HTTP_HOST in the test run.
190 Value of HTTP_HOST in the test run.
191 """
191 """
192 return 'test.example.com:80'
192 return 'test.example.com:80'
193
193
194
194
195 @pytest.fixture
195 @pytest.fixture
196 def http_environ(http_host_stub):
196 def http_environ(http_host_stub):
197 """
197 """
198 HTTP extra environ keys.
198 HTTP extra environ keys.
199
199
200 User by the test application and as well for setting up the pylons
200 User by the test application and as well for setting up the pylons
201 environment. In the case of the fixture "app" it should be possible
201 environment. In the case of the fixture "app" it should be possible
202 to override this for a specific test case.
202 to override this for a specific test case.
203 """
203 """
204 return {
204 return {
205 'SERVER_NAME': http_host_stub.split(':')[0],
205 'SERVER_NAME': http_host_stub.split(':')[0],
206 'SERVER_PORT': http_host_stub.split(':')[1],
206 'SERVER_PORT': http_host_stub.split(':')[1],
207 'HTTP_HOST': http_host_stub,
207 'HTTP_HOST': http_host_stub,
208 }
208 }
209
209
210
210
211 @pytest.fixture(scope='function')
211 @pytest.fixture(scope='function')
212 def app(request, pylonsapp, http_environ):
212 def app(request, pylonsapp, http_environ):
213 app = TestApp(
213 app = TestApp(
214 pylonsapp,
214 pylonsapp,
215 extra_environ=http_environ)
215 extra_environ=http_environ)
216 if request.cls:
216 if request.cls:
217 request.cls.app = app
217 request.cls.app = app
218 return app
218 return app
219
219
220
220
221 @pytest.fixture()
221 @pytest.fixture()
222 def app_settings(pylonsapp, pylons_config):
222 def app_settings(pylonsapp, pylons_config):
223 """
223 """
224 Settings dictionary used to create the app.
224 Settings dictionary used to create the app.
225
225
226 Parses the ini file and passes the result through the sanitize and apply
226 Parses the ini file and passes the result through the sanitize and apply
227 defaults mechanism in `rhodecode.config.middleware`.
227 defaults mechanism in `rhodecode.config.middleware`.
228 """
228 """
229 from paste.deploy.loadwsgi import loadcontext, APP
229 from paste.deploy.loadwsgi import loadcontext, APP
230 from rhodecode.config.middleware import (
230 from rhodecode.config.middleware import (
231 sanitize_settings_and_apply_defaults)
231 sanitize_settings_and_apply_defaults)
232 context = loadcontext(APP, 'config:' + pylons_config)
232 context = loadcontext(APP, 'config:' + pylons_config)
233 settings = sanitize_settings_and_apply_defaults(context.config())
233 settings = sanitize_settings_and_apply_defaults(context.config())
234 return settings
234 return settings
235
235
236
236
237 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
237 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
238
238
239
239
240 def _autologin_user(app, *args):
240 def _autologin_user(app, *args):
241 session = login_user_session(app, *args)
241 session = login_user_session(app, *args)
242 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
242 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
243 return LoginData(csrf_token, session['rhodecode_user'])
243 return LoginData(csrf_token, session['rhodecode_user'])
244
244
245
245
246 @pytest.fixture
246 @pytest.fixture
247 def autologin_user(app):
247 def autologin_user(app):
248 """
248 """
249 Utility fixture which makes sure that the admin user is logged in
249 Utility fixture which makes sure that the admin user is logged in
250 """
250 """
251 return _autologin_user(app)
251 return _autologin_user(app)
252
252
253
253
254 @pytest.fixture
254 @pytest.fixture
255 def autologin_regular_user(app):
255 def autologin_regular_user(app):
256 """
256 """
257 Utility fixture which makes sure that the regular user is logged in
257 Utility fixture which makes sure that the regular user is logged in
258 """
258 """
259 return _autologin_user(
259 return _autologin_user(
260 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
260 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
261
261
262
262
263 @pytest.fixture(scope='function')
263 @pytest.fixture(scope='function')
264 def csrf_token(request, autologin_user):
264 def csrf_token(request, autologin_user):
265 return autologin_user.csrf_token
265 return autologin_user.csrf_token
266
266
267
267
268 @pytest.fixture(scope='function')
268 @pytest.fixture(scope='function')
269 def xhr_header(request):
269 def xhr_header(request):
270 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
270 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
271
271
272
272
273 @pytest.fixture
273 @pytest.fixture
274 def real_crypto_backend(monkeypatch):
274 def real_crypto_backend(monkeypatch):
275 """
275 """
276 Switch the production crypto backend on for this test.
276 Switch the production crypto backend on for this test.
277
277
278 During the test run the crypto backend is replaced with a faster
278 During the test run the crypto backend is replaced with a faster
279 implementation based on the MD5 algorithm.
279 implementation based on the MD5 algorithm.
280 """
280 """
281 monkeypatch.setattr(rhodecode, 'is_test', False)
281 monkeypatch.setattr(rhodecode, 'is_test', False)
282
282
283
283
284 @pytest.fixture(scope='class')
284 @pytest.fixture(scope='class')
285 def index_location(request, pylonsapp):
285 def index_location(request, pylonsapp):
286 index_location = pylonsapp.config['app_conf']['search.location']
286 index_location = pylonsapp.config['app_conf']['search.location']
287 if request.cls:
287 if request.cls:
288 request.cls.index_location = index_location
288 request.cls.index_location = index_location
289 return index_location
289 return index_location
290
290
291
291
292 @pytest.fixture(scope='session', autouse=True)
292 @pytest.fixture(scope='session', autouse=True)
293 def tests_tmp_path(request):
293 def tests_tmp_path(request):
294 """
294 """
295 Create temporary directory to be used during the test session.
295 Create temporary directory to be used during the test session.
296 """
296 """
297 if not os.path.exists(TESTS_TMP_PATH):
297 if not os.path.exists(TESTS_TMP_PATH):
298 os.makedirs(TESTS_TMP_PATH)
298 os.makedirs(TESTS_TMP_PATH)
299
299
300 if not request.config.getoption('--keep-tmp-path'):
300 if not request.config.getoption('--keep-tmp-path'):
301 @request.addfinalizer
301 @request.addfinalizer
302 def remove_tmp_path():
302 def remove_tmp_path():
303 shutil.rmtree(TESTS_TMP_PATH)
303 shutil.rmtree(TESTS_TMP_PATH)
304
304
305 return TESTS_TMP_PATH
305 return TESTS_TMP_PATH
306
306
307
307
308 @pytest.fixture(scope='session', autouse=True)
308 @pytest.fixture(scope='session', autouse=True)
309 def patch_pyro_request_scope_proxy_factory(request):
309 def patch_pyro_request_scope_proxy_factory(request):
310 """
310 """
311 Patch the pyro proxy factory to always use the same dummy request object
311 Patch the pyro proxy factory to always use the same dummy request object
312 when under test. This will return the same pyro proxy on every call.
312 when under test. This will return the same pyro proxy on every call.
313 """
313 """
314 dummy_request = pyramid.testing.DummyRequest()
314 dummy_request = pyramid.testing.DummyRequest()
315
315
316 def mocked_call(self, request=None):
316 def mocked_call(self, request=None):
317 return self.getProxy(request=dummy_request)
317 return self.getProxy(request=dummy_request)
318
318
319 patcher = mock.patch(
319 patcher = mock.patch(
320 'rhodecode.lib.vcs.client.RequestScopeProxyFactory.__call__',
320 'rhodecode.lib.vcs.client.RequestScopeProxyFactory.__call__',
321 new=mocked_call)
321 new=mocked_call)
322 patcher.start()
322 patcher.start()
323
323
324 @request.addfinalizer
324 @request.addfinalizer
325 def undo_patching():
325 def undo_patching():
326 patcher.stop()
326 patcher.stop()
327
327
328
328
329 @pytest.fixture
329 @pytest.fixture
330 def test_repo_group(request):
330 def test_repo_group(request):
331 """
331 """
332 Create a temporary repository group, and destroy it after
332 Create a temporary repository group, and destroy it after
333 usage automatically
333 usage automatically
334 """
334 """
335 fixture = Fixture()
335 fixture = Fixture()
336 repogroupid = 'test_repo_group_%s' % int(time.time())
336 repogroupid = 'test_repo_group_%s' % int(time.time())
337 repo_group = fixture.create_repo_group(repogroupid)
337 repo_group = fixture.create_repo_group(repogroupid)
338
338
339 def _cleanup():
339 def _cleanup():
340 fixture.destroy_repo_group(repogroupid)
340 fixture.destroy_repo_group(repogroupid)
341
341
342 request.addfinalizer(_cleanup)
342 request.addfinalizer(_cleanup)
343 return repo_group
343 return repo_group
344
344
345
345
346 @pytest.fixture
346 @pytest.fixture
347 def test_user_group(request):
347 def test_user_group(request):
348 """
348 """
349 Create a temporary user group, and destroy it after
349 Create a temporary user group, and destroy it after
350 usage automatically
350 usage automatically
351 """
351 """
352 fixture = Fixture()
352 fixture = Fixture()
353 usergroupid = 'test_user_group_%s' % int(time.time())
353 usergroupid = 'test_user_group_%s' % int(time.time())
354 user_group = fixture.create_user_group(usergroupid)
354 user_group = fixture.create_user_group(usergroupid)
355
355
356 def _cleanup():
356 def _cleanup():
357 fixture.destroy_user_group(user_group)
357 fixture.destroy_user_group(user_group)
358
358
359 request.addfinalizer(_cleanup)
359 request.addfinalizer(_cleanup)
360 return user_group
360 return user_group
361
361
362
362
363 @pytest.fixture(scope='session')
363 @pytest.fixture(scope='session')
364 def test_repo(request):
364 def test_repo(request):
365 container = TestRepoContainer()
365 container = TestRepoContainer()
366 request.addfinalizer(container._cleanup)
366 request.addfinalizer(container._cleanup)
367 return container
367 return container
368
368
369
369
370 class TestRepoContainer(object):
370 class TestRepoContainer(object):
371 """
371 """
372 Container for test repositories which are used read only.
372 Container for test repositories which are used read only.
373
373
374 Repositories will be created on demand and re-used during the lifetime
374 Repositories will be created on demand and re-used during the lifetime
375 of this object.
375 of this object.
376
376
377 Usage to get the svn test repository "minimal"::
377 Usage to get the svn test repository "minimal"::
378
378
379 test_repo = TestContainer()
379 test_repo = TestContainer()
380 repo = test_repo('minimal', 'svn')
380 repo = test_repo('minimal', 'svn')
381
381
382 """
382 """
383
383
384 dump_extractors = {
384 dump_extractors = {
385 'git': utils.extract_git_repo_from_dump,
385 'git': utils.extract_git_repo_from_dump,
386 'hg': utils.extract_hg_repo_from_dump,
386 'hg': utils.extract_hg_repo_from_dump,
387 'svn': utils.extract_svn_repo_from_dump,
387 'svn': utils.extract_svn_repo_from_dump,
388 }
388 }
389
389
390 def __init__(self):
390 def __init__(self):
391 self._cleanup_repos = []
391 self._cleanup_repos = []
392 self._fixture = Fixture()
392 self._fixture = Fixture()
393 self._repos = {}
393 self._repos = {}
394
394
395 def __call__(self, dump_name, backend_alias):
395 def __call__(self, dump_name, backend_alias):
396 key = (dump_name, backend_alias)
396 key = (dump_name, backend_alias)
397 if key not in self._repos:
397 if key not in self._repos:
398 repo = self._create_repo(dump_name, backend_alias)
398 repo = self._create_repo(dump_name, backend_alias)
399 self._repos[key] = repo.repo_id
399 self._repos[key] = repo.repo_id
400 return Repository.get(self._repos[key])
400 return Repository.get(self._repos[key])
401
401
402 def _create_repo(self, dump_name, backend_alias):
402 def _create_repo(self, dump_name, backend_alias):
403 repo_name = '%s-%s' % (backend_alias, dump_name)
403 repo_name = '%s-%s' % (backend_alias, dump_name)
404 backend_class = get_backend(backend_alias)
404 backend_class = get_backend(backend_alias)
405 dump_extractor = self.dump_extractors[backend_alias]
405 dump_extractor = self.dump_extractors[backend_alias]
406 repo_path = dump_extractor(dump_name, repo_name)
406 repo_path = dump_extractor(dump_name, repo_name)
407 vcs_repo = backend_class(repo_path)
407 vcs_repo = backend_class(repo_path)
408 repo2db_mapper({repo_name: vcs_repo})
408 repo2db_mapper({repo_name: vcs_repo})
409 repo = RepoModel().get_by_repo_name(repo_name)
409 repo = RepoModel().get_by_repo_name(repo_name)
410 self._cleanup_repos.append(repo_name)
410 self._cleanup_repos.append(repo_name)
411 return repo
411 return repo
412
412
413 def _cleanup(self):
413 def _cleanup(self):
414 for repo_name in reversed(self._cleanup_repos):
414 for repo_name in reversed(self._cleanup_repos):
415 self._fixture.destroy_repo(repo_name)
415 self._fixture.destroy_repo(repo_name)
416
416
417
417
418 @pytest.fixture
418 @pytest.fixture
419 def backend(request, backend_alias, pylonsapp, test_repo):
419 def backend(request, backend_alias, pylonsapp, test_repo):
420 """
420 """
421 Parametrized fixture which represents a single backend implementation.
421 Parametrized fixture which represents a single backend implementation.
422
422
423 It respects the option `--backends` to focus the test run on specific
423 It respects the option `--backends` to focus the test run on specific
424 backend implementations.
424 backend implementations.
425
425
426 It also supports `pytest.mark.xfail_backends` to mark tests as failing
426 It also supports `pytest.mark.xfail_backends` to mark tests as failing
427 for specific backends. This is intended as a utility for incremental
427 for specific backends. This is intended as a utility for incremental
428 development of a new backend implementation.
428 development of a new backend implementation.
429 """
429 """
430 if backend_alias not in request.config.getoption('--backends'):
430 if backend_alias not in request.config.getoption('--backends'):
431 pytest.skip("Backend %s not selected." % (backend_alias, ))
431 pytest.skip("Backend %s not selected." % (backend_alias, ))
432
432
433 utils.check_xfail_backends(request.node, backend_alias)
433 utils.check_xfail_backends(request.node, backend_alias)
434 utils.check_skip_backends(request.node, backend_alias)
434 utils.check_skip_backends(request.node, backend_alias)
435
435
436 repo_name = 'vcs_test_%s' % (backend_alias, )
436 repo_name = 'vcs_test_%s' % (backend_alias, )
437 backend = Backend(
437 backend = Backend(
438 alias=backend_alias,
438 alias=backend_alias,
439 repo_name=repo_name,
439 repo_name=repo_name,
440 test_name=request.node.name,
440 test_name=request.node.name,
441 test_repo_container=test_repo)
441 test_repo_container=test_repo)
442 request.addfinalizer(backend.cleanup)
442 request.addfinalizer(backend.cleanup)
443 return backend
443 return backend
444
444
445
445
446 @pytest.fixture
446 @pytest.fixture
447 def backend_git(request, pylonsapp, test_repo):
447 def backend_git(request, pylonsapp, test_repo):
448 return backend(request, 'git', pylonsapp, test_repo)
448 return backend(request, 'git', pylonsapp, test_repo)
449
449
450
450
451 @pytest.fixture
451 @pytest.fixture
452 def backend_hg(request, pylonsapp, test_repo):
452 def backend_hg(request, pylonsapp, test_repo):
453 return backend(request, 'hg', pylonsapp, test_repo)
453 return backend(request, 'hg', pylonsapp, test_repo)
454
454
455
455
456 @pytest.fixture
456 @pytest.fixture
457 def backend_svn(request, pylonsapp, test_repo):
457 def backend_svn(request, pylonsapp, test_repo):
458 return backend(request, 'svn', pylonsapp, test_repo)
458 return backend(request, 'svn', pylonsapp, test_repo)
459
459
460
460
461 @pytest.fixture
461 @pytest.fixture
462 def backend_random(backend_git):
462 def backend_random(backend_git):
463 """
463 """
464 Use this to express that your tests need "a backend.
464 Use this to express that your tests need "a backend.
465
465
466 A few of our tests need a backend, so that we can run the code. This
466 A few of our tests need a backend, so that we can run the code. This
467 fixture is intended to be used for such cases. It will pick one of the
467 fixture is intended to be used for such cases. It will pick one of the
468 backends and run the tests.
468 backends and run the tests.
469
469
470 The fixture `backend` would run the test multiple times for each
470 The fixture `backend` would run the test multiple times for each
471 available backend which is a pure waste of time if the test is
471 available backend which is a pure waste of time if the test is
472 independent of the backend type.
472 independent of the backend type.
473 """
473 """
474 # TODO: johbo: Change this to pick a random backend
474 # TODO: johbo: Change this to pick a random backend
475 return backend_git
475 return backend_git
476
476
477
477
478 @pytest.fixture
478 @pytest.fixture
479 def backend_stub(backend_git):
479 def backend_stub(backend_git):
480 """
480 """
481 Use this to express that your tests need a backend stub
481 Use this to express that your tests need a backend stub
482
482
483 TODO: mikhail: Implement a real stub logic instead of returning
483 TODO: mikhail: Implement a real stub logic instead of returning
484 a git backend
484 a git backend
485 """
485 """
486 return backend_git
486 return backend_git
487
487
488
488
489 @pytest.fixture
489 @pytest.fixture
490 def repo_stub(backend_stub):
490 def repo_stub(backend_stub):
491 """
491 """
492 Use this to express that your tests need a repository stub
492 Use this to express that your tests need a repository stub
493 """
493 """
494 return backend_stub.create_repo()
494 return backend_stub.create_repo()
495
495
496
496
497 class Backend(object):
497 class Backend(object):
498 """
498 """
499 Represents the test configuration for one supported backend
499 Represents the test configuration for one supported backend
500
500
501 Provides easy access to different test repositories based on
501 Provides easy access to different test repositories based on
502 `__getitem__`. Such repositories will only be created once per test
502 `__getitem__`. Such repositories will only be created once per test
503 session.
503 session.
504 """
504 """
505
505
506 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
506 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
507 _master_repo = None
507 _master_repo = None
508 _commit_ids = {}
508 _commit_ids = {}
509
509
510 def __init__(self, alias, repo_name, test_name, test_repo_container):
510 def __init__(self, alias, repo_name, test_name, test_repo_container):
511 self.alias = alias
511 self.alias = alias
512 self.repo_name = repo_name
512 self.repo_name = repo_name
513 self._cleanup_repos = []
513 self._cleanup_repos = []
514 self._test_name = test_name
514 self._test_name = test_name
515 self._test_repo_container = test_repo_container
515 self._test_repo_container = test_repo_container
516 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
516 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
517 # Fixture will survive in the end.
517 # Fixture will survive in the end.
518 self._fixture = Fixture()
518 self._fixture = Fixture()
519
519
520 def __getitem__(self, key):
520 def __getitem__(self, key):
521 return self._test_repo_container(key, self.alias)
521 return self._test_repo_container(key, self.alias)
522
522
523 @property
523 @property
524 def repo(self):
524 def repo(self):
525 """
525 """
526 Returns the "current" repository. This is the vcs_test repo or the
526 Returns the "current" repository. This is the vcs_test repo or the
527 last repo which has been created with `create_repo`.
527 last repo which has been created with `create_repo`.
528 """
528 """
529 from rhodecode.model.db import Repository
529 from rhodecode.model.db import Repository
530 return Repository.get_by_repo_name(self.repo_name)
530 return Repository.get_by_repo_name(self.repo_name)
531
531
532 @property
532 @property
533 def default_branch_name(self):
533 def default_branch_name(self):
534 VcsRepository = get_backend(self.alias)
534 VcsRepository = get_backend(self.alias)
535 return VcsRepository.DEFAULT_BRANCH_NAME
535 return VcsRepository.DEFAULT_BRANCH_NAME
536
536
537 @property
537 @property
538 def default_head_id(self):
538 def default_head_id(self):
539 """
539 """
540 Returns the default head id of the underlying backend.
540 Returns the default head id of the underlying backend.
541
541
542 This will be the default branch name in case the backend does have a
542 This will be the default branch name in case the backend does have a
543 default branch. In the other cases it will point to a valid head
543 default branch. In the other cases it will point to a valid head
544 which can serve as the base to create a new commit on top of it.
544 which can serve as the base to create a new commit on top of it.
545 """
545 """
546 vcsrepo = self.repo.scm_instance()
546 vcsrepo = self.repo.scm_instance()
547 head_id = (
547 head_id = (
548 vcsrepo.DEFAULT_BRANCH_NAME or
548 vcsrepo.DEFAULT_BRANCH_NAME or
549 vcsrepo.commit_ids[-1])
549 vcsrepo.commit_ids[-1])
550 return head_id
550 return head_id
551
551
552 @property
552 @property
553 def commit_ids(self):
553 def commit_ids(self):
554 """
554 """
555 Returns the list of commits for the last created repository
555 Returns the list of commits for the last created repository
556 """
556 """
557 return self._commit_ids
557 return self._commit_ids
558
558
559 def create_master_repo(self, commits):
559 def create_master_repo(self, commits):
560 """
560 """
561 Create a repository and remember it as a template.
561 Create a repository and remember it as a template.
562
562
563 This allows to easily create derived repositories to construct
563 This allows to easily create derived repositories to construct
564 more complex scenarios for diff, compare and pull requests.
564 more complex scenarios for diff, compare and pull requests.
565
565
566 Returns a commit map which maps from commit message to raw_id.
566 Returns a commit map which maps from commit message to raw_id.
567 """
567 """
568 self._master_repo = self.create_repo(commits=commits)
568 self._master_repo = self.create_repo(commits=commits)
569 return self._commit_ids
569 return self._commit_ids
570
570
571 def create_repo(
571 def create_repo(
572 self, commits=None, number_of_commits=0, heads=None,
572 self, commits=None, number_of_commits=0, heads=None,
573 name_suffix=u'', **kwargs):
573 name_suffix=u'', **kwargs):
574 """
574 """
575 Create a repository and record it for later cleanup.
575 Create a repository and record it for later cleanup.
576
576
577 :param commits: Optional. A sequence of dict instances.
577 :param commits: Optional. A sequence of dict instances.
578 Will add a commit per entry to the new repository.
578 Will add a commit per entry to the new repository.
579 :param number_of_commits: Optional. If set to a number, this number of
579 :param number_of_commits: Optional. If set to a number, this number of
580 commits will be added to the new repository.
580 commits will be added to the new repository.
581 :param heads: Optional. Can be set to a sequence of of commit
581 :param heads: Optional. Can be set to a sequence of of commit
582 names which shall be pulled in from the master repository.
582 names which shall be pulled in from the master repository.
583
583
584 """
584 """
585 self.repo_name = self._next_repo_name() + name_suffix
585 self.repo_name = self._next_repo_name() + name_suffix
586 repo = self._fixture.create_repo(
586 repo = self._fixture.create_repo(
587 self.repo_name, repo_type=self.alias, **kwargs)
587 self.repo_name, repo_type=self.alias, **kwargs)
588 self._cleanup_repos.append(repo.repo_name)
588 self._cleanup_repos.append(repo.repo_name)
589
589
590 commits = commits or [
590 commits = commits or [
591 {'message': 'Commit %s of %s' % (x, self.repo_name)}
591 {'message': 'Commit %s of %s' % (x, self.repo_name)}
592 for x in xrange(number_of_commits)]
592 for x in xrange(number_of_commits)]
593 self._add_commits_to_repo(repo.scm_instance(), commits)
593 self._add_commits_to_repo(repo.scm_instance(), commits)
594 if heads:
594 if heads:
595 self.pull_heads(repo, heads)
595 self.pull_heads(repo, heads)
596
596
597 return repo
597 return repo
598
598
599 def pull_heads(self, repo, heads):
599 def pull_heads(self, repo, heads):
600 """
600 """
601 Make sure that repo contains all commits mentioned in `heads`
601 Make sure that repo contains all commits mentioned in `heads`
602 """
602 """
603 vcsmaster = self._master_repo.scm_instance()
603 vcsmaster = self._master_repo.scm_instance()
604 vcsrepo = repo.scm_instance()
604 vcsrepo = repo.scm_instance()
605 vcsrepo.config.clear_section('hooks')
605 vcsrepo.config.clear_section('hooks')
606 commit_ids = [self._commit_ids[h] for h in heads]
606 commit_ids = [self._commit_ids[h] for h in heads]
607 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
607 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
608
608
609 def create_fork(self):
609 def create_fork(self):
610 repo_to_fork = self.repo_name
610 repo_to_fork = self.repo_name
611 self.repo_name = self._next_repo_name()
611 self.repo_name = self._next_repo_name()
612 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
612 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
613 self._cleanup_repos.append(self.repo_name)
613 self._cleanup_repos.append(self.repo_name)
614 return repo
614 return repo
615
615
616 def new_repo_name(self, suffix=u''):
616 def new_repo_name(self, suffix=u''):
617 self.repo_name = self._next_repo_name() + suffix
617 self.repo_name = self._next_repo_name() + suffix
618 self._cleanup_repos.append(self.repo_name)
618 self._cleanup_repos.append(self.repo_name)
619 return self.repo_name
619 return self.repo_name
620
620
621 def _next_repo_name(self):
621 def _next_repo_name(self):
622 return u"%s_%s" % (
622 return u"%s_%s" % (
623 self.invalid_repo_name.sub(u'_', self._test_name),
623 self.invalid_repo_name.sub(u'_', self._test_name),
624 len(self._cleanup_repos))
624 len(self._cleanup_repos))
625
625
626 def ensure_file(self, filename, content='Test content\n'):
626 def ensure_file(self, filename, content='Test content\n'):
627 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
627 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
628 commits = [
628 commits = [
629 {'added': [
629 {'added': [
630 FileNode(filename, content=content),
630 FileNode(filename, content=content),
631 ]},
631 ]},
632 ]
632 ]
633 self._add_commits_to_repo(self.repo.scm_instance(), commits)
633 self._add_commits_to_repo(self.repo.scm_instance(), commits)
634
634
635 def enable_downloads(self):
635 def enable_downloads(self):
636 repo = self.repo
636 repo = self.repo
637 repo.enable_downloads = True
637 repo.enable_downloads = True
638 Session().add(repo)
638 Session().add(repo)
639 Session().commit()
639 Session().commit()
640
640
641 def cleanup(self):
641 def cleanup(self):
642 for repo_name in reversed(self._cleanup_repos):
642 for repo_name in reversed(self._cleanup_repos):
643 self._fixture.destroy_repo(repo_name)
643 self._fixture.destroy_repo(repo_name)
644
644
645 def _add_commits_to_repo(self, repo, commits):
645 def _add_commits_to_repo(self, repo, commits):
646 commit_ids = _add_commits_to_repo(repo, commits)
646 commit_ids = _add_commits_to_repo(repo, commits)
647 if not commit_ids:
647 if not commit_ids:
648 return
648 return
649 self._commit_ids = commit_ids
649 self._commit_ids = commit_ids
650
650
651 # Creating refs for Git to allow fetching them from remote repository
651 # Creating refs for Git to allow fetching them from remote repository
652 if self.alias == 'git':
652 if self.alias == 'git':
653 refs = {}
653 refs = {}
654 for message in self._commit_ids:
654 for message in self._commit_ids:
655 # TODO: mikhail: do more special chars replacements
655 # TODO: mikhail: do more special chars replacements
656 ref_name = 'refs/test-refs/{}'.format(
656 ref_name = 'refs/test-refs/{}'.format(
657 message.replace(' ', ''))
657 message.replace(' ', ''))
658 refs[ref_name] = self._commit_ids[message]
658 refs[ref_name] = self._commit_ids[message]
659 self._create_refs(repo, refs)
659 self._create_refs(repo, refs)
660
660
661 def _create_refs(self, repo, refs):
661 def _create_refs(self, repo, refs):
662 for ref_name in refs:
662 for ref_name in refs:
663 repo.set_refs(ref_name, refs[ref_name])
663 repo.set_refs(ref_name, refs[ref_name])
664
664
665
665
666 @pytest.fixture
666 @pytest.fixture
667 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
667 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
668 """
668 """
669 Parametrized fixture which represents a single vcs backend implementation.
669 Parametrized fixture which represents a single vcs backend implementation.
670
670
671 See the fixture `backend` for more details. This one implements the same
671 See the fixture `backend` for more details. This one implements the same
672 concept, but on vcs level. So it does not provide model instances etc.
672 concept, but on vcs level. So it does not provide model instances etc.
673
673
674 Parameters are generated dynamically, see :func:`pytest_generate_tests`
674 Parameters are generated dynamically, see :func:`pytest_generate_tests`
675 for how this works.
675 for how this works.
676 """
676 """
677 if backend_alias not in request.config.getoption('--backends'):
677 if backend_alias not in request.config.getoption('--backends'):
678 pytest.skip("Backend %s not selected." % (backend_alias, ))
678 pytest.skip("Backend %s not selected." % (backend_alias, ))
679
679
680 utils.check_xfail_backends(request.node, backend_alias)
680 utils.check_xfail_backends(request.node, backend_alias)
681 utils.check_skip_backends(request.node, backend_alias)
681 utils.check_skip_backends(request.node, backend_alias)
682
682
683 repo_name = 'vcs_test_%s' % (backend_alias, )
683 repo_name = 'vcs_test_%s' % (backend_alias, )
684 repo_path = os.path.join(tests_tmp_path, repo_name)
684 repo_path = os.path.join(tests_tmp_path, repo_name)
685 backend = VcsBackend(
685 backend = VcsBackend(
686 alias=backend_alias,
686 alias=backend_alias,
687 repo_path=repo_path,
687 repo_path=repo_path,
688 test_name=request.node.name,
688 test_name=request.node.name,
689 test_repo_container=test_repo)
689 test_repo_container=test_repo)
690 request.addfinalizer(backend.cleanup)
690 request.addfinalizer(backend.cleanup)
691 return backend
691 return backend
692
692
693
693
694 @pytest.fixture
694 @pytest.fixture
695 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
695 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
696 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
696 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
697
697
698
698
699 @pytest.fixture
699 @pytest.fixture
700 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
700 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
701 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
701 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
702
702
703
703
704 @pytest.fixture
704 @pytest.fixture
705 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
705 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
706 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
706 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
707
707
708
708
709 @pytest.fixture
709 @pytest.fixture
710 def vcsbackend_random(vcsbackend_git):
710 def vcsbackend_random(vcsbackend_git):
711 """
711 """
712 Use this to express that your tests need "a vcsbackend".
712 Use this to express that your tests need "a vcsbackend".
713
713
714 The fixture `vcsbackend` would run the test multiple times for each
714 The fixture `vcsbackend` would run the test multiple times for each
715 available vcs backend which is a pure waste of time if the test is
715 available vcs backend which is a pure waste of time if the test is
716 independent of the vcs backend type.
716 independent of the vcs backend type.
717 """
717 """
718 # TODO: johbo: Change this to pick a random backend
718 # TODO: johbo: Change this to pick a random backend
719 return vcsbackend_git
719 return vcsbackend_git
720
720
721
721
722 @pytest.fixture
722 @pytest.fixture
723 def vcsbackend_stub(vcsbackend_git):
723 def vcsbackend_stub(vcsbackend_git):
724 """
724 """
725 Use this to express that your test just needs a stub of a vcsbackend.
725 Use this to express that your test just needs a stub of a vcsbackend.
726
726
727 Plan is to eventually implement an in-memory stub to speed tests up.
727 Plan is to eventually implement an in-memory stub to speed tests up.
728 """
728 """
729 return vcsbackend_git
729 return vcsbackend_git
730
730
731
731
732 class VcsBackend(object):
732 class VcsBackend(object):
733 """
733 """
734 Represents the test configuration for one supported vcs backend.
734 Represents the test configuration for one supported vcs backend.
735 """
735 """
736
736
737 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
737 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
738
738
739 def __init__(self, alias, repo_path, test_name, test_repo_container):
739 def __init__(self, alias, repo_path, test_name, test_repo_container):
740 self.alias = alias
740 self.alias = alias
741 self._repo_path = repo_path
741 self._repo_path = repo_path
742 self._cleanup_repos = []
742 self._cleanup_repos = []
743 self._test_name = test_name
743 self._test_name = test_name
744 self._test_repo_container = test_repo_container
744 self._test_repo_container = test_repo_container
745
745
746 def __getitem__(self, key):
746 def __getitem__(self, key):
747 return self._test_repo_container(key, self.alias).scm_instance()
747 return self._test_repo_container(key, self.alias).scm_instance()
748
748
749 @property
749 @property
750 def repo(self):
750 def repo(self):
751 """
751 """
752 Returns the "current" repository. This is the vcs_test repo of the last
752 Returns the "current" repository. This is the vcs_test repo of the last
753 repo which has been created.
753 repo which has been created.
754 """
754 """
755 Repository = get_backend(self.alias)
755 Repository = get_backend(self.alias)
756 return Repository(self._repo_path)
756 return Repository(self._repo_path)
757
757
758 @property
758 @property
759 def backend(self):
759 def backend(self):
760 """
760 """
761 Returns the backend implementation class.
761 Returns the backend implementation class.
762 """
762 """
763 return get_backend(self.alias)
763 return get_backend(self.alias)
764
764
765 def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None):
765 def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None):
766 repo_name = self._next_repo_name()
766 repo_name = self._next_repo_name()
767 self._repo_path = get_new_dir(repo_name)
767 self._repo_path = get_new_dir(repo_name)
768 repo_class = get_backend(self.alias)
768 repo_class = get_backend(self.alias)
769 src_url = None
769 src_url = None
770 if _clone_repo:
770 if _clone_repo:
771 src_url = _clone_repo.path
771 src_url = _clone_repo.path
772 repo = repo_class(self._repo_path, create=True, src_url=src_url)
772 repo = repo_class(self._repo_path, create=True, src_url=src_url)
773 self._cleanup_repos.append(repo)
773 self._cleanup_repos.append(repo)
774
774
775 commits = commits or [
775 commits = commits or [
776 {'message': 'Commit %s of %s' % (x, repo_name)}
776 {'message': 'Commit %s of %s' % (x, repo_name)}
777 for x in xrange(number_of_commits)]
777 for x in xrange(number_of_commits)]
778 _add_commits_to_repo(repo, commits)
778 _add_commits_to_repo(repo, commits)
779 return repo
779 return repo
780
780
781 def clone_repo(self, repo):
781 def clone_repo(self, repo):
782 return self.create_repo(_clone_repo=repo)
782 return self.create_repo(_clone_repo=repo)
783
783
784 def cleanup(self):
784 def cleanup(self):
785 for repo in self._cleanup_repos:
785 for repo in self._cleanup_repos:
786 shutil.rmtree(repo.path)
786 shutil.rmtree(repo.path)
787
787
788 def new_repo_path(self):
788 def new_repo_path(self):
789 repo_name = self._next_repo_name()
789 repo_name = self._next_repo_name()
790 self._repo_path = get_new_dir(repo_name)
790 self._repo_path = get_new_dir(repo_name)
791 return self._repo_path
791 return self._repo_path
792
792
793 def _next_repo_name(self):
793 def _next_repo_name(self):
794 return "%s_%s" % (
794 return "%s_%s" % (
795 self.invalid_repo_name.sub('_', self._test_name),
795 self.invalid_repo_name.sub('_', self._test_name),
796 len(self._cleanup_repos))
796 len(self._cleanup_repos))
797
797
798 def add_file(self, repo, filename, content='Test content\n'):
798 def add_file(self, repo, filename, content='Test content\n'):
799 imc = repo.in_memory_commit
799 imc = repo.in_memory_commit
800 imc.add(FileNode(filename, content=content))
800 imc.add(FileNode(filename, content=content))
801 imc.commit(
801 imc.commit(
802 message=u'Automatic commit from vcsbackend fixture',
802 message=u'Automatic commit from vcsbackend fixture',
803 author=u'Automatic')
803 author=u'Automatic')
804
804
805 def ensure_file(self, filename, content='Test content\n'):
805 def ensure_file(self, filename, content='Test content\n'):
806 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
806 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
807 self.add_file(self.repo, filename, content)
807 self.add_file(self.repo, filename, content)
808
808
809
809
810 def _add_commits_to_repo(vcs_repo, commits):
810 def _add_commits_to_repo(vcs_repo, commits):
811 commit_ids = {}
811 commit_ids = {}
812 if not commits:
812 if not commits:
813 return commit_ids
813 return commit_ids
814
814
815 imc = vcs_repo.in_memory_commit
815 imc = vcs_repo.in_memory_commit
816 commit = None
816 commit = None
817
817
818 for idx, commit in enumerate(commits):
818 for idx, commit in enumerate(commits):
819 message = unicode(commit.get('message', 'Commit %s' % idx))
819 message = unicode(commit.get('message', 'Commit %s' % idx))
820
820
821 for node in commit.get('added', []):
821 for node in commit.get('added', []):
822 imc.add(FileNode(node.path, content=node.content))
822 imc.add(FileNode(node.path, content=node.content))
823 for node in commit.get('changed', []):
823 for node in commit.get('changed', []):
824 imc.change(FileNode(node.path, content=node.content))
824 imc.change(FileNode(node.path, content=node.content))
825 for node in commit.get('removed', []):
825 for node in commit.get('removed', []):
826 imc.remove(FileNode(node.path))
826 imc.remove(FileNode(node.path))
827
827
828 parents = [
828 parents = [
829 vcs_repo.get_commit(commit_id=commit_ids[p])
829 vcs_repo.get_commit(commit_id=commit_ids[p])
830 for p in commit.get('parents', [])]
830 for p in commit.get('parents', [])]
831
831
832 operations = ('added', 'changed', 'removed')
832 operations = ('added', 'changed', 'removed')
833 if not any((commit.get(o) for o in operations)):
833 if not any((commit.get(o) for o in operations)):
834 imc.add(FileNode('file_%s' % idx, content=message))
834 imc.add(FileNode('file_%s' % idx, content=message))
835
835
836 commit = imc.commit(
836 commit = imc.commit(
837 message=message,
837 message=message,
838 author=unicode(commit.get('author', 'Automatic')),
838 author=unicode(commit.get('author', 'Automatic')),
839 date=commit.get('date'),
839 date=commit.get('date'),
840 branch=commit.get('branch'),
840 branch=commit.get('branch'),
841 parents=parents)
841 parents=parents)
842
842
843 commit_ids[commit.message] = commit.raw_id
843 commit_ids[commit.message] = commit.raw_id
844
844
845 return commit_ids
845 return commit_ids
846
846
847
847
848 @pytest.fixture
848 @pytest.fixture
849 def reposerver(request):
849 def reposerver(request):
850 """
850 """
851 Allows to serve a backend repository
851 Allows to serve a backend repository
852 """
852 """
853
853
854 repo_server = RepoServer()
854 repo_server = RepoServer()
855 request.addfinalizer(repo_server.cleanup)
855 request.addfinalizer(repo_server.cleanup)
856 return repo_server
856 return repo_server
857
857
858
858
859 class RepoServer(object):
859 class RepoServer(object):
860 """
860 """
861 Utility to serve a local repository for the duration of a test case.
861 Utility to serve a local repository for the duration of a test case.
862
862
863 Supports only Subversion so far.
863 Supports only Subversion so far.
864 """
864 """
865
865
866 url = None
866 url = None
867
867
868 def __init__(self):
868 def __init__(self):
869 self._cleanup_servers = []
869 self._cleanup_servers = []
870
870
871 def serve(self, vcsrepo):
871 def serve(self, vcsrepo):
872 if vcsrepo.alias != 'svn':
872 if vcsrepo.alias != 'svn':
873 raise TypeError("Backend %s not supported" % vcsrepo.alias)
873 raise TypeError("Backend %s not supported" % vcsrepo.alias)
874
874
875 proc = subprocess.Popen(
875 proc = subprocess.Popen(
876 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
876 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
877 '--root', vcsrepo.path])
877 '--root', vcsrepo.path])
878 self._cleanup_servers.append(proc)
878 self._cleanup_servers.append(proc)
879 self.url = 'svn://localhost'
879 self.url = 'svn://localhost'
880
880
881 def cleanup(self):
881 def cleanup(self):
882 for proc in self._cleanup_servers:
882 for proc in self._cleanup_servers:
883 proc.terminate()
883 proc.terminate()
884
884
885
885
886 @pytest.fixture
886 @pytest.fixture
887 def pr_util(backend, request):
887 def pr_util(backend, request):
888 """
888 """
889 Utility for tests of models and for functional tests around pull requests.
889 Utility for tests of models and for functional tests around pull requests.
890
890
891 It gives an instance of :class:`PRTestUtility` which provides various
891 It gives an instance of :class:`PRTestUtility` which provides various
892 utility methods around one pull request.
892 utility methods around one pull request.
893
893
894 This fixture uses `backend` and inherits its parameterization.
894 This fixture uses `backend` and inherits its parameterization.
895 """
895 """
896
896
897 util = PRTestUtility(backend)
897 util = PRTestUtility(backend)
898
898
899 @request.addfinalizer
899 @request.addfinalizer
900 def cleanup():
900 def cleanup():
901 util.cleanup()
901 util.cleanup()
902
902
903 return util
903 return util
904
904
905
905
906 class PRTestUtility(object):
906 class PRTestUtility(object):
907
907
908 pull_request = None
908 pull_request = None
909 pull_request_id = None
909 pull_request_id = None
910 mergeable_patcher = None
910 mergeable_patcher = None
911 mergeable_mock = None
911 mergeable_mock = None
912 notification_patcher = None
912 notification_patcher = None
913
913
914 def __init__(self, backend):
914 def __init__(self, backend):
915 self.backend = backend
915 self.backend = backend
916
916
917 def create_pull_request(
917 def create_pull_request(
918 self, commits=None, target_head=None, source_head=None,
918 self, commits=None, target_head=None, source_head=None,
919 revisions=None, approved=False, author=None, mergeable=False,
919 revisions=None, approved=False, author=None, mergeable=False,
920 enable_notifications=True, name_suffix=u'', reviewers=None,
920 enable_notifications=True, name_suffix=u'', reviewers=None,
921 title=u"Test", description=u"Description"):
921 title=u"Test", description=u"Description"):
922 self.set_mergeable(mergeable)
922 self.set_mergeable(mergeable)
923 if not enable_notifications:
923 if not enable_notifications:
924 # mock notification side effect
924 # mock notification side effect
925 self.notification_patcher = mock.patch(
925 self.notification_patcher = mock.patch(
926 'rhodecode.model.notification.NotificationModel.create')
926 'rhodecode.model.notification.NotificationModel.create')
927 self.notification_patcher.start()
927 self.notification_patcher.start()
928
928
929 if not self.pull_request:
929 if not self.pull_request:
930 if not commits:
930 if not commits:
931 commits = [
931 commits = [
932 {'message': 'c1'},
932 {'message': 'c1'},
933 {'message': 'c2'},
933 {'message': 'c2'},
934 {'message': 'c3'},
934 {'message': 'c3'},
935 ]
935 ]
936 target_head = 'c1'
936 target_head = 'c1'
937 source_head = 'c2'
937 source_head = 'c2'
938 revisions = ['c2']
938 revisions = ['c2']
939
939
940 self.commit_ids = self.backend.create_master_repo(commits)
940 self.commit_ids = self.backend.create_master_repo(commits)
941 self.target_repository = self.backend.create_repo(
941 self.target_repository = self.backend.create_repo(
942 heads=[target_head], name_suffix=name_suffix)
942 heads=[target_head], name_suffix=name_suffix)
943 self.source_repository = self.backend.create_repo(
943 self.source_repository = self.backend.create_repo(
944 heads=[source_head], name_suffix=name_suffix)
944 heads=[source_head], name_suffix=name_suffix)
945 self.author = author or UserModel().get_by_username(
945 self.author = author or UserModel().get_by_username(
946 TEST_USER_ADMIN_LOGIN)
946 TEST_USER_ADMIN_LOGIN)
947
947
948 model = PullRequestModel()
948 model = PullRequestModel()
949 self.create_parameters = {
949 self.create_parameters = {
950 'created_by': self.author,
950 'created_by': self.author,
951 'source_repo': self.source_repository.repo_name,
951 'source_repo': self.source_repository.repo_name,
952 'source_ref': self._default_branch_reference(source_head),
952 'source_ref': self._default_branch_reference(source_head),
953 'target_repo': self.target_repository.repo_name,
953 'target_repo': self.target_repository.repo_name,
954 'target_ref': self._default_branch_reference(target_head),
954 'target_ref': self._default_branch_reference(target_head),
955 'revisions': [self.commit_ids[r] for r in revisions],
955 'revisions': [self.commit_ids[r] for r in revisions],
956 'reviewers': reviewers or self._get_reviewers(),
956 'reviewers': reviewers or self._get_reviewers(),
957 'title': title,
957 'title': title,
958 'description': description,
958 'description': description,
959 }
959 }
960 self.pull_request = model.create(**self.create_parameters)
960 self.pull_request = model.create(**self.create_parameters)
961 assert model.get_versions(self.pull_request) == []
961 assert model.get_versions(self.pull_request) == []
962
962
963 self.pull_request_id = self.pull_request.pull_request_id
963 self.pull_request_id = self.pull_request.pull_request_id
964
964
965 if approved:
965 if approved:
966 self.approve()
966 self.approve()
967
967
968 Session().add(self.pull_request)
968 Session().add(self.pull_request)
969 Session().commit()
969 Session().commit()
970
970
971 return self.pull_request
971 return self.pull_request
972
972
973 def approve(self):
973 def approve(self):
974 self.create_status_votes(
974 self.create_status_votes(
975 ChangesetStatus.STATUS_APPROVED,
975 ChangesetStatus.STATUS_APPROVED,
976 *self.pull_request.reviewers)
976 *self.pull_request.reviewers)
977
977
978 def close(self):
978 def close(self):
979 PullRequestModel().close_pull_request(self.pull_request, self.author)
979 PullRequestModel().close_pull_request(self.pull_request, self.author)
980
980
981 def _default_branch_reference(self, commit_message):
981 def _default_branch_reference(self, commit_message):
982 reference = '%s:%s:%s' % (
982 reference = '%s:%s:%s' % (
983 'branch',
983 'branch',
984 self.backend.default_branch_name,
984 self.backend.default_branch_name,
985 self.commit_ids[commit_message])
985 self.commit_ids[commit_message])
986 return reference
986 return reference
987
987
988 def _get_reviewers(self):
988 def _get_reviewers(self):
989 model = UserModel()
989 model = UserModel()
990 return [
990 return [
991 model.get_by_username(TEST_USER_REGULAR_LOGIN),
991 model.get_by_username(TEST_USER_REGULAR_LOGIN),
992 model.get_by_username(TEST_USER_REGULAR2_LOGIN),
992 model.get_by_username(TEST_USER_REGULAR2_LOGIN),
993 ]
993 ]
994
994
995 def update_source_repository(self, head=None):
995 def update_source_repository(self, head=None):
996 heads = [head or 'c3']
996 heads = [head or 'c3']
997 self.backend.pull_heads(self.source_repository, heads=heads)
997 self.backend.pull_heads(self.source_repository, heads=heads)
998
998
999 def add_one_commit(self, head=None):
999 def add_one_commit(self, head=None):
1000 self.update_source_repository(head=head)
1000 self.update_source_repository(head=head)
1001 old_commit_ids = set(self.pull_request.revisions)
1001 old_commit_ids = set(self.pull_request.revisions)
1002 PullRequestModel().update_commits(self.pull_request)
1002 PullRequestModel().update_commits(self.pull_request)
1003 commit_ids = set(self.pull_request.revisions)
1003 commit_ids = set(self.pull_request.revisions)
1004 new_commit_ids = commit_ids - old_commit_ids
1004 new_commit_ids = commit_ids - old_commit_ids
1005 assert len(new_commit_ids) == 1
1005 assert len(new_commit_ids) == 1
1006 return new_commit_ids.pop()
1006 return new_commit_ids.pop()
1007
1007
1008 def remove_one_commit(self):
1008 def remove_one_commit(self):
1009 assert len(self.pull_request.revisions) == 2
1009 assert len(self.pull_request.revisions) == 2
1010 source_vcs = self.source_repository.scm_instance()
1010 source_vcs = self.source_repository.scm_instance()
1011 removed_commit_id = source_vcs.commit_ids[-1]
1011 removed_commit_id = source_vcs.commit_ids[-1]
1012
1012
1013 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
1013 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
1014 # remove the if once that's sorted out.
1014 # remove the if once that's sorted out.
1015 if self.backend.alias == "git":
1015 if self.backend.alias == "git":
1016 kwargs = {'branch_name': self.backend.default_branch_name}
1016 kwargs = {'branch_name': self.backend.default_branch_name}
1017 else:
1017 else:
1018 kwargs = {}
1018 kwargs = {}
1019 source_vcs.strip(removed_commit_id, **kwargs)
1019 source_vcs.strip(removed_commit_id, **kwargs)
1020
1020
1021 PullRequestModel().update_commits(self.pull_request)
1021 PullRequestModel().update_commits(self.pull_request)
1022 assert len(self.pull_request.revisions) == 1
1022 assert len(self.pull_request.revisions) == 1
1023 return removed_commit_id
1023 return removed_commit_id
1024
1024
1025 def create_comment(self, linked_to=None):
1025 def create_comment(self, linked_to=None):
1026 comment = ChangesetCommentsModel().create(
1026 comment = ChangesetCommentsModel().create(
1027 text=u"Test comment",
1027 text=u"Test comment",
1028 repo=self.target_repository.repo_name,
1028 repo=self.target_repository.repo_name,
1029 user=self.author,
1029 user=self.author,
1030 pull_request=self.pull_request)
1030 pull_request=self.pull_request)
1031 assert comment.pull_request_version_id is None
1031 assert comment.pull_request_version_id is None
1032
1032
1033 if linked_to:
1033 if linked_to:
1034 PullRequestModel()._link_comments_to_version(linked_to)
1034 PullRequestModel()._link_comments_to_version(linked_to)
1035
1035
1036 return comment
1036 return comment
1037
1037
1038 def create_inline_comment(
1038 def create_inline_comment(
1039 self, linked_to=None, line_no=u'n1', file_path='file_1'):
1039 self, linked_to=None, line_no=u'n1', file_path='file_1'):
1040 comment = ChangesetCommentsModel().create(
1040 comment = ChangesetCommentsModel().create(
1041 text=u"Test comment",
1041 text=u"Test comment",
1042 repo=self.target_repository.repo_name,
1042 repo=self.target_repository.repo_name,
1043 user=self.author,
1043 user=self.author,
1044 line_no=line_no,
1044 line_no=line_no,
1045 f_path=file_path,
1045 f_path=file_path,
1046 pull_request=self.pull_request)
1046 pull_request=self.pull_request)
1047 assert comment.pull_request_version_id is None
1047 assert comment.pull_request_version_id is None
1048
1048
1049 if linked_to:
1049 if linked_to:
1050 PullRequestModel()._link_comments_to_version(linked_to)
1050 PullRequestModel()._link_comments_to_version(linked_to)
1051
1051
1052 return comment
1052 return comment
1053
1053
1054 def create_version_of_pull_request(self):
1054 def create_version_of_pull_request(self):
1055 pull_request = self.create_pull_request()
1055 pull_request = self.create_pull_request()
1056 version = PullRequestModel()._create_version_from_snapshot(
1056 version = PullRequestModel()._create_version_from_snapshot(
1057 pull_request)
1057 pull_request)
1058 return version
1058 return version
1059
1059
1060 def create_status_votes(self, status, *reviewers):
1060 def create_status_votes(self, status, *reviewers):
1061 for reviewer in reviewers:
1061 for reviewer in reviewers:
1062 ChangesetStatusModel().set_status(
1062 ChangesetStatusModel().set_status(
1063 repo=self.pull_request.target_repo,
1063 repo=self.pull_request.target_repo,
1064 status=status,
1064 status=status,
1065 user=reviewer.user_id,
1065 user=reviewer.user_id,
1066 pull_request=self.pull_request)
1066 pull_request=self.pull_request)
1067
1067
1068 def set_mergeable(self, value):
1068 def set_mergeable(self, value):
1069 if not self.mergeable_patcher:
1069 if not self.mergeable_patcher:
1070 self.mergeable_patcher = mock.patch.object(
1070 self.mergeable_patcher = mock.patch.object(
1071 VcsSettingsModel, 'get_general_settings')
1071 VcsSettingsModel, 'get_general_settings')
1072 self.mergeable_mock = self.mergeable_patcher.start()
1072 self.mergeable_mock = self.mergeable_patcher.start()
1073 self.mergeable_mock.return_value = {
1073 self.mergeable_mock.return_value = {
1074 'rhodecode_pr_merge_enabled': value}
1074 'rhodecode_pr_merge_enabled': value}
1075
1075
1076 def cleanup(self):
1076 def cleanup(self):
1077 # In case the source repository is already cleaned up, the pull
1077 # In case the source repository is already cleaned up, the pull
1078 # request will already be deleted.
1078 # request will already be deleted.
1079 pull_request = PullRequest().get(self.pull_request_id)
1079 pull_request = PullRequest().get(self.pull_request_id)
1080 if pull_request:
1080 if pull_request:
1081 PullRequestModel().delete(pull_request)
1081 PullRequestModel().delete(pull_request)
1082 Session().commit()
1082 Session().commit()
1083
1083
1084 if self.notification_patcher:
1084 if self.notification_patcher:
1085 self.notification_patcher.stop()
1085 self.notification_patcher.stop()
1086
1086
1087 if self.mergeable_patcher:
1087 if self.mergeable_patcher:
1088 self.mergeable_patcher.stop()
1088 self.mergeable_patcher.stop()
1089
1089
1090
1090
1091 @pytest.fixture
1091 @pytest.fixture
1092 def user_admin(pylonsapp):
1092 def user_admin(pylonsapp):
1093 """
1093 """
1094 Provides the default admin test user as an instance of `db.User`.
1094 Provides the default admin test user as an instance of `db.User`.
1095 """
1095 """
1096 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1096 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1097 return user
1097 return user
1098
1098
1099
1099
1100 @pytest.fixture
1100 @pytest.fixture
1101 def user_regular(pylonsapp):
1101 def user_regular(pylonsapp):
1102 """
1102 """
1103 Provides the default regular test user as an instance of `db.User`.
1103 Provides the default regular test user as an instance of `db.User`.
1104 """
1104 """
1105 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1105 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1106 return user
1106 return user
1107
1107
1108
1108
1109 @pytest.fixture
1109 @pytest.fixture
1110 def user_util(request, pylonsapp):
1110 def user_util(request, pylonsapp):
1111 """
1111 """
1112 Provides a wired instance of `UserUtility` with integrated cleanup.
1112 Provides a wired instance of `UserUtility` with integrated cleanup.
1113 """
1113 """
1114 utility = UserUtility(test_name=request.node.name)
1114 utility = UserUtility(test_name=request.node.name)
1115 request.addfinalizer(utility.cleanup)
1115 request.addfinalizer(utility.cleanup)
1116 return utility
1116 return utility
1117
1117
1118
1118
1119 # TODO: johbo: Split this up into utilities per domain or something similar
1119 # TODO: johbo: Split this up into utilities per domain or something similar
1120 class UserUtility(object):
1120 class UserUtility(object):
1121
1121
1122 def __init__(self, test_name="test"):
1122 def __init__(self, test_name="test"):
1123 self._test_name = test_name
1123 self._test_name = test_name
1124 self.fixture = Fixture()
1124 self.fixture = Fixture()
1125 self.repo_group_ids = []
1125 self.repo_group_ids = []
1126 self.user_ids = []
1126 self.user_ids = []
1127 self.user_group_ids = []
1127 self.user_group_ids = []
1128 self.user_repo_permission_ids = []
1128 self.user_repo_permission_ids = []
1129 self.user_group_repo_permission_ids = []
1129 self.user_group_repo_permission_ids = []
1130 self.user_repo_group_permission_ids = []
1130 self.user_repo_group_permission_ids = []
1131 self.user_group_repo_group_permission_ids = []
1131 self.user_group_repo_group_permission_ids = []
1132 self.user_user_group_permission_ids = []
1132 self.user_user_group_permission_ids = []
1133 self.user_group_user_group_permission_ids = []
1133 self.user_group_user_group_permission_ids = []
1134 self.user_permissions = []
1134 self.user_permissions = []
1135
1135
1136 def create_repo_group(
1136 def create_repo_group(
1137 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1137 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1138 group_name = "{prefix}_repogroup_{count}".format(
1138 group_name = "{prefix}_repogroup_{count}".format(
1139 prefix=self._test_name,
1139 prefix=self._test_name,
1140 count=len(self.repo_group_ids))
1140 count=len(self.repo_group_ids))
1141 repo_group = self.fixture.create_repo_group(
1141 repo_group = self.fixture.create_repo_group(
1142 group_name, cur_user=owner)
1142 group_name, cur_user=owner)
1143 if auto_cleanup:
1143 if auto_cleanup:
1144 self.repo_group_ids.append(repo_group.group_id)
1144 self.repo_group_ids.append(repo_group.group_id)
1145 return repo_group
1145 return repo_group
1146
1146
1147 def create_user(self, auto_cleanup=True, **kwargs):
1147 def create_user(self, auto_cleanup=True, **kwargs):
1148 user_name = "{prefix}_user_{count}".format(
1148 user_name = "{prefix}_user_{count}".format(
1149 prefix=self._test_name,
1149 prefix=self._test_name,
1150 count=len(self.user_ids))
1150 count=len(self.user_ids))
1151 user = self.fixture.create_user(user_name, **kwargs)
1151 user = self.fixture.create_user(user_name, **kwargs)
1152 if auto_cleanup:
1152 if auto_cleanup:
1153 self.user_ids.append(user.user_id)
1153 self.user_ids.append(user.user_id)
1154 return user
1154 return user
1155
1155
1156 def create_user_with_group(self):
1156 def create_user_with_group(self):
1157 user = self.create_user()
1157 user = self.create_user()
1158 user_group = self.create_user_group(members=[user])
1158 user_group = self.create_user_group(members=[user])
1159 return user, user_group
1159 return user, user_group
1160
1160
1161 def create_user_group(self, members=None, auto_cleanup=True, **kwargs):
1161 def create_user_group(self, members=None, auto_cleanup=True, **kwargs):
1162 group_name = "{prefix}_usergroup_{count}".format(
1162 group_name = "{prefix}_usergroup_{count}".format(
1163 prefix=self._test_name,
1163 prefix=self._test_name,
1164 count=len(self.user_group_ids))
1164 count=len(self.user_group_ids))
1165 user_group = self.fixture.create_user_group(group_name, **kwargs)
1165 user_group = self.fixture.create_user_group(group_name, **kwargs)
1166 if auto_cleanup:
1166 if auto_cleanup:
1167 self.user_group_ids.append(user_group.users_group_id)
1167 self.user_group_ids.append(user_group.users_group_id)
1168 if members:
1168 if members:
1169 for user in members:
1169 for user in members:
1170 UserGroupModel().add_user_to_group(user_group, user)
1170 UserGroupModel().add_user_to_group(user_group, user)
1171 return user_group
1171 return user_group
1172
1172
1173 def grant_user_permission(self, user_name, permission_name):
1173 def grant_user_permission(self, user_name, permission_name):
1174 self._inherit_default_user_permissions(user_name, False)
1174 self._inherit_default_user_permissions(user_name, False)
1175 self.user_permissions.append((user_name, permission_name))
1175 self.user_permissions.append((user_name, permission_name))
1176
1176
1177 def grant_user_permission_to_repo_group(
1177 def grant_user_permission_to_repo_group(
1178 self, repo_group, user, permission_name):
1178 self, repo_group, user, permission_name):
1179 permission = RepoGroupModel().grant_user_permission(
1179 permission = RepoGroupModel().grant_user_permission(
1180 repo_group, user, permission_name)
1180 repo_group, user, permission_name)
1181 self.user_repo_group_permission_ids.append(
1181 self.user_repo_group_permission_ids.append(
1182 (repo_group.group_id, user.user_id))
1182 (repo_group.group_id, user.user_id))
1183 return permission
1183 return permission
1184
1184
1185 def grant_user_group_permission_to_repo_group(
1185 def grant_user_group_permission_to_repo_group(
1186 self, repo_group, user_group, permission_name):
1186 self, repo_group, user_group, permission_name):
1187 permission = RepoGroupModel().grant_user_group_permission(
1187 permission = RepoGroupModel().grant_user_group_permission(
1188 repo_group, user_group, permission_name)
1188 repo_group, user_group, permission_name)
1189 self.user_group_repo_group_permission_ids.append(
1189 self.user_group_repo_group_permission_ids.append(
1190 (repo_group.group_id, user_group.users_group_id))
1190 (repo_group.group_id, user_group.users_group_id))
1191 return permission
1191 return permission
1192
1192
1193 def grant_user_permission_to_repo(
1193 def grant_user_permission_to_repo(
1194 self, repo, user, permission_name):
1194 self, repo, user, permission_name):
1195 permission = RepoModel().grant_user_permission(
1195 permission = RepoModel().grant_user_permission(
1196 repo, user, permission_name)
1196 repo, user, permission_name)
1197 self.user_repo_permission_ids.append(
1197 self.user_repo_permission_ids.append(
1198 (repo.repo_id, user.user_id))
1198 (repo.repo_id, user.user_id))
1199 return permission
1199 return permission
1200
1200
1201 def grant_user_group_permission_to_repo(
1201 def grant_user_group_permission_to_repo(
1202 self, repo, user_group, permission_name):
1202 self, repo, user_group, permission_name):
1203 permission = RepoModel().grant_user_group_permission(
1203 permission = RepoModel().grant_user_group_permission(
1204 repo, user_group, permission_name)
1204 repo, user_group, permission_name)
1205 self.user_group_repo_permission_ids.append(
1205 self.user_group_repo_permission_ids.append(
1206 (repo.repo_id, user_group.users_group_id))
1206 (repo.repo_id, user_group.users_group_id))
1207 return permission
1207 return permission
1208
1208
1209 def grant_user_permission_to_user_group(
1209 def grant_user_permission_to_user_group(
1210 self, target_user_group, user, permission_name):
1210 self, target_user_group, user, permission_name):
1211 permission = UserGroupModel().grant_user_permission(
1211 permission = UserGroupModel().grant_user_permission(
1212 target_user_group, user, permission_name)
1212 target_user_group, user, permission_name)
1213 self.user_user_group_permission_ids.append(
1213 self.user_user_group_permission_ids.append(
1214 (target_user_group.users_group_id, user.user_id))
1214 (target_user_group.users_group_id, user.user_id))
1215 return permission
1215 return permission
1216
1216
1217 def grant_user_group_permission_to_user_group(
1217 def grant_user_group_permission_to_user_group(
1218 self, target_user_group, user_group, permission_name):
1218 self, target_user_group, user_group, permission_name):
1219 permission = UserGroupModel().grant_user_group_permission(
1219 permission = UserGroupModel().grant_user_group_permission(
1220 target_user_group, user_group, permission_name)
1220 target_user_group, user_group, permission_name)
1221 self.user_group_user_group_permission_ids.append(
1221 self.user_group_user_group_permission_ids.append(
1222 (target_user_group.users_group_id, user_group.users_group_id))
1222 (target_user_group.users_group_id, user_group.users_group_id))
1223 return permission
1223 return permission
1224
1224
1225 def revoke_user_permission(self, user_name, permission_name):
1225 def revoke_user_permission(self, user_name, permission_name):
1226 self._inherit_default_user_permissions(user_name, True)
1226 self._inherit_default_user_permissions(user_name, True)
1227 UserModel().revoke_perm(user_name, permission_name)
1227 UserModel().revoke_perm(user_name, permission_name)
1228
1228
1229 def _inherit_default_user_permissions(self, user_name, value):
1229 def _inherit_default_user_permissions(self, user_name, value):
1230 user = UserModel().get_by_username(user_name)
1230 user = UserModel().get_by_username(user_name)
1231 user.inherit_default_permissions = value
1231 user.inherit_default_permissions = value
1232 Session().add(user)
1232 Session().add(user)
1233 Session().commit()
1233 Session().commit()
1234
1234
1235 def cleanup(self):
1235 def cleanup(self):
1236 self._cleanup_permissions()
1236 self._cleanup_permissions()
1237 self._cleanup_repo_groups()
1237 self._cleanup_repo_groups()
1238 self._cleanup_user_groups()
1238 self._cleanup_user_groups()
1239 self._cleanup_users()
1239 self._cleanup_users()
1240
1240
1241 def _cleanup_permissions(self):
1241 def _cleanup_permissions(self):
1242 if self.user_permissions:
1242 if self.user_permissions:
1243 for user_name, permission_name in self.user_permissions:
1243 for user_name, permission_name in self.user_permissions:
1244 self.revoke_user_permission(user_name, permission_name)
1244 self.revoke_user_permission(user_name, permission_name)
1245
1245
1246 for permission in self.user_repo_permission_ids:
1246 for permission in self.user_repo_permission_ids:
1247 RepoModel().revoke_user_permission(*permission)
1247 RepoModel().revoke_user_permission(*permission)
1248
1248
1249 for permission in self.user_group_repo_permission_ids:
1249 for permission in self.user_group_repo_permission_ids:
1250 RepoModel().revoke_user_group_permission(*permission)
1250 RepoModel().revoke_user_group_permission(*permission)
1251
1251
1252 for permission in self.user_repo_group_permission_ids:
1252 for permission in self.user_repo_group_permission_ids:
1253 RepoGroupModel().revoke_user_permission(*permission)
1253 RepoGroupModel().revoke_user_permission(*permission)
1254
1254
1255 for permission in self.user_group_repo_group_permission_ids:
1255 for permission in self.user_group_repo_group_permission_ids:
1256 RepoGroupModel().revoke_user_group_permission(*permission)
1256 RepoGroupModel().revoke_user_group_permission(*permission)
1257
1257
1258 for permission in self.user_user_group_permission_ids:
1258 for permission in self.user_user_group_permission_ids:
1259 UserGroupModel().revoke_user_permission(*permission)
1259 UserGroupModel().revoke_user_permission(*permission)
1260
1260
1261 for permission in self.user_group_user_group_permission_ids:
1261 for permission in self.user_group_user_group_permission_ids:
1262 UserGroupModel().revoke_user_group_permission(*permission)
1262 UserGroupModel().revoke_user_group_permission(*permission)
1263
1263
1264 def _cleanup_repo_groups(self):
1264 def _cleanup_repo_groups(self):
1265 def _repo_group_compare(first_group_id, second_group_id):
1265 def _repo_group_compare(first_group_id, second_group_id):
1266 """
1266 """
1267 Gives higher priority to the groups with the most complex paths
1267 Gives higher priority to the groups with the most complex paths
1268 """
1268 """
1269 first_group = RepoGroup.get(first_group_id)
1269 first_group = RepoGroup.get(first_group_id)
1270 second_group = RepoGroup.get(second_group_id)
1270 second_group = RepoGroup.get(second_group_id)
1271 first_group_parts = (
1271 first_group_parts = (
1272 len(first_group.group_name.split('/')) if first_group else 0)
1272 len(first_group.group_name.split('/')) if first_group else 0)
1273 second_group_parts = (
1273 second_group_parts = (
1274 len(second_group.group_name.split('/')) if second_group else 0)
1274 len(second_group.group_name.split('/')) if second_group else 0)
1275 return cmp(second_group_parts, first_group_parts)
1275 return cmp(second_group_parts, first_group_parts)
1276
1276
1277 sorted_repo_group_ids = sorted(
1277 sorted_repo_group_ids = sorted(
1278 self.repo_group_ids, cmp=_repo_group_compare)
1278 self.repo_group_ids, cmp=_repo_group_compare)
1279 for repo_group_id in sorted_repo_group_ids:
1279 for repo_group_id in sorted_repo_group_ids:
1280 self.fixture.destroy_repo_group(repo_group_id)
1280 self.fixture.destroy_repo_group(repo_group_id)
1281
1281
1282 def _cleanup_user_groups(self):
1282 def _cleanup_user_groups(self):
1283 def _user_group_compare(first_group_id, second_group_id):
1283 def _user_group_compare(first_group_id, second_group_id):
1284 """
1284 """
1285 Gives higher priority to the groups with the most complex paths
1285 Gives higher priority to the groups with the most complex paths
1286 """
1286 """
1287 first_group = UserGroup.get(first_group_id)
1287 first_group = UserGroup.get(first_group_id)
1288 second_group = UserGroup.get(second_group_id)
1288 second_group = UserGroup.get(second_group_id)
1289 first_group_parts = (
1289 first_group_parts = (
1290 len(first_group.users_group_name.split('/'))
1290 len(first_group.users_group_name.split('/'))
1291 if first_group else 0)
1291 if first_group else 0)
1292 second_group_parts = (
1292 second_group_parts = (
1293 len(second_group.users_group_name.split('/'))
1293 len(second_group.users_group_name.split('/'))
1294 if second_group else 0)
1294 if second_group else 0)
1295 return cmp(second_group_parts, first_group_parts)
1295 return cmp(second_group_parts, first_group_parts)
1296
1296
1297 sorted_user_group_ids = sorted(
1297 sorted_user_group_ids = sorted(
1298 self.user_group_ids, cmp=_user_group_compare)
1298 self.user_group_ids, cmp=_user_group_compare)
1299 for user_group_id in sorted_user_group_ids:
1299 for user_group_id in sorted_user_group_ids:
1300 self.fixture.destroy_user_group(user_group_id)
1300 self.fixture.destroy_user_group(user_group_id)
1301
1301
1302 def _cleanup_users(self):
1302 def _cleanup_users(self):
1303 for user_id in self.user_ids:
1303 for user_id in self.user_ids:
1304 self.fixture.destroy_user(user_id)
1304 self.fixture.destroy_user(user_id)
1305
1305
1306
1306
1307 # TODO: Think about moving this into a pytest-pyro package and make it a
1307 # TODO: Think about moving this into a pytest-pyro package and make it a
1308 # pytest plugin
1308 # pytest plugin
1309 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1309 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1310 def pytest_runtest_makereport(item, call):
1310 def pytest_runtest_makereport(item, call):
1311 """
1311 """
1312 Adding the remote traceback if the exception has this information.
1312 Adding the remote traceback if the exception has this information.
1313
1313
1314 Pyro4 attaches this information as the attribute `_pyroTraceback`
1314 Pyro4 attaches this information as the attribute `_pyroTraceback`
1315 to the exception instance.
1315 to the exception instance.
1316 """
1316 """
1317 outcome = yield
1317 outcome = yield
1318 report = outcome.get_result()
1318 report = outcome.get_result()
1319 if call.excinfo:
1319 if call.excinfo:
1320 _add_pyro_remote_traceback(report, call.excinfo.value)
1320 _add_pyro_remote_traceback(report, call.excinfo.value)
1321
1321
1322
1322
1323 def _add_pyro_remote_traceback(report, exc):
1323 def _add_pyro_remote_traceback(report, exc):
1324 pyro_traceback = getattr(exc, '_pyroTraceback', None)
1324 pyro_traceback = getattr(exc, '_pyroTraceback', None)
1325
1325
1326 if pyro_traceback:
1326 if pyro_traceback:
1327 traceback = ''.join(pyro_traceback)
1327 traceback = ''.join(pyro_traceback)
1328 section = 'Pyro4 remote traceback ' + report.when
1328 section = 'Pyro4 remote traceback ' + report.when
1329 report.sections.append((section, traceback))
1329 report.sections.append((section, traceback))
1330
1330
1331
1331
1332 @pytest.fixture(scope='session')
1332 @pytest.fixture(scope='session')
1333 def testrun():
1333 def testrun():
1334 return {
1334 return {
1335 'uuid': uuid.uuid4(),
1335 'uuid': uuid.uuid4(),
1336 'start': datetime.datetime.utcnow().isoformat(),
1336 'start': datetime.datetime.utcnow().isoformat(),
1337 'timestamp': int(time.time()),
1337 'timestamp': int(time.time()),
1338 }
1338 }
1339
1339
1340
1340
1341 @pytest.fixture(autouse=True)
1341 @pytest.fixture(autouse=True)
1342 def collect_appenlight_stats(request, testrun):
1342 def collect_appenlight_stats(request, testrun):
1343 """
1343 """
1344 This fixture reports memory consumtion of single tests.
1344 This fixture reports memory consumtion of single tests.
1345
1345
1346 It gathers data based on `psutil` and sends them to Appenlight. The option
1346 It gathers data based on `psutil` and sends them to Appenlight. The option
1347 ``--ae`` has te be used to enable this fixture and the API key for your
1347 ``--ae`` has te be used to enable this fixture and the API key for your
1348 application has to be provided in ``--ae-key``.
1348 application has to be provided in ``--ae-key``.
1349 """
1349 """
1350 try:
1350 try:
1351 # cygwin cannot have yet psutil support.
1351 # cygwin cannot have yet psutil support.
1352 import psutil
1352 import psutil
1353 except ImportError:
1353 except ImportError:
1354 return
1354 return
1355
1355
1356 if not request.config.getoption('--appenlight'):
1356 if not request.config.getoption('--appenlight'):
1357 return
1357 return
1358 else:
1358 else:
1359 # Only request the pylonsapp fixture if appenlight tracking is
1359 # Only request the pylonsapp fixture if appenlight tracking is
1360 # enabled. This will speed up a test run of unit tests by 2 to 3
1360 # enabled. This will speed up a test run of unit tests by 2 to 3
1361 # seconds if appenlight is not enabled.
1361 # seconds if appenlight is not enabled.
1362 pylonsapp = request.getfuncargvalue("pylonsapp")
1362 pylonsapp = request.getfuncargvalue("pylonsapp")
1363 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1363 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1364 client = AppenlightClient(
1364 client = AppenlightClient(
1365 url=url,
1365 url=url,
1366 api_key=request.config.getoption('--appenlight-api-key'),
1366 api_key=request.config.getoption('--appenlight-api-key'),
1367 namespace=request.node.nodeid,
1367 namespace=request.node.nodeid,
1368 request=str(testrun['uuid']),
1368 request=str(testrun['uuid']),
1369 testrun=testrun)
1369 testrun=testrun)
1370
1370
1371 client.collect({
1371 client.collect({
1372 'message': "Starting",
1372 'message': "Starting",
1373 })
1373 })
1374
1374
1375 server_and_port = pylonsapp.config['vcs.server']
1375 server_and_port = pylonsapp.config['vcs.server']
1376 server = create_vcsserver_proxy(server_and_port)
1376 server = create_vcsserver_proxy(server_and_port)
1377 with server:
1377 with server:
1378 vcs_pid = server.get_pid()
1378 vcs_pid = server.get_pid()
1379 server.run_gc()
1379 server.run_gc()
1380 vcs_process = psutil.Process(vcs_pid)
1380 vcs_process = psutil.Process(vcs_pid)
1381 mem = vcs_process.memory_info()
1381 mem = vcs_process.memory_info()
1382 client.tag_before('vcsserver.rss', mem.rss)
1382 client.tag_before('vcsserver.rss', mem.rss)
1383 client.tag_before('vcsserver.vms', mem.vms)
1383 client.tag_before('vcsserver.vms', mem.vms)
1384
1384
1385 test_process = psutil.Process()
1385 test_process = psutil.Process()
1386 mem = test_process.memory_info()
1386 mem = test_process.memory_info()
1387 client.tag_before('test.rss', mem.rss)
1387 client.tag_before('test.rss', mem.rss)
1388 client.tag_before('test.vms', mem.vms)
1388 client.tag_before('test.vms', mem.vms)
1389
1389
1390 client.tag_before('time', time.time())
1390 client.tag_before('time', time.time())
1391
1391
1392 @request.addfinalizer
1392 @request.addfinalizer
1393 def send_stats():
1393 def send_stats():
1394 client.tag_after('time', time.time())
1394 client.tag_after('time', time.time())
1395 with server:
1395 with server:
1396 gc_stats = server.run_gc()
1396 gc_stats = server.run_gc()
1397 for tag, value in gc_stats.items():
1397 for tag, value in gc_stats.items():
1398 client.tag_after(tag, value)
1398 client.tag_after(tag, value)
1399 mem = vcs_process.memory_info()
1399 mem = vcs_process.memory_info()
1400 client.tag_after('vcsserver.rss', mem.rss)
1400 client.tag_after('vcsserver.rss', mem.rss)
1401 client.tag_after('vcsserver.vms', mem.vms)
1401 client.tag_after('vcsserver.vms', mem.vms)
1402
1402
1403 mem = test_process.memory_info()
1403 mem = test_process.memory_info()
1404 client.tag_after('test.rss', mem.rss)
1404 client.tag_after('test.rss', mem.rss)
1405 client.tag_after('test.vms', mem.vms)
1405 client.tag_after('test.vms', mem.vms)
1406
1406
1407 client.collect({
1407 client.collect({
1408 'message': "Finished",
1408 'message': "Finished",
1409 })
1409 })
1410 client.send_stats()
1410 client.send_stats()
1411
1411
1412 return client
1412 return client
1413
1413
1414
1414
1415 class AppenlightClient():
1415 class AppenlightClient():
1416
1416
1417 url_template = '{url}?protocol_version=0.5'
1417 url_template = '{url}?protocol_version=0.5'
1418
1418
1419 def __init__(
1419 def __init__(
1420 self, url, api_key, add_server=True, add_timestamp=True,
1420 self, url, api_key, add_server=True, add_timestamp=True,
1421 namespace=None, request=None, testrun=None):
1421 namespace=None, request=None, testrun=None):
1422 self.url = self.url_template.format(url=url)
1422 self.url = self.url_template.format(url=url)
1423 self.api_key = api_key
1423 self.api_key = api_key
1424 self.add_server = add_server
1424 self.add_server = add_server
1425 self.add_timestamp = add_timestamp
1425 self.add_timestamp = add_timestamp
1426 self.namespace = namespace
1426 self.namespace = namespace
1427 self.request = request
1427 self.request = request
1428 self.server = socket.getfqdn(socket.gethostname())
1428 self.server = socket.getfqdn(socket.gethostname())
1429 self.tags_before = {}
1429 self.tags_before = {}
1430 self.tags_after = {}
1430 self.tags_after = {}
1431 self.stats = []
1431 self.stats = []
1432 self.testrun = testrun or {}
1432 self.testrun = testrun or {}
1433
1433
1434 def tag_before(self, tag, value):
1434 def tag_before(self, tag, value):
1435 self.tags_before[tag] = value
1435 self.tags_before[tag] = value
1436
1436
1437 def tag_after(self, tag, value):
1437 def tag_after(self, tag, value):
1438 self.tags_after[tag] = value
1438 self.tags_after[tag] = value
1439
1439
1440 def collect(self, data):
1440 def collect(self, data):
1441 if self.add_server:
1441 if self.add_server:
1442 data.setdefault('server', self.server)
1442 data.setdefault('server', self.server)
1443 if self.add_timestamp:
1443 if self.add_timestamp:
1444 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1444 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1445 if self.namespace:
1445 if self.namespace:
1446 data.setdefault('namespace', self.namespace)
1446 data.setdefault('namespace', self.namespace)
1447 if self.request:
1447 if self.request:
1448 data.setdefault('request', self.request)
1448 data.setdefault('request', self.request)
1449 self.stats.append(data)
1449 self.stats.append(data)
1450
1450
1451 def send_stats(self):
1451 def send_stats(self):
1452 tags = [
1452 tags = [
1453 ('testrun', self.request),
1453 ('testrun', self.request),
1454 ('testrun.start', self.testrun['start']),
1454 ('testrun.start', self.testrun['start']),
1455 ('testrun.timestamp', self.testrun['timestamp']),
1455 ('testrun.timestamp', self.testrun['timestamp']),
1456 ('test', self.namespace),
1456 ('test', self.namespace),
1457 ]
1457 ]
1458 for key, value in self.tags_before.items():
1458 for key, value in self.tags_before.items():
1459 tags.append((key + '.before', value))
1459 tags.append((key + '.before', value))
1460 try:
1460 try:
1461 delta = self.tags_after[key] - value
1461 delta = self.tags_after[key] - value
1462 tags.append((key + '.delta', delta))
1462 tags.append((key + '.delta', delta))
1463 except Exception:
1463 except Exception:
1464 pass
1464 pass
1465 for key, value in self.tags_after.items():
1465 for key, value in self.tags_after.items():
1466 tags.append((key + '.after', value))
1466 tags.append((key + '.after', value))
1467 self.collect({
1467 self.collect({
1468 'message': "Collected tags",
1468 'message': "Collected tags",
1469 'tags': tags,
1469 'tags': tags,
1470 })
1470 })
1471
1471
1472 response = requests.post(
1472 response = requests.post(
1473 self.url,
1473 self.url,
1474 headers={
1474 headers={
1475 'X-appenlight-api-key': self.api_key},
1475 'X-appenlight-api-key': self.api_key},
1476 json=self.stats,
1476 json=self.stats,
1477 )
1477 )
1478
1478
1479 if not response.status_code == 200:
1479 if not response.status_code == 200:
1480 pprint.pprint(self.stats)
1480 pprint.pprint(self.stats)
1481 print response.headers
1481 print response.headers
1482 print response.text
1482 print response.text
1483 raise Exception('Sending to appenlight failed')
1483 raise Exception('Sending to appenlight failed')
1484
1484
1485
1485
1486 @pytest.fixture
1486 @pytest.fixture
1487 def gist_util(request, pylonsapp):
1487 def gist_util(request, pylonsapp):
1488 """
1488 """
1489 Provides a wired instance of `GistUtility` with integrated cleanup.
1489 Provides a wired instance of `GistUtility` with integrated cleanup.
1490 """
1490 """
1491 utility = GistUtility()
1491 utility = GistUtility()
1492 request.addfinalizer(utility.cleanup)
1492 request.addfinalizer(utility.cleanup)
1493 return utility
1493 return utility
1494
1494
1495
1495
1496 class GistUtility(object):
1496 class GistUtility(object):
1497 def __init__(self):
1497 def __init__(self):
1498 self.fixture = Fixture()
1498 self.fixture = Fixture()
1499 self.gist_ids = []
1499 self.gist_ids = []
1500
1500
1501 def create_gist(self, **kwargs):
1501 def create_gist(self, **kwargs):
1502 gist = self.fixture.create_gist(**kwargs)
1502 gist = self.fixture.create_gist(**kwargs)
1503 self.gist_ids.append(gist.gist_id)
1503 self.gist_ids.append(gist.gist_id)
1504 return gist
1504 return gist
1505
1505
1506 def cleanup(self):
1506 def cleanup(self):
1507 for id_ in self.gist_ids:
1507 for id_ in self.gist_ids:
1508 self.fixture.destroy_gists(str(id_))
1508 self.fixture.destroy_gists(str(id_))
1509
1509
1510
1510
1511 @pytest.fixture
1511 @pytest.fixture
1512 def enabled_backends(request):
1512 def enabled_backends(request):
1513 backends = request.config.option.backends
1513 backends = request.config.option.backends
1514 return backends[:]
1514 return backends[:]
1515
1515
1516
1516
1517 @pytest.fixture
1517 @pytest.fixture
1518 def settings_util(request):
1518 def settings_util(request):
1519 """
1519 """
1520 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1520 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1521 """
1521 """
1522 utility = SettingsUtility()
1522 utility = SettingsUtility()
1523 request.addfinalizer(utility.cleanup)
1523 request.addfinalizer(utility.cleanup)
1524 return utility
1524 return utility
1525
1525
1526
1526
1527 class SettingsUtility(object):
1527 class SettingsUtility(object):
1528 def __init__(self):
1528 def __init__(self):
1529 self.rhodecode_ui_ids = []
1529 self.rhodecode_ui_ids = []
1530 self.rhodecode_setting_ids = []
1530 self.rhodecode_setting_ids = []
1531 self.repo_rhodecode_ui_ids = []
1531 self.repo_rhodecode_ui_ids = []
1532 self.repo_rhodecode_setting_ids = []
1532 self.repo_rhodecode_setting_ids = []
1533
1533
1534 def create_repo_rhodecode_ui(
1534 def create_repo_rhodecode_ui(
1535 self, repo, section, value, key=None, active=True, cleanup=True):
1535 self, repo, section, value, key=None, active=True, cleanup=True):
1536 key = key or hashlib.sha1(
1536 key = key or hashlib.sha1(
1537 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1537 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1538
1538
1539 setting = RepoRhodeCodeUi()
1539 setting = RepoRhodeCodeUi()
1540 setting.repository_id = repo.repo_id
1540 setting.repository_id = repo.repo_id
1541 setting.ui_section = section
1541 setting.ui_section = section
1542 setting.ui_value = value
1542 setting.ui_value = value
1543 setting.ui_key = key
1543 setting.ui_key = key
1544 setting.ui_active = active
1544 setting.ui_active = active
1545 Session().add(setting)
1545 Session().add(setting)
1546 Session().commit()
1546 Session().commit()
1547
1547
1548 if cleanup:
1548 if cleanup:
1549 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1549 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1550 return setting
1550 return setting
1551
1551
1552 def create_rhodecode_ui(
1552 def create_rhodecode_ui(
1553 self, section, value, key=None, active=True, cleanup=True):
1553 self, section, value, key=None, active=True, cleanup=True):
1554 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1554 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1555
1555
1556 setting = RhodeCodeUi()
1556 setting = RhodeCodeUi()
1557 setting.ui_section = section
1557 setting.ui_section = section
1558 setting.ui_value = value
1558 setting.ui_value = value
1559 setting.ui_key = key
1559 setting.ui_key = key
1560 setting.ui_active = active
1560 setting.ui_active = active
1561 Session().add(setting)
1561 Session().add(setting)
1562 Session().commit()
1562 Session().commit()
1563
1563
1564 if cleanup:
1564 if cleanup:
1565 self.rhodecode_ui_ids.append(setting.ui_id)
1565 self.rhodecode_ui_ids.append(setting.ui_id)
1566 return setting
1566 return setting
1567
1567
1568 def create_repo_rhodecode_setting(
1568 def create_repo_rhodecode_setting(
1569 self, repo, name, value, type_, cleanup=True):
1569 self, repo, name, value, type_, cleanup=True):
1570 setting = RepoRhodeCodeSetting(
1570 setting = RepoRhodeCodeSetting(
1571 repo.repo_id, key=name, val=value, type=type_)
1571 repo.repo_id, key=name, val=value, type=type_)
1572 Session().add(setting)
1572 Session().add(setting)
1573 Session().commit()
1573 Session().commit()
1574
1574
1575 if cleanup:
1575 if cleanup:
1576 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1576 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1577 return setting
1577 return setting
1578
1578
1579 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1579 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1580 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1580 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1581 Session().add(setting)
1581 Session().add(setting)
1582 Session().commit()
1582 Session().commit()
1583
1583
1584 if cleanup:
1584 if cleanup:
1585 self.rhodecode_setting_ids.append(setting.app_settings_id)
1585 self.rhodecode_setting_ids.append(setting.app_settings_id)
1586
1586
1587 return setting
1587 return setting
1588
1588
1589 def cleanup(self):
1589 def cleanup(self):
1590 for id_ in self.rhodecode_ui_ids:
1590 for id_ in self.rhodecode_ui_ids:
1591 setting = RhodeCodeUi.get(id_)
1591 setting = RhodeCodeUi.get(id_)
1592 Session().delete(setting)
1592 Session().delete(setting)
1593
1593
1594 for id_ in self.rhodecode_setting_ids:
1594 for id_ in self.rhodecode_setting_ids:
1595 setting = RhodeCodeSetting.get(id_)
1595 setting = RhodeCodeSetting.get(id_)
1596 Session().delete(setting)
1596 Session().delete(setting)
1597
1597
1598 for id_ in self.repo_rhodecode_ui_ids:
1598 for id_ in self.repo_rhodecode_ui_ids:
1599 setting = RepoRhodeCodeUi.get(id_)
1599 setting = RepoRhodeCodeUi.get(id_)
1600 Session().delete(setting)
1600 Session().delete(setting)
1601
1601
1602 for id_ in self.repo_rhodecode_setting_ids:
1602 for id_ in self.repo_rhodecode_setting_ids:
1603 setting = RepoRhodeCodeSetting.get(id_)
1603 setting = RepoRhodeCodeSetting.get(id_)
1604 Session().delete(setting)
1604 Session().delete(setting)
1605
1605
1606 Session().commit()
1606 Session().commit()
1607
1607
1608
1608
1609 @pytest.fixture
1609 @pytest.fixture
1610 def no_notifications(request):
1610 def no_notifications(request):
1611 notification_patcher = mock.patch(
1611 notification_patcher = mock.patch(
1612 'rhodecode.model.notification.NotificationModel.create')
1612 'rhodecode.model.notification.NotificationModel.create')
1613 notification_patcher.start()
1613 notification_patcher.start()
1614 request.addfinalizer(notification_patcher.stop)
1614 request.addfinalizer(notification_patcher.stop)
1615
1615
1616
1616
1617 @pytest.fixture
1617 @pytest.fixture
1618 def silence_action_logger(request):
1618 def silence_action_logger(request):
1619 notification_patcher = mock.patch(
1619 notification_patcher = mock.patch(
1620 'rhodecode.lib.utils.action_logger')
1620 'rhodecode.lib.utils.action_logger')
1621 notification_patcher.start()
1621 notification_patcher.start()
1622 request.addfinalizer(notification_patcher.stop)
1622 request.addfinalizer(notification_patcher.stop)
1623
1623
1624
1624
1625 @pytest.fixture(scope='session')
1625 @pytest.fixture(scope='session')
1626 def repeat(request):
1626 def repeat(request):
1627 """
1627 """
1628 The number of repetitions is based on this fixture.
1628 The number of repetitions is based on this fixture.
1629
1629
1630 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1630 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1631 tests are not too slow in our default test suite.
1631 tests are not too slow in our default test suite.
1632 """
1632 """
1633 return request.config.getoption('--repeat')
1633 return request.config.getoption('--repeat')
1634
1634
1635
1635
1636 @pytest.fixture
1636 @pytest.fixture
1637 def rhodecode_fixtures():
1637 def rhodecode_fixtures():
1638 return Fixture()
1638 return Fixture()
1639
1639
1640
1640
1641 @pytest.fixture
1641 @pytest.fixture
1642 def request_stub():
1642 def request_stub():
1643 """
1643 """
1644 Stub request object.
1644 Stub request object.
1645 """
1645 """
1646 request = pyramid.testing.DummyRequest()
1646 request = pyramid.testing.DummyRequest()
1647 request.scheme = 'https'
1647 request.scheme = 'https'
1648 return request
1648 return request
1649
1649
1650
1650
1651 @pytest.fixture
1651 @pytest.fixture
1652 def config_stub(request, request_stub):
1652 def config_stub(request, request_stub):
1653 """
1653 """
1654 Set up pyramid.testing and return the Configurator.
1654 Set up pyramid.testing and return the Configurator.
1655 """
1655 """
1656 config = pyramid.testing.setUp(request=request_stub)
1656 config = pyramid.testing.setUp(request=request_stub)
1657
1657
1658 @request.addfinalizer
1658 @request.addfinalizer
1659 def cleanup():
1659 def cleanup():
1660 pyramid.testing.tearDown()
1660 pyramid.testing.tearDown()
1661
1661
1662 return config
1662 return config
1663
1663
1664
1664
1665 @pytest.fixture
1665 @pytest.fixture
1666 def StubIntegrationType():
1666 def StubIntegrationType():
1667 class _StubIntegrationType(IntegrationTypeBase):
1667 class _StubIntegrationType(IntegrationTypeBase):
1668 """ Test integration type class """
1668 """ Test integration type class """
1669
1669
1670 key = 'test'
1670 key = 'test'
1671 display_name = 'Test integration type'
1671 display_name = 'Test integration type'
1672 description = 'A test integration type for testing'
1672 description = 'A test integration type for testing'
1673 icon = 'test_icon_html_image'
1673 icon = 'test_icon_html_image'
1674
1674
1675 def __init__(self, settings):
1675 def __init__(self, settings):
1676 super(_StubIntegrationType, self).__init__(settings)
1676 super(_StubIntegrationType, self).__init__(settings)
1677 self.sent_events = [] # for testing
1677 self.sent_events = [] # for testing
1678
1678
1679 def send_event(self, event):
1679 def send_event(self, event):
1680 self.sent_events.append(event)
1680 self.sent_events.append(event)
1681
1681
1682 def settings_schema(self):
1682 def settings_schema(self):
1683 class SettingsSchema(colander.Schema):
1683 class SettingsSchema(colander.Schema):
1684 test_string_field = colander.SchemaNode(
1684 test_string_field = colander.SchemaNode(
1685 colander.String(),
1685 colander.String(),
1686 missing=colander.required,
1686 missing=colander.required,
1687 title='test string field',
1687 title='test string field',
1688 )
1688 )
1689 test_int_field = colander.SchemaNode(
1689 test_int_field = colander.SchemaNode(
1690 colander.Int(),
1690 colander.Int(),
1691 title='some integer setting',
1691 title='some integer setting',
1692 )
1692 )
1693 return SettingsSchema()
1693 return SettingsSchema()
1694
1694
1695
1695
1696 integration_type_registry.register_integration_type(_StubIntegrationType)
1696 integration_type_registry.register_integration_type(_StubIntegrationType)
1697 return _StubIntegrationType
1697 return _StubIntegrationType
1698
1698
1699 @pytest.fixture
1699 @pytest.fixture
1700 def stub_integration_settings():
1700 def stub_integration_settings():
1701 return {
1701 return {
1702 'test_string_field': 'some data',
1702 'test_string_field': 'some data',
1703 'test_int_field': 100,
1703 'test_int_field': 100,
1704 }
1704 }
1705
1705
1706
1706
1707 @pytest.fixture
1707 @pytest.fixture
1708 def repo_integration_stub(request, repo_stub, StubIntegrationType,
1708 def repo_integration_stub(request, repo_stub, StubIntegrationType,
1709 stub_integration_settings):
1709 stub_integration_settings):
1710 integration = IntegrationModel().create(
1710 integration = IntegrationModel().create(
1711 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1711 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1712 name='test repo integration', scope=repo_stub)
1712 name='test repo integration',
1713 repo=repo_stub, repo_group=None, child_repos_only=None)
1713
1714
1714 @request.addfinalizer
1715 @request.addfinalizer
1715 def cleanup():
1716 def cleanup():
1716 IntegrationModel().delete(integration)
1717 IntegrationModel().delete(integration)
1717
1718
1718 return integration
1719 return integration
1719
1720
1720
1721
1721 @pytest.fixture
1722 @pytest.fixture
1722 def repogroup_integration_stub(request, test_repo_group, StubIntegrationType,
1723 def repogroup_integration_stub(request, test_repo_group, StubIntegrationType,
1723 stub_integration_settings):
1724 stub_integration_settings):
1724 integration = IntegrationModel().create(
1725 integration = IntegrationModel().create(
1725 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1726 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1726 name='test repogroup integration', scope=test_repo_group)
1727 name='test repogroup integration',
1728 repo=None, repo_group=test_repo_group, child_repos_only=True)
1729
1730 @request.addfinalizer
1731 def cleanup():
1732 IntegrationModel().delete(integration)
1733
1734 return integration
1735
1736
1737 @pytest.fixture
1738 def repogroup_recursive_integration_stub(request, test_repo_group,
1739 StubIntegrationType, stub_integration_settings):
1740 integration = IntegrationModel().create(
1741 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1742 name='test recursive repogroup integration',
1743 repo=None, repo_group=test_repo_group, child_repos_only=False)
1727
1744
1728 @request.addfinalizer
1745 @request.addfinalizer
1729 def cleanup():
1746 def cleanup():
1730 IntegrationModel().delete(integration)
1747 IntegrationModel().delete(integration)
1731
1748
1732 return integration
1749 return integration
1733
1750
1734
1751
1735 @pytest.fixture
1752 @pytest.fixture
1736 def global_integration_stub(request, StubIntegrationType,
1753 def global_integration_stub(request, StubIntegrationType,
1737 stub_integration_settings):
1754 stub_integration_settings):
1738 integration = IntegrationModel().create(
1755 integration = IntegrationModel().create(
1739 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1756 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1740 name='test global integration', scope='global')
1757 name='test global integration',
1758 repo=None, repo_group=None, child_repos_only=None)
1741
1759
1742 @request.addfinalizer
1760 @request.addfinalizer
1743 def cleanup():
1761 def cleanup():
1744 IntegrationModel().delete(integration)
1762 IntegrationModel().delete(integration)
1745
1763
1746 return integration
1764 return integration
1747
1765
1748
1766
1749 @pytest.fixture
1767 @pytest.fixture
1750 def root_repos_integration_stub(request, StubIntegrationType,
1768 def root_repos_integration_stub(request, StubIntegrationType,
1751 stub_integration_settings):
1769 stub_integration_settings):
1752 integration = IntegrationModel().create(
1770 integration = IntegrationModel().create(
1753 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1771 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1754 name='test global integration', scope='root_repos')
1772 name='test global integration',
1773 repo=None, repo_group=None, child_repos_only=True)
1755
1774
1756 @request.addfinalizer
1775 @request.addfinalizer
1757 def cleanup():
1776 def cleanup():
1758 IntegrationModel().delete(integration)
1777 IntegrationModel().delete(integration)
1759
1778
1760 return integration
1779 return integration
General Comments 0
You need to be logged in to leave comments. Login now