##// END OF EJS Templates
hooks: deprecate old action_logger
marcink -
r1754:0436d3e6 default
parent child Browse files
Show More
@@ -1,421 +1,413 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Set of hooks run by RhodeCode Enterprise
23 Set of hooks run by RhodeCode Enterprise
24 """
24 """
25
25
26 import os
26 import os
27 import collections
27 import collections
28 import logging
28 import logging
29
29
30 import rhodecode
30 import rhodecode
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib import audit_logger
33 from rhodecode.lib import audit_logger
34 from rhodecode.lib.utils import action_logger
35 from rhodecode.lib.utils2 import safe_str
34 from rhodecode.lib.utils2 import safe_str
36 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
35 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
37 from rhodecode.model.db import Repository, User
36 from rhodecode.model.db import Repository, User
38
37
39 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
40
39
41
40
42 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
41 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
43
42
44
43
45 def is_shadow_repo(extras):
44 def is_shadow_repo(extras):
46 """
45 """
47 Returns ``True`` if this is an action executed against a shadow repository.
46 Returns ``True`` if this is an action executed against a shadow repository.
48 """
47 """
49 return extras['is_shadow_repo']
48 return extras['is_shadow_repo']
50
49
51
50
52 def _get_scm_size(alias, root_path):
51 def _get_scm_size(alias, root_path):
53
52
54 if not alias.startswith('.'):
53 if not alias.startswith('.'):
55 alias += '.'
54 alias += '.'
56
55
57 size_scm, size_root = 0, 0
56 size_scm, size_root = 0, 0
58 for path, unused_dirs, files in os.walk(safe_str(root_path)):
57 for path, unused_dirs, files in os.walk(safe_str(root_path)):
59 if path.find(alias) != -1:
58 if path.find(alias) != -1:
60 for f in files:
59 for f in files:
61 try:
60 try:
62 size_scm += os.path.getsize(os.path.join(path, f))
61 size_scm += os.path.getsize(os.path.join(path, f))
63 except OSError:
62 except OSError:
64 pass
63 pass
65 else:
64 else:
66 for f in files:
65 for f in files:
67 try:
66 try:
68 size_root += os.path.getsize(os.path.join(path, f))
67 size_root += os.path.getsize(os.path.join(path, f))
69 except OSError:
68 except OSError:
70 pass
69 pass
71
70
72 size_scm_f = h.format_byte_size_binary(size_scm)
71 size_scm_f = h.format_byte_size_binary(size_scm)
73 size_root_f = h.format_byte_size_binary(size_root)
72 size_root_f = h.format_byte_size_binary(size_root)
74 size_total_f = h.format_byte_size_binary(size_root + size_scm)
73 size_total_f = h.format_byte_size_binary(size_root + size_scm)
75
74
76 return size_scm_f, size_root_f, size_total_f
75 return size_scm_f, size_root_f, size_total_f
77
76
78
77
79 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
78 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
80 def repo_size(extras):
79 def repo_size(extras):
81 """Present size of repository after push."""
80 """Present size of repository after push."""
82 repo = Repository.get_by_repo_name(extras.repository)
81 repo = Repository.get_by_repo_name(extras.repository)
83 vcs_part = safe_str(u'.%s' % repo.repo_type)
82 vcs_part = safe_str(u'.%s' % repo.repo_type)
84 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
83 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
85 repo.repo_full_path)
84 repo.repo_full_path)
86 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
85 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
87 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
86 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
88 return HookResponse(0, msg)
87 return HookResponse(0, msg)
89
88
90
89
91 def pre_push(extras):
90 def pre_push(extras):
92 """
91 """
93 Hook executed before pushing code.
92 Hook executed before pushing code.
94
93
95 It bans pushing when the repository is locked.
94 It bans pushing when the repository is locked.
96 """
95 """
97
96
98 usr = User.get_by_username(extras.username)
97 usr = User.get_by_username(extras.username)
99 output = ''
98 output = ''
100 if extras.locked_by[0] and usr.user_id != int(extras.locked_by[0]):
99 if extras.locked_by[0] and usr.user_id != int(extras.locked_by[0]):
101 locked_by = User.get(extras.locked_by[0]).username
100 locked_by = User.get(extras.locked_by[0]).username
102 reason = extras.locked_by[2]
101 reason = extras.locked_by[2]
103 # this exception is interpreted in git/hg middlewares and based
102 # this exception is interpreted in git/hg middlewares and based
104 # on that proper return code is server to client
103 # on that proper return code is server to client
105 _http_ret = HTTPLockedRC(
104 _http_ret = HTTPLockedRC(
106 _locked_by_explanation(extras.repository, locked_by, reason))
105 _locked_by_explanation(extras.repository, locked_by, reason))
107 if str(_http_ret.code).startswith('2'):
106 if str(_http_ret.code).startswith('2'):
108 # 2xx Codes don't raise exceptions
107 # 2xx Codes don't raise exceptions
109 output = _http_ret.title
108 output = _http_ret.title
110 else:
109 else:
111 raise _http_ret
110 raise _http_ret
112
111
113 # Propagate to external components. This is done after checking the
112 # Propagate to external components. This is done after checking the
114 # lock, for consistent behavior.
113 # lock, for consistent behavior.
115 if not is_shadow_repo(extras):
114 if not is_shadow_repo(extras):
116 pre_push_extension(repo_store_path=Repository.base_path(), **extras)
115 pre_push_extension(repo_store_path=Repository.base_path(), **extras)
117 events.trigger(events.RepoPrePushEvent(
116 events.trigger(events.RepoPrePushEvent(
118 repo_name=extras.repository, extras=extras))
117 repo_name=extras.repository, extras=extras))
119
118
120 return HookResponse(0, output)
119 return HookResponse(0, output)
121
120
122
121
123 def pre_pull(extras):
122 def pre_pull(extras):
124 """
123 """
125 Hook executed before pulling the code.
124 Hook executed before pulling the code.
126
125
127 It bans pulling when the repository is locked.
126 It bans pulling when the repository is locked.
128 """
127 """
129
128
130 output = ''
129 output = ''
131 if extras.locked_by[0]:
130 if extras.locked_by[0]:
132 locked_by = User.get(extras.locked_by[0]).username
131 locked_by = User.get(extras.locked_by[0]).username
133 reason = extras.locked_by[2]
132 reason = extras.locked_by[2]
134 # this exception is interpreted in git/hg middlewares and based
133 # this exception is interpreted in git/hg middlewares and based
135 # on that proper return code is server to client
134 # on that proper return code is server to client
136 _http_ret = HTTPLockedRC(
135 _http_ret = HTTPLockedRC(
137 _locked_by_explanation(extras.repository, locked_by, reason))
136 _locked_by_explanation(extras.repository, locked_by, reason))
138 if str(_http_ret.code).startswith('2'):
137 if str(_http_ret.code).startswith('2'):
139 # 2xx Codes don't raise exceptions
138 # 2xx Codes don't raise exceptions
140 output = _http_ret.title
139 output = _http_ret.title
141 else:
140 else:
142 raise _http_ret
141 raise _http_ret
143
142
144 # Propagate to external components. This is done after checking the
143 # Propagate to external components. This is done after checking the
145 # lock, for consistent behavior.
144 # lock, for consistent behavior.
146 if not is_shadow_repo(extras):
145 if not is_shadow_repo(extras):
147 pre_pull_extension(**extras)
146 pre_pull_extension(**extras)
148 events.trigger(events.RepoPrePullEvent(
147 events.trigger(events.RepoPrePullEvent(
149 repo_name=extras.repository, extras=extras))
148 repo_name=extras.repository, extras=extras))
150
149
151 return HookResponse(0, output)
150 return HookResponse(0, output)
152
151
153
152
154 def post_pull(extras):
153 def post_pull(extras):
155 """Hook executed after client pulls the code."""
154 """Hook executed after client pulls the code."""
156 user = User.get_by_username(extras.username)
157 action = 'pull'
158 action_logger(user, action, extras.repository, extras.ip, commit=True)
159
155
160 audit_user = audit_logger.UserWrap(
156 audit_user = audit_logger.UserWrap(
161 username=extras.username,
157 username=extras.username,
162 ip_addr=extras.ip)
158 ip_addr=extras.ip)
163 repo = audit_logger.RepoWrap(repo_name=extras.repository)
159 repo = audit_logger.RepoWrap(repo_name=extras.repository)
164 audit_logger.store(
160 audit_logger.store(
165 action='user.pull', action_data={
161 action='user.pull', action_data={
166 'user_agent': extras.user_agent},
162 'user_agent': extras.user_agent},
167 user=audit_user, repo=repo, commit=True)
163 user=audit_user, repo=repo, commit=True)
168
164
169 # Propagate to external components.
165 # Propagate to external components.
170 if not is_shadow_repo(extras):
166 if not is_shadow_repo(extras):
171 post_pull_extension(**extras)
167 post_pull_extension(**extras)
172 events.trigger(events.RepoPullEvent(
168 events.trigger(events.RepoPullEvent(
173 repo_name=extras.repository, extras=extras))
169 repo_name=extras.repository, extras=extras))
174
170
175 output = ''
171 output = ''
176 # make lock is a tri state False, True, None. We only make lock on True
172 # make lock is a tri state False, True, None. We only make lock on True
177 if extras.make_lock is True and not is_shadow_repo(extras):
173 if extras.make_lock is True and not is_shadow_repo(extras):
174 user = User.get_by_username(extras.username)
178 Repository.lock(Repository.get_by_repo_name(extras.repository),
175 Repository.lock(Repository.get_by_repo_name(extras.repository),
179 user.user_id,
176 user.user_id,
180 lock_reason=Repository.LOCK_PULL)
177 lock_reason=Repository.LOCK_PULL)
181 msg = 'Made lock on repo `%s`' % (extras.repository,)
178 msg = 'Made lock on repo `%s`' % (extras.repository,)
182 output += msg
179 output += msg
183
180
184 if extras.locked_by[0]:
181 if extras.locked_by[0]:
185 locked_by = User.get(extras.locked_by[0]).username
182 locked_by = User.get(extras.locked_by[0]).username
186 reason = extras.locked_by[2]
183 reason = extras.locked_by[2]
187 _http_ret = HTTPLockedRC(
184 _http_ret = HTTPLockedRC(
188 _locked_by_explanation(extras.repository, locked_by, reason))
185 _locked_by_explanation(extras.repository, locked_by, reason))
189 if str(_http_ret.code).startswith('2'):
186 if str(_http_ret.code).startswith('2'):
190 # 2xx Codes don't raise exceptions
187 # 2xx Codes don't raise exceptions
191 output += _http_ret.title
188 output += _http_ret.title
192
189
193 return HookResponse(0, output)
190 return HookResponse(0, output)
194
191
195
192
196 def post_push(extras):
193 def post_push(extras):
197 """Hook executed after user pushes to the repository."""
194 """Hook executed after user pushes to the repository."""
198 action_tmpl = extras.action + ':%s'
195 commit_ids = extras.commit_ids
199 commit_ids = extras.commit_ids[:29000]
200
196
201 action = action_tmpl % ','.join(commit_ids)
197 # log the push call
202 action_logger(
203 extras.username, action, extras.repository, extras.ip, commit=True)
204
205 audit_user = audit_logger.UserWrap(
198 audit_user = audit_logger.UserWrap(
206 username=extras.username,
199 username=extras.username, ip_addr=extras.ip)
207 ip_addr=extras.ip)
208 repo = audit_logger.RepoWrap(repo_name=extras.repository)
200 repo = audit_logger.RepoWrap(repo_name=extras.repository)
209 audit_logger.store(
201 audit_logger.store(
210 action='user.push', action_data={
202 action='user.push', action_data={
211 'user_agent': extras.user_agent,
203 'user_agent': extras.user_agent,
212 'commit_ids': commit_ids[:10000]},
204 'commit_ids': commit_ids[:10000]},
213 user=audit_user, repo=repo, commit=True)
205 user=audit_user, repo=repo, commit=True)
214
206
215 # Propagate to external components.
207 # Propagate to external components.
216 if not is_shadow_repo(extras):
208 if not is_shadow_repo(extras):
217 post_push_extension(
209 post_push_extension(
218 repo_store_path=Repository.base_path(),
210 repo_store_path=Repository.base_path(),
219 pushed_revs=commit_ids,
211 pushed_revs=commit_ids,
220 **extras)
212 **extras)
221 events.trigger(events.RepoPushEvent(
213 events.trigger(events.RepoPushEvent(
222 repo_name=extras.repository,
214 repo_name=extras.repository,
223 pushed_commit_ids=commit_ids,
215 pushed_commit_ids=commit_ids,
224 extras=extras))
216 extras=extras))
225
217
226 output = ''
218 output = ''
227 # make lock is a tri state False, True, None. We only release lock on False
219 # make lock is a tri state False, True, None. We only release lock on False
228 if extras.make_lock is False and not is_shadow_repo(extras):
220 if extras.make_lock is False and not is_shadow_repo(extras):
229 Repository.unlock(Repository.get_by_repo_name(extras.repository))
221 Repository.unlock(Repository.get_by_repo_name(extras.repository))
230 msg = 'Released lock on repo `%s`\n' % extras.repository
222 msg = 'Released lock on repo `%s`\n' % extras.repository
231 output += msg
223 output += msg
232
224
233 if extras.locked_by[0]:
225 if extras.locked_by[0]:
234 locked_by = User.get(extras.locked_by[0]).username
226 locked_by = User.get(extras.locked_by[0]).username
235 reason = extras.locked_by[2]
227 reason = extras.locked_by[2]
236 _http_ret = HTTPLockedRC(
228 _http_ret = HTTPLockedRC(
237 _locked_by_explanation(extras.repository, locked_by, reason))
229 _locked_by_explanation(extras.repository, locked_by, reason))
238 # TODO: johbo: if not?
230 # TODO: johbo: if not?
239 if str(_http_ret.code).startswith('2'):
231 if str(_http_ret.code).startswith('2'):
240 # 2xx Codes don't raise exceptions
232 # 2xx Codes don't raise exceptions
241 output += _http_ret.title
233 output += _http_ret.title
242
234
243 output += 'RhodeCode: push completed\n'
235 output += 'RhodeCode: push completed\n'
244
236
245 return HookResponse(0, output)
237 return HookResponse(0, output)
246
238
247
239
248 def _locked_by_explanation(repo_name, user_name, reason):
240 def _locked_by_explanation(repo_name, user_name, reason):
249 message = (
241 message = (
250 'Repository `%s` locked by user `%s`. Reason:`%s`'
242 'Repository `%s` locked by user `%s`. Reason:`%s`'
251 % (repo_name, user_name, reason))
243 % (repo_name, user_name, reason))
252 return message
244 return message
253
245
254
246
255 def check_allowed_create_user(user_dict, created_by, **kwargs):
247 def check_allowed_create_user(user_dict, created_by, **kwargs):
256 # pre create hooks
248 # pre create hooks
257 if pre_create_user.is_active():
249 if pre_create_user.is_active():
258 allowed, reason = pre_create_user(created_by=created_by, **user_dict)
250 allowed, reason = pre_create_user(created_by=created_by, **user_dict)
259 if not allowed:
251 if not allowed:
260 raise UserCreationError(reason)
252 raise UserCreationError(reason)
261
253
262
254
263 class ExtensionCallback(object):
255 class ExtensionCallback(object):
264 """
256 """
265 Forwards a given call to rcextensions, sanitizes keyword arguments.
257 Forwards a given call to rcextensions, sanitizes keyword arguments.
266
258
267 Does check if there is an extension active for that hook. If it is
259 Does check if there is an extension active for that hook. If it is
268 there, it will forward all `kwargs_keys` keyword arguments to the
260 there, it will forward all `kwargs_keys` keyword arguments to the
269 extension callback.
261 extension callback.
270 """
262 """
271
263
272 def __init__(self, hook_name, kwargs_keys):
264 def __init__(self, hook_name, kwargs_keys):
273 self._hook_name = hook_name
265 self._hook_name = hook_name
274 self._kwargs_keys = set(kwargs_keys)
266 self._kwargs_keys = set(kwargs_keys)
275
267
276 def __call__(self, *args, **kwargs):
268 def __call__(self, *args, **kwargs):
277 log.debug('Calling extension callback for %s', self._hook_name)
269 log.debug('Calling extension callback for %s', self._hook_name)
278
270
279 kwargs_to_pass = dict((key, kwargs[key]) for key in self._kwargs_keys)
271 kwargs_to_pass = dict((key, kwargs[key]) for key in self._kwargs_keys)
280 # backward compat for removed api_key for old hooks. THis was it works
272 # backward compat for removed api_key for old hooks. THis was it works
281 # with older rcextensions that require api_key present
273 # with older rcextensions that require api_key present
282 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
274 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
283 kwargs_to_pass['api_key'] = '_DEPRECATED_'
275 kwargs_to_pass['api_key'] = '_DEPRECATED_'
284
276
285 callback = self._get_callback()
277 callback = self._get_callback()
286 if callback:
278 if callback:
287 return callback(**kwargs_to_pass)
279 return callback(**kwargs_to_pass)
288 else:
280 else:
289 log.debug('extensions callback not found skipping...')
281 log.debug('extensions callback not found skipping...')
290
282
291 def is_active(self):
283 def is_active(self):
292 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
284 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
293
285
294 def _get_callback(self):
286 def _get_callback(self):
295 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
287 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
296
288
297
289
298 pre_pull_extension = ExtensionCallback(
290 pre_pull_extension = ExtensionCallback(
299 hook_name='PRE_PULL_HOOK',
291 hook_name='PRE_PULL_HOOK',
300 kwargs_keys=(
292 kwargs_keys=(
301 'server_url', 'config', 'scm', 'username', 'ip', 'action',
293 'server_url', 'config', 'scm', 'username', 'ip', 'action',
302 'repository'))
294 'repository'))
303
295
304
296
305 post_pull_extension = ExtensionCallback(
297 post_pull_extension = ExtensionCallback(
306 hook_name='PULL_HOOK',
298 hook_name='PULL_HOOK',
307 kwargs_keys=(
299 kwargs_keys=(
308 'server_url', 'config', 'scm', 'username', 'ip', 'action',
300 'server_url', 'config', 'scm', 'username', 'ip', 'action',
309 'repository'))
301 'repository'))
310
302
311
303
312 pre_push_extension = ExtensionCallback(
304 pre_push_extension = ExtensionCallback(
313 hook_name='PRE_PUSH_HOOK',
305 hook_name='PRE_PUSH_HOOK',
314 kwargs_keys=(
306 kwargs_keys=(
315 'server_url', 'config', 'scm', 'username', 'ip', 'action',
307 'server_url', 'config', 'scm', 'username', 'ip', 'action',
316 'repository', 'repo_store_path', 'commit_ids'))
308 'repository', 'repo_store_path', 'commit_ids'))
317
309
318
310
319 post_push_extension = ExtensionCallback(
311 post_push_extension = ExtensionCallback(
320 hook_name='PUSH_HOOK',
312 hook_name='PUSH_HOOK',
321 kwargs_keys=(
313 kwargs_keys=(
322 'server_url', 'config', 'scm', 'username', 'ip', 'action',
314 'server_url', 'config', 'scm', 'username', 'ip', 'action',
323 'repository', 'repo_store_path', 'pushed_revs'))
315 'repository', 'repo_store_path', 'pushed_revs'))
324
316
325
317
326 pre_create_user = ExtensionCallback(
318 pre_create_user = ExtensionCallback(
327 hook_name='PRE_CREATE_USER_HOOK',
319 hook_name='PRE_CREATE_USER_HOOK',
328 kwargs_keys=(
320 kwargs_keys=(
329 'username', 'password', 'email', 'firstname', 'lastname', 'active',
321 'username', 'password', 'email', 'firstname', 'lastname', 'active',
330 'admin', 'created_by'))
322 'admin', 'created_by'))
331
323
332
324
333 log_create_pull_request = ExtensionCallback(
325 log_create_pull_request = ExtensionCallback(
334 hook_name='CREATE_PULL_REQUEST',
326 hook_name='CREATE_PULL_REQUEST',
335 kwargs_keys=(
327 kwargs_keys=(
336 'server_url', 'config', 'scm', 'username', 'ip', 'action',
328 'server_url', 'config', 'scm', 'username', 'ip', 'action',
337 'repository', 'pull_request_id', 'url', 'title', 'description',
329 'repository', 'pull_request_id', 'url', 'title', 'description',
338 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
330 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
339 'mergeable', 'source', 'target', 'author', 'reviewers'))
331 'mergeable', 'source', 'target', 'author', 'reviewers'))
340
332
341
333
342 log_merge_pull_request = ExtensionCallback(
334 log_merge_pull_request = ExtensionCallback(
343 hook_name='MERGE_PULL_REQUEST',
335 hook_name='MERGE_PULL_REQUEST',
344 kwargs_keys=(
336 kwargs_keys=(
345 'server_url', 'config', 'scm', 'username', 'ip', 'action',
337 'server_url', 'config', 'scm', 'username', 'ip', 'action',
346 'repository', 'pull_request_id', 'url', 'title', 'description',
338 'repository', 'pull_request_id', 'url', 'title', 'description',
347 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
339 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
348 'mergeable', 'source', 'target', 'author', 'reviewers'))
340 'mergeable', 'source', 'target', 'author', 'reviewers'))
349
341
350
342
351 log_close_pull_request = ExtensionCallback(
343 log_close_pull_request = ExtensionCallback(
352 hook_name='CLOSE_PULL_REQUEST',
344 hook_name='CLOSE_PULL_REQUEST',
353 kwargs_keys=(
345 kwargs_keys=(
354 'server_url', 'config', 'scm', 'username', 'ip', 'action',
346 'server_url', 'config', 'scm', 'username', 'ip', 'action',
355 'repository', 'pull_request_id', 'url', 'title', 'description',
347 'repository', 'pull_request_id', 'url', 'title', 'description',
356 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
348 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
357 'mergeable', 'source', 'target', 'author', 'reviewers'))
349 'mergeable', 'source', 'target', 'author', 'reviewers'))
358
350
359
351
360 log_review_pull_request = ExtensionCallback(
352 log_review_pull_request = ExtensionCallback(
361 hook_name='REVIEW_PULL_REQUEST',
353 hook_name='REVIEW_PULL_REQUEST',
362 kwargs_keys=(
354 kwargs_keys=(
363 'server_url', 'config', 'scm', 'username', 'ip', 'action',
355 'server_url', 'config', 'scm', 'username', 'ip', 'action',
364 'repository', 'pull_request_id', 'url', 'title', 'description',
356 'repository', 'pull_request_id', 'url', 'title', 'description',
365 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
357 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
366 'mergeable', 'source', 'target', 'author', 'reviewers'))
358 'mergeable', 'source', 'target', 'author', 'reviewers'))
367
359
368
360
369 log_update_pull_request = ExtensionCallback(
361 log_update_pull_request = ExtensionCallback(
370 hook_name='UPDATE_PULL_REQUEST',
362 hook_name='UPDATE_PULL_REQUEST',
371 kwargs_keys=(
363 kwargs_keys=(
372 'server_url', 'config', 'scm', 'username', 'ip', 'action',
364 'server_url', 'config', 'scm', 'username', 'ip', 'action',
373 'repository', 'pull_request_id', 'url', 'title', 'description',
365 'repository', 'pull_request_id', 'url', 'title', 'description',
374 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
366 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
375 'mergeable', 'source', 'target', 'author', 'reviewers'))
367 'mergeable', 'source', 'target', 'author', 'reviewers'))
376
368
377
369
378 log_create_user = ExtensionCallback(
370 log_create_user = ExtensionCallback(
379 hook_name='CREATE_USER_HOOK',
371 hook_name='CREATE_USER_HOOK',
380 kwargs_keys=(
372 kwargs_keys=(
381 'username', 'full_name_or_username', 'full_contact', 'user_id',
373 'username', 'full_name_or_username', 'full_contact', 'user_id',
382 'name', 'firstname', 'short_contact', 'admin', 'lastname',
374 'name', 'firstname', 'short_contact', 'admin', 'lastname',
383 'ip_addresses', 'extern_type', 'extern_name',
375 'ip_addresses', 'extern_type', 'extern_name',
384 'email', 'api_keys', 'last_login',
376 'email', 'api_keys', 'last_login',
385 'full_name', 'active', 'password', 'emails',
377 'full_name', 'active', 'password', 'emails',
386 'inherit_default_permissions', 'created_by', 'created_on'))
378 'inherit_default_permissions', 'created_by', 'created_on'))
387
379
388
380
389 log_delete_user = ExtensionCallback(
381 log_delete_user = ExtensionCallback(
390 hook_name='DELETE_USER_HOOK',
382 hook_name='DELETE_USER_HOOK',
391 kwargs_keys=(
383 kwargs_keys=(
392 'username', 'full_name_or_username', 'full_contact', 'user_id',
384 'username', 'full_name_or_username', 'full_contact', 'user_id',
393 'name', 'firstname', 'short_contact', 'admin', 'lastname',
385 'name', 'firstname', 'short_contact', 'admin', 'lastname',
394 'ip_addresses',
386 'ip_addresses',
395 'email', 'last_login',
387 'email', 'last_login',
396 'full_name', 'active', 'password', 'emails',
388 'full_name', 'active', 'password', 'emails',
397 'inherit_default_permissions', 'deleted_by'))
389 'inherit_default_permissions', 'deleted_by'))
398
390
399
391
400 log_create_repository = ExtensionCallback(
392 log_create_repository = ExtensionCallback(
401 hook_name='CREATE_REPO_HOOK',
393 hook_name='CREATE_REPO_HOOK',
402 kwargs_keys=(
394 kwargs_keys=(
403 'repo_name', 'repo_type', 'description', 'private', 'created_on',
395 'repo_name', 'repo_type', 'description', 'private', 'created_on',
404 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
396 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
405 'clone_uri', 'fork_id', 'group_id', 'created_by'))
397 'clone_uri', 'fork_id', 'group_id', 'created_by'))
406
398
407
399
408 log_delete_repository = ExtensionCallback(
400 log_delete_repository = ExtensionCallback(
409 hook_name='DELETE_REPO_HOOK',
401 hook_name='DELETE_REPO_HOOK',
410 kwargs_keys=(
402 kwargs_keys=(
411 'repo_name', 'repo_type', 'description', 'private', 'created_on',
403 'repo_name', 'repo_type', 'description', 'private', 'created_on',
412 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
404 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
413 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
405 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
414
406
415
407
416 log_create_repository_group = ExtensionCallback(
408 log_create_repository_group = ExtensionCallback(
417 hook_name='CREATE_REPO_GROUP_HOOK',
409 hook_name='CREATE_REPO_GROUP_HOOK',
418 kwargs_keys=(
410 kwargs_keys=(
419 'group_name', 'group_parent_id', 'group_description',
411 'group_name', 'group_parent_id', 'group_description',
420 'group_id', 'user_id', 'created_by', 'created_on',
412 'group_id', 'user_id', 'created_by', 'created_on',
421 'enable_locking'))
413 'enable_locking'))
@@ -1,1097 +1,1103 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import 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.lib.vcs.nodes import FileNode
26 from rhodecode.lib.vcs.nodes import FileNode
27 from rhodecode.model.changeset_status import ChangesetStatusModel
27 from rhodecode.model.changeset_status import ChangesetStatusModel
28 from rhodecode.model.db import (
28 from rhodecode.model.db import (
29 PullRequest, ChangesetStatus, UserLog, Notification)
29 PullRequest, ChangesetStatus, UserLog, Notification)
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31 from rhodecode.model.pull_request import PullRequestModel
31 from rhodecode.model.pull_request import PullRequestModel
32 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
33 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo import RepoModel
34 from rhodecode.tests import (
34 from rhodecode.tests import (
35 assert_session_flash, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
35 assert_session_flash, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
36 from rhodecode.tests.utils import AssertResponse
36 from rhodecode.tests.utils import AssertResponse
37
37
38
38
39 @pytest.mark.usefixtures('app', 'autologin_user')
39 @pytest.mark.usefixtures('app', 'autologin_user')
40 @pytest.mark.backends("git", "hg")
40 @pytest.mark.backends("git", "hg")
41 class TestPullrequestsController:
41 class TestPullrequestsController:
42
42
43 def test_index(self, backend):
43 def test_index(self, backend):
44 self.app.get(url(
44 self.app.get(url(
45 controller='pullrequests', action='index',
45 controller='pullrequests', action='index',
46 repo_name=backend.repo_name))
46 repo_name=backend.repo_name))
47
47
48 def test_option_menu_create_pull_request_exists(self, backend):
48 def test_option_menu_create_pull_request_exists(self, backend):
49 repo_name = backend.repo_name
49 repo_name = backend.repo_name
50 response = self.app.get(url('summary_home', repo_name=repo_name))
50 response = self.app.get(url('summary_home', repo_name=repo_name))
51
51
52 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
52 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
53 'pullrequest', repo_name=repo_name)
53 'pullrequest', repo_name=repo_name)
54 response.mustcontain(create_pr_link)
54 response.mustcontain(create_pr_link)
55
55
56 def test_global_redirect_of_pr(self, backend, pr_util):
56 def test_global_redirect_of_pr(self, backend, pr_util):
57 pull_request = pr_util.create_pull_request()
57 pull_request = pr_util.create_pull_request()
58
58
59 response = self.app.get(
59 response = self.app.get(
60 url('pull_requests_global',
60 url('pull_requests_global',
61 pull_request_id=pull_request.pull_request_id))
61 pull_request_id=pull_request.pull_request_id))
62
62
63 repo_name = pull_request.target_repo.repo_name
63 repo_name = pull_request.target_repo.repo_name
64 redirect_url = url('pullrequest_show', repo_name=repo_name,
64 redirect_url = url('pullrequest_show', repo_name=repo_name,
65 pull_request_id=pull_request.pull_request_id)
65 pull_request_id=pull_request.pull_request_id)
66 assert response.status == '302 Found'
66 assert response.status == '302 Found'
67 assert redirect_url in response.location
67 assert redirect_url in response.location
68
68
69 def test_create_pr_form_with_raw_commit_id(self, backend):
69 def test_create_pr_form_with_raw_commit_id(self, backend):
70 repo = backend.repo
70 repo = backend.repo
71
71
72 self.app.get(
72 self.app.get(
73 url(controller='pullrequests', action='index',
73 url(controller='pullrequests', action='index',
74 repo_name=repo.repo_name,
74 repo_name=repo.repo_name,
75 commit=repo.get_commit().raw_id),
75 commit=repo.get_commit().raw_id),
76 status=200)
76 status=200)
77
77
78 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
78 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
79 def test_show(self, pr_util, pr_merge_enabled):
79 def test_show(self, pr_util, pr_merge_enabled):
80 pull_request = pr_util.create_pull_request(
80 pull_request = pr_util.create_pull_request(
81 mergeable=pr_merge_enabled, enable_notifications=False)
81 mergeable=pr_merge_enabled, enable_notifications=False)
82
82
83 response = self.app.get(url(
83 response = self.app.get(url(
84 controller='pullrequests', action='show',
84 controller='pullrequests', action='show',
85 repo_name=pull_request.target_repo.scm_instance().name,
85 repo_name=pull_request.target_repo.scm_instance().name,
86 pull_request_id=str(pull_request.pull_request_id)))
86 pull_request_id=str(pull_request.pull_request_id)))
87
87
88 for commit_id in pull_request.revisions:
88 for commit_id in pull_request.revisions:
89 response.mustcontain(commit_id)
89 response.mustcontain(commit_id)
90
90
91 assert pull_request.target_ref_parts.type in response
91 assert pull_request.target_ref_parts.type in response
92 assert pull_request.target_ref_parts.name in response
92 assert pull_request.target_ref_parts.name in response
93 target_clone_url = pull_request.target_repo.clone_url()
93 target_clone_url = pull_request.target_repo.clone_url()
94 assert target_clone_url in response
94 assert target_clone_url in response
95
95
96 assert 'class="pull-request-merge"' in response
96 assert 'class="pull-request-merge"' in response
97 assert (
97 assert (
98 'Server-side pull request merging is disabled.'
98 'Server-side pull request merging is disabled.'
99 in response) != pr_merge_enabled
99 in response) != pr_merge_enabled
100
100
101 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
101 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
102 from rhodecode.tests.functional.test_login import login_url, logut_url
102 from rhodecode.tests.functional.test_login import login_url, logut_url
103 # Logout
103 # Logout
104 response = self.app.post(
104 response = self.app.post(
105 logut_url,
105 logut_url,
106 params={'csrf_token': csrf_token})
106 params={'csrf_token': csrf_token})
107 # Login as regular user
107 # Login as regular user
108 response = self.app.post(login_url,
108 response = self.app.post(login_url,
109 {'username': TEST_USER_REGULAR_LOGIN,
109 {'username': TEST_USER_REGULAR_LOGIN,
110 'password': 'test12'})
110 'password': 'test12'})
111
111
112 pull_request = pr_util.create_pull_request(
112 pull_request = pr_util.create_pull_request(
113 author=TEST_USER_REGULAR_LOGIN)
113 author=TEST_USER_REGULAR_LOGIN)
114
114
115 response = self.app.get(url(
115 response = self.app.get(url(
116 controller='pullrequests', action='show',
116 controller='pullrequests', action='show',
117 repo_name=pull_request.target_repo.scm_instance().name,
117 repo_name=pull_request.target_repo.scm_instance().name,
118 pull_request_id=str(pull_request.pull_request_id)))
118 pull_request_id=str(pull_request.pull_request_id)))
119
119
120 response.mustcontain('Server-side pull request merging is disabled.')
120 response.mustcontain('Server-side pull request merging is disabled.')
121
121
122 assert_response = response.assert_response()
122 assert_response = response.assert_response()
123 # for regular user without a merge permissions, we don't see it
123 # for regular user without a merge permissions, we don't see it
124 assert_response.no_element_exists('#close-pull-request-action')
124 assert_response.no_element_exists('#close-pull-request-action')
125
125
126 user_util.grant_user_permission_to_repo(
126 user_util.grant_user_permission_to_repo(
127 pull_request.target_repo,
127 pull_request.target_repo,
128 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
128 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
129 'repository.write')
129 'repository.write')
130 response = self.app.get(url(
130 response = self.app.get(url(
131 controller='pullrequests', action='show',
131 controller='pullrequests', action='show',
132 repo_name=pull_request.target_repo.scm_instance().name,
132 repo_name=pull_request.target_repo.scm_instance().name,
133 pull_request_id=str(pull_request.pull_request_id)))
133 pull_request_id=str(pull_request.pull_request_id)))
134
134
135 response.mustcontain('Server-side pull request merging is disabled.')
135 response.mustcontain('Server-side pull request merging is disabled.')
136
136
137 assert_response = response.assert_response()
137 assert_response = response.assert_response()
138 # now regular user has a merge permissions, we have CLOSE button
138 # now regular user has a merge permissions, we have CLOSE button
139 assert_response.one_element_exists('#close-pull-request-action')
139 assert_response.one_element_exists('#close-pull-request-action')
140
140
141 def test_show_invalid_commit_id(self, pr_util):
141 def test_show_invalid_commit_id(self, pr_util):
142 # Simulating invalid revisions which will cause a lookup error
142 # Simulating invalid revisions which will cause a lookup error
143 pull_request = pr_util.create_pull_request()
143 pull_request = pr_util.create_pull_request()
144 pull_request.revisions = ['invalid']
144 pull_request.revisions = ['invalid']
145 Session().add(pull_request)
145 Session().add(pull_request)
146 Session().commit()
146 Session().commit()
147
147
148 response = self.app.get(url(
148 response = self.app.get(url(
149 controller='pullrequests', action='show',
149 controller='pullrequests', action='show',
150 repo_name=pull_request.target_repo.scm_instance().name,
150 repo_name=pull_request.target_repo.scm_instance().name,
151 pull_request_id=str(pull_request.pull_request_id)))
151 pull_request_id=str(pull_request.pull_request_id)))
152
152
153 for commit_id in pull_request.revisions:
153 for commit_id in pull_request.revisions:
154 response.mustcontain(commit_id)
154 response.mustcontain(commit_id)
155
155
156 def test_show_invalid_source_reference(self, pr_util):
156 def test_show_invalid_source_reference(self, pr_util):
157 pull_request = pr_util.create_pull_request()
157 pull_request = pr_util.create_pull_request()
158 pull_request.source_ref = 'branch:b:invalid'
158 pull_request.source_ref = 'branch:b:invalid'
159 Session().add(pull_request)
159 Session().add(pull_request)
160 Session().commit()
160 Session().commit()
161
161
162 self.app.get(url(
162 self.app.get(url(
163 controller='pullrequests', action='show',
163 controller='pullrequests', action='show',
164 repo_name=pull_request.target_repo.scm_instance().name,
164 repo_name=pull_request.target_repo.scm_instance().name,
165 pull_request_id=str(pull_request.pull_request_id)))
165 pull_request_id=str(pull_request.pull_request_id)))
166
166
167 def test_edit_title_description(self, pr_util, csrf_token):
167 def test_edit_title_description(self, pr_util, csrf_token):
168 pull_request = pr_util.create_pull_request()
168 pull_request = pr_util.create_pull_request()
169 pull_request_id = pull_request.pull_request_id
169 pull_request_id = pull_request.pull_request_id
170
170
171 response = self.app.post(
171 response = self.app.post(
172 url(controller='pullrequests', action='update',
172 url(controller='pullrequests', action='update',
173 repo_name=pull_request.target_repo.repo_name,
173 repo_name=pull_request.target_repo.repo_name,
174 pull_request_id=str(pull_request_id)),
174 pull_request_id=str(pull_request_id)),
175 params={
175 params={
176 'edit_pull_request': 'true',
176 'edit_pull_request': 'true',
177 '_method': 'put',
177 '_method': 'put',
178 'title': 'New title',
178 'title': 'New title',
179 'description': 'New description',
179 'description': 'New description',
180 'csrf_token': csrf_token})
180 'csrf_token': csrf_token})
181
181
182 assert_session_flash(
182 assert_session_flash(
183 response, u'Pull request title & description updated.',
183 response, u'Pull request title & description updated.',
184 category='success')
184 category='success')
185
185
186 pull_request = PullRequest.get(pull_request_id)
186 pull_request = PullRequest.get(pull_request_id)
187 assert pull_request.title == 'New title'
187 assert pull_request.title == 'New title'
188 assert pull_request.description == 'New description'
188 assert pull_request.description == 'New description'
189
189
190 def test_edit_title_description_closed(self, pr_util, csrf_token):
190 def test_edit_title_description_closed(self, pr_util, csrf_token):
191 pull_request = pr_util.create_pull_request()
191 pull_request = pr_util.create_pull_request()
192 pull_request_id = pull_request.pull_request_id
192 pull_request_id = pull_request.pull_request_id
193 pr_util.close()
193 pr_util.close()
194
194
195 response = self.app.post(
195 response = self.app.post(
196 url(controller='pullrequests', action='update',
196 url(controller='pullrequests', action='update',
197 repo_name=pull_request.target_repo.repo_name,
197 repo_name=pull_request.target_repo.repo_name,
198 pull_request_id=str(pull_request_id)),
198 pull_request_id=str(pull_request_id)),
199 params={
199 params={
200 'edit_pull_request': 'true',
200 'edit_pull_request': 'true',
201 '_method': 'put',
201 '_method': 'put',
202 'title': 'New title',
202 'title': 'New title',
203 'description': 'New description',
203 'description': 'New description',
204 'csrf_token': csrf_token})
204 'csrf_token': csrf_token})
205
205
206 assert_session_flash(
206 assert_session_flash(
207 response, u'Cannot update closed pull requests.',
207 response, u'Cannot update closed pull requests.',
208 category='error')
208 category='error')
209
209
210 def test_update_invalid_source_reference(self, pr_util, csrf_token):
210 def test_update_invalid_source_reference(self, pr_util, csrf_token):
211 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
211 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
212
212
213 pull_request = pr_util.create_pull_request()
213 pull_request = pr_util.create_pull_request()
214 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
214 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
215 Session().add(pull_request)
215 Session().add(pull_request)
216 Session().commit()
216 Session().commit()
217
217
218 pull_request_id = pull_request.pull_request_id
218 pull_request_id = pull_request.pull_request_id
219
219
220 response = self.app.post(
220 response = self.app.post(
221 url(controller='pullrequests', action='update',
221 url(controller='pullrequests', action='update',
222 repo_name=pull_request.target_repo.repo_name,
222 repo_name=pull_request.target_repo.repo_name,
223 pull_request_id=str(pull_request_id)),
223 pull_request_id=str(pull_request_id)),
224 params={'update_commits': 'true', '_method': 'put',
224 params={'update_commits': 'true', '_method': 'put',
225 'csrf_token': csrf_token})
225 'csrf_token': csrf_token})
226
226
227 expected_msg = PullRequestModel.UPDATE_STATUS_MESSAGES[
227 expected_msg = PullRequestModel.UPDATE_STATUS_MESSAGES[
228 UpdateFailureReason.MISSING_SOURCE_REF]
228 UpdateFailureReason.MISSING_SOURCE_REF]
229 assert_session_flash(response, expected_msg, category='error')
229 assert_session_flash(response, expected_msg, category='error')
230
230
231 def test_missing_target_reference(self, pr_util, csrf_token):
231 def test_missing_target_reference(self, pr_util, csrf_token):
232 from rhodecode.lib.vcs.backends.base import MergeFailureReason
232 from rhodecode.lib.vcs.backends.base import MergeFailureReason
233 pull_request = pr_util.create_pull_request(
233 pull_request = pr_util.create_pull_request(
234 approved=True, mergeable=True)
234 approved=True, mergeable=True)
235 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
235 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
236 Session().add(pull_request)
236 Session().add(pull_request)
237 Session().commit()
237 Session().commit()
238
238
239 pull_request_id = pull_request.pull_request_id
239 pull_request_id = pull_request.pull_request_id
240 pull_request_url = url(
240 pull_request_url = url(
241 controller='pullrequests', action='show',
241 controller='pullrequests', action='show',
242 repo_name=pull_request.target_repo.repo_name,
242 repo_name=pull_request.target_repo.repo_name,
243 pull_request_id=str(pull_request_id))
243 pull_request_id=str(pull_request_id))
244
244
245 response = self.app.get(pull_request_url)
245 response = self.app.get(pull_request_url)
246
246
247 assertr = AssertResponse(response)
247 assertr = AssertResponse(response)
248 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
248 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
249 MergeFailureReason.MISSING_TARGET_REF]
249 MergeFailureReason.MISSING_TARGET_REF]
250 assertr.element_contains(
250 assertr.element_contains(
251 'span[data-role="merge-message"]', str(expected_msg))
251 'span[data-role="merge-message"]', str(expected_msg))
252
252
253 def test_comment_and_close_pull_request(self, pr_util, csrf_token):
253 def test_comment_and_close_pull_request(self, pr_util, csrf_token):
254 pull_request = pr_util.create_pull_request(approved=True)
254 pull_request = pr_util.create_pull_request(approved=True)
255 pull_request_id = pull_request.pull_request_id
255 pull_request_id = pull_request.pull_request_id
256 author = pull_request.user_id
256 author = pull_request.user_id
257 repo = pull_request.target_repo.repo_id
257 repo = pull_request.target_repo.repo_id
258
258
259 self.app.post(
259 self.app.post(
260 url(controller='pullrequests',
260 url(controller='pullrequests',
261 action='comment',
261 action='comment',
262 repo_name=pull_request.target_repo.scm_instance().name,
262 repo_name=pull_request.target_repo.scm_instance().name,
263 pull_request_id=str(pull_request_id)),
263 pull_request_id=str(pull_request_id)),
264 params={
264 params={
265 'changeset_status': ChangesetStatus.STATUS_APPROVED,
265 'changeset_status': ChangesetStatus.STATUS_APPROVED,
266 'close_pull_request': '1',
266 'close_pull_request': '1',
267 'text': 'Closing a PR',
267 'text': 'Closing a PR',
268 'csrf_token': csrf_token},
268 'csrf_token': csrf_token},
269 status=302)
269 status=302)
270
270
271 action = 'user_closed_pull_request:%d' % pull_request_id
271 action = 'user_closed_pull_request:%d' % pull_request_id
272 journal = UserLog.query()\
272 journal = UserLog.query()\
273 .filter(UserLog.user_id == author)\
273 .filter(UserLog.user_id == author)\
274 .filter(UserLog.repository_id == repo)\
274 .filter(UserLog.repository_id == repo)\
275 .filter(UserLog.action == action)\
275 .filter(UserLog.action == action)\
276 .all()
276 .all()
277 assert len(journal) == 1
277 assert len(journal) == 1
278
278
279 pull_request = PullRequest.get(pull_request_id)
279 pull_request = PullRequest.get(pull_request_id)
280 assert pull_request.is_closed()
280 assert pull_request.is_closed()
281
281
282 # check only the latest status, not the review status
282 # check only the latest status, not the review status
283 status = ChangesetStatusModel().get_status(
283 status = ChangesetStatusModel().get_status(
284 pull_request.source_repo, pull_request=pull_request)
284 pull_request.source_repo, pull_request=pull_request)
285 assert status == ChangesetStatus.STATUS_APPROVED
285 assert status == ChangesetStatus.STATUS_APPROVED
286
286
287 def test_reject_and_close_pull_request(self, pr_util, csrf_token):
287 def test_reject_and_close_pull_request(self, pr_util, csrf_token):
288 pull_request = pr_util.create_pull_request()
288 pull_request = pr_util.create_pull_request()
289 pull_request_id = pull_request.pull_request_id
289 pull_request_id = pull_request.pull_request_id
290 response = self.app.post(
290 response = self.app.post(
291 url(controller='pullrequests',
291 url(controller='pullrequests',
292 action='update',
292 action='update',
293 repo_name=pull_request.target_repo.scm_instance().name,
293 repo_name=pull_request.target_repo.scm_instance().name,
294 pull_request_id=str(pull_request.pull_request_id)),
294 pull_request_id=str(pull_request.pull_request_id)),
295 params={'close_pull_request': 'true', '_method': 'put',
295 params={'close_pull_request': 'true', '_method': 'put',
296 'csrf_token': csrf_token})
296 'csrf_token': csrf_token})
297
297
298 pull_request = PullRequest.get(pull_request_id)
298 pull_request = PullRequest.get(pull_request_id)
299
299
300 assert response.json is True
300 assert response.json is True
301 assert pull_request.is_closed()
301 assert pull_request.is_closed()
302
302
303 # check only the latest status, not the review status
303 # check only the latest status, not the review status
304 status = ChangesetStatusModel().get_status(
304 status = ChangesetStatusModel().get_status(
305 pull_request.source_repo, pull_request=pull_request)
305 pull_request.source_repo, pull_request=pull_request)
306 assert status == ChangesetStatus.STATUS_REJECTED
306 assert status == ChangesetStatus.STATUS_REJECTED
307
307
308 def test_comment_force_close_pull_request(self, pr_util, csrf_token):
308 def test_comment_force_close_pull_request(self, pr_util, csrf_token):
309 pull_request = pr_util.create_pull_request()
309 pull_request = pr_util.create_pull_request()
310 pull_request_id = pull_request.pull_request_id
310 pull_request_id = pull_request.pull_request_id
311 reviewers_data = [(1, ['reason']), (2, ['reason2'])]
311 reviewers_data = [(1, ['reason']), (2, ['reason2'])]
312 PullRequestModel().update_reviewers(pull_request_id, reviewers_data)
312 PullRequestModel().update_reviewers(pull_request_id, reviewers_data)
313 author = pull_request.user_id
313 author = pull_request.user_id
314 repo = pull_request.target_repo.repo_id
314 repo = pull_request.target_repo.repo_id
315 self.app.post(
315 self.app.post(
316 url(controller='pullrequests',
316 url(controller='pullrequests',
317 action='comment',
317 action='comment',
318 repo_name=pull_request.target_repo.scm_instance().name,
318 repo_name=pull_request.target_repo.scm_instance().name,
319 pull_request_id=str(pull_request_id)),
319 pull_request_id=str(pull_request_id)),
320 params={
320 params={
321 'changeset_status': 'rejected',
321 'changeset_status': 'rejected',
322 'close_pull_request': '1',
322 'close_pull_request': '1',
323 'csrf_token': csrf_token},
323 'csrf_token': csrf_token},
324 status=302)
324 status=302)
325
325
326 pull_request = PullRequest.get(pull_request_id)
326 pull_request = PullRequest.get(pull_request_id)
327
327
328 action = 'user_closed_pull_request:%d' % pull_request_id
328 action = 'user_closed_pull_request:%d' % pull_request_id
329 journal = UserLog.query().filter(
329 journal = UserLog.query().filter(
330 UserLog.user_id == author,
330 UserLog.user_id == author,
331 UserLog.repository_id == repo,
331 UserLog.repository_id == repo,
332 UserLog.action == action).all()
332 UserLog.action == action).all()
333 assert len(journal) == 1
333 assert len(journal) == 1
334
334
335 # check only the latest status, not the review status
335 # check only the latest status, not the review status
336 status = ChangesetStatusModel().get_status(
336 status = ChangesetStatusModel().get_status(
337 pull_request.source_repo, pull_request=pull_request)
337 pull_request.source_repo, pull_request=pull_request)
338 assert status == ChangesetStatus.STATUS_REJECTED
338 assert status == ChangesetStatus.STATUS_REJECTED
339
339
340 def test_create_pull_request(self, backend, csrf_token):
340 def test_create_pull_request(self, backend, csrf_token):
341 commits = [
341 commits = [
342 {'message': 'ancestor'},
342 {'message': 'ancestor'},
343 {'message': 'change'},
343 {'message': 'change'},
344 {'message': 'change2'},
344 {'message': 'change2'},
345 ]
345 ]
346 commit_ids = backend.create_master_repo(commits)
346 commit_ids = backend.create_master_repo(commits)
347 target = backend.create_repo(heads=['ancestor'])
347 target = backend.create_repo(heads=['ancestor'])
348 source = backend.create_repo(heads=['change2'])
348 source = backend.create_repo(heads=['change2'])
349
349
350 response = self.app.post(
350 response = self.app.post(
351 url(
351 url(
352 controller='pullrequests',
352 controller='pullrequests',
353 action='create',
353 action='create',
354 repo_name=source.repo_name
354 repo_name=source.repo_name
355 ),
355 ),
356 [
356 [
357 ('source_repo', source.repo_name),
357 ('source_repo', source.repo_name),
358 ('source_ref', 'branch:default:' + commit_ids['change2']),
358 ('source_ref', 'branch:default:' + commit_ids['change2']),
359 ('target_repo', target.repo_name),
359 ('target_repo', target.repo_name),
360 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
360 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
361 ('pullrequest_desc', 'Description'),
361 ('pullrequest_desc', 'Description'),
362 ('pullrequest_title', 'Title'),
362 ('pullrequest_title', 'Title'),
363 ('__start__', 'review_members:sequence'),
363 ('__start__', 'review_members:sequence'),
364 ('__start__', 'reviewer:mapping'),
364 ('__start__', 'reviewer:mapping'),
365 ('user_id', '1'),
365 ('user_id', '1'),
366 ('__start__', 'reasons:sequence'),
366 ('__start__', 'reasons:sequence'),
367 ('reason', 'Some reason'),
367 ('reason', 'Some reason'),
368 ('__end__', 'reasons:sequence'),
368 ('__end__', 'reasons:sequence'),
369 ('__end__', 'reviewer:mapping'),
369 ('__end__', 'reviewer:mapping'),
370 ('__end__', 'review_members:sequence'),
370 ('__end__', 'review_members:sequence'),
371 ('__start__', 'revisions:sequence'),
371 ('__start__', 'revisions:sequence'),
372 ('revisions', commit_ids['change']),
372 ('revisions', commit_ids['change']),
373 ('revisions', commit_ids['change2']),
373 ('revisions', commit_ids['change2']),
374 ('__end__', 'revisions:sequence'),
374 ('__end__', 'revisions:sequence'),
375 ('user', ''),
375 ('user', ''),
376 ('csrf_token', csrf_token),
376 ('csrf_token', csrf_token),
377 ],
377 ],
378 status=302)
378 status=302)
379
379
380 location = response.headers['Location']
380 location = response.headers['Location']
381 pull_request_id = int(location.rsplit('/', 1)[1])
381 pull_request_id = int(location.rsplit('/', 1)[1])
382 pull_request = PullRequest.get(pull_request_id)
382 pull_request = PullRequest.get(pull_request_id)
383
383
384 # check that we have now both revisions
384 # check that we have now both revisions
385 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
385 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
386 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
386 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
387 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
387 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
388 assert pull_request.target_ref == expected_target_ref
388 assert pull_request.target_ref == expected_target_ref
389
389
390 def test_reviewer_notifications(self, backend, csrf_token):
390 def test_reviewer_notifications(self, backend, csrf_token):
391 # We have to use the app.post for this test so it will create the
391 # We have to use the app.post for this test so it will create the
392 # notifications properly with the new PR
392 # notifications properly with the new PR
393 commits = [
393 commits = [
394 {'message': 'ancestor',
394 {'message': 'ancestor',
395 'added': [FileNode('file_A', content='content_of_ancestor')]},
395 'added': [FileNode('file_A', content='content_of_ancestor')]},
396 {'message': 'change',
396 {'message': 'change',
397 'added': [FileNode('file_a', content='content_of_change')]},
397 'added': [FileNode('file_a', content='content_of_change')]},
398 {'message': 'change-child'},
398 {'message': 'change-child'},
399 {'message': 'ancestor-child', 'parents': ['ancestor'],
399 {'message': 'ancestor-child', 'parents': ['ancestor'],
400 'added': [
400 'added': [
401 FileNode('file_B', content='content_of_ancestor_child')]},
401 FileNode('file_B', content='content_of_ancestor_child')]},
402 {'message': 'ancestor-child-2'},
402 {'message': 'ancestor-child-2'},
403 ]
403 ]
404 commit_ids = backend.create_master_repo(commits)
404 commit_ids = backend.create_master_repo(commits)
405 target = backend.create_repo(heads=['ancestor-child'])
405 target = backend.create_repo(heads=['ancestor-child'])
406 source = backend.create_repo(heads=['change'])
406 source = backend.create_repo(heads=['change'])
407
407
408 response = self.app.post(
408 response = self.app.post(
409 url(
409 url(
410 controller='pullrequests',
410 controller='pullrequests',
411 action='create',
411 action='create',
412 repo_name=source.repo_name
412 repo_name=source.repo_name
413 ),
413 ),
414 [
414 [
415 ('source_repo', source.repo_name),
415 ('source_repo', source.repo_name),
416 ('source_ref', 'branch:default:' + commit_ids['change']),
416 ('source_ref', 'branch:default:' + commit_ids['change']),
417 ('target_repo', target.repo_name),
417 ('target_repo', target.repo_name),
418 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
418 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
419 ('pullrequest_desc', 'Description'),
419 ('pullrequest_desc', 'Description'),
420 ('pullrequest_title', 'Title'),
420 ('pullrequest_title', 'Title'),
421 ('__start__', 'review_members:sequence'),
421 ('__start__', 'review_members:sequence'),
422 ('__start__', 'reviewer:mapping'),
422 ('__start__', 'reviewer:mapping'),
423 ('user_id', '2'),
423 ('user_id', '2'),
424 ('__start__', 'reasons:sequence'),
424 ('__start__', 'reasons:sequence'),
425 ('reason', 'Some reason'),
425 ('reason', 'Some reason'),
426 ('__end__', 'reasons:sequence'),
426 ('__end__', 'reasons:sequence'),
427 ('__end__', 'reviewer:mapping'),
427 ('__end__', 'reviewer:mapping'),
428 ('__end__', 'review_members:sequence'),
428 ('__end__', 'review_members:sequence'),
429 ('__start__', 'revisions:sequence'),
429 ('__start__', 'revisions:sequence'),
430 ('revisions', commit_ids['change']),
430 ('revisions', commit_ids['change']),
431 ('__end__', 'revisions:sequence'),
431 ('__end__', 'revisions:sequence'),
432 ('user', ''),
432 ('user', ''),
433 ('csrf_token', csrf_token),
433 ('csrf_token', csrf_token),
434 ],
434 ],
435 status=302)
435 status=302)
436
436
437 location = response.headers['Location']
437 location = response.headers['Location']
438 pull_request_id = int(location.rsplit('/', 1)[1])
438 pull_request_id = int(location.rsplit('/', 1)[1])
439 pull_request = PullRequest.get(pull_request_id)
439 pull_request = PullRequest.get(pull_request_id)
440
440
441 # Check that a notification was made
441 # Check that a notification was made
442 notifications = Notification.query()\
442 notifications = Notification.query()\
443 .filter(Notification.created_by == pull_request.author.user_id,
443 .filter(Notification.created_by == pull_request.author.user_id,
444 Notification.type_ == Notification.TYPE_PULL_REQUEST,
444 Notification.type_ == Notification.TYPE_PULL_REQUEST,
445 Notification.subject.contains("wants you to review "
445 Notification.subject.contains("wants you to review "
446 "pull request #%d"
446 "pull request #%d"
447 % pull_request_id))
447 % pull_request_id))
448 assert len(notifications.all()) == 1
448 assert len(notifications.all()) == 1
449
449
450 # Change reviewers and check that a notification was made
450 # Change reviewers and check that a notification was made
451 PullRequestModel().update_reviewers(
451 PullRequestModel().update_reviewers(
452 pull_request.pull_request_id, [(1, [])])
452 pull_request.pull_request_id, [(1, [])])
453 assert len(notifications.all()) == 2
453 assert len(notifications.all()) == 2
454
454
455 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
455 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
456 csrf_token):
456 csrf_token):
457 commits = [
457 commits = [
458 {'message': 'ancestor',
458 {'message': 'ancestor',
459 'added': [FileNode('file_A', content='content_of_ancestor')]},
459 'added': [FileNode('file_A', content='content_of_ancestor')]},
460 {'message': 'change',
460 {'message': 'change',
461 'added': [FileNode('file_a', content='content_of_change')]},
461 'added': [FileNode('file_a', content='content_of_change')]},
462 {'message': 'change-child'},
462 {'message': 'change-child'},
463 {'message': 'ancestor-child', 'parents': ['ancestor'],
463 {'message': 'ancestor-child', 'parents': ['ancestor'],
464 'added': [
464 'added': [
465 FileNode('file_B', content='content_of_ancestor_child')]},
465 FileNode('file_B', content='content_of_ancestor_child')]},
466 {'message': 'ancestor-child-2'},
466 {'message': 'ancestor-child-2'},
467 ]
467 ]
468 commit_ids = backend.create_master_repo(commits)
468 commit_ids = backend.create_master_repo(commits)
469 target = backend.create_repo(heads=['ancestor-child'])
469 target = backend.create_repo(heads=['ancestor-child'])
470 source = backend.create_repo(heads=['change'])
470 source = backend.create_repo(heads=['change'])
471
471
472 response = self.app.post(
472 response = self.app.post(
473 url(
473 url(
474 controller='pullrequests',
474 controller='pullrequests',
475 action='create',
475 action='create',
476 repo_name=source.repo_name
476 repo_name=source.repo_name
477 ),
477 ),
478 [
478 [
479 ('source_repo', source.repo_name),
479 ('source_repo', source.repo_name),
480 ('source_ref', 'branch:default:' + commit_ids['change']),
480 ('source_ref', 'branch:default:' + commit_ids['change']),
481 ('target_repo', target.repo_name),
481 ('target_repo', target.repo_name),
482 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
482 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
483 ('pullrequest_desc', 'Description'),
483 ('pullrequest_desc', 'Description'),
484 ('pullrequest_title', 'Title'),
484 ('pullrequest_title', 'Title'),
485 ('__start__', 'review_members:sequence'),
485 ('__start__', 'review_members:sequence'),
486 ('__start__', 'reviewer:mapping'),
486 ('__start__', 'reviewer:mapping'),
487 ('user_id', '1'),
487 ('user_id', '1'),
488 ('__start__', 'reasons:sequence'),
488 ('__start__', 'reasons:sequence'),
489 ('reason', 'Some reason'),
489 ('reason', 'Some reason'),
490 ('__end__', 'reasons:sequence'),
490 ('__end__', 'reasons:sequence'),
491 ('__end__', 'reviewer:mapping'),
491 ('__end__', 'reviewer:mapping'),
492 ('__end__', 'review_members:sequence'),
492 ('__end__', 'review_members:sequence'),
493 ('__start__', 'revisions:sequence'),
493 ('__start__', 'revisions:sequence'),
494 ('revisions', commit_ids['change']),
494 ('revisions', commit_ids['change']),
495 ('__end__', 'revisions:sequence'),
495 ('__end__', 'revisions:sequence'),
496 ('user', ''),
496 ('user', ''),
497 ('csrf_token', csrf_token),
497 ('csrf_token', csrf_token),
498 ],
498 ],
499 status=302)
499 status=302)
500
500
501 location = response.headers['Location']
501 location = response.headers['Location']
502 pull_request_id = int(location.rsplit('/', 1)[1])
502 pull_request_id = int(location.rsplit('/', 1)[1])
503 pull_request = PullRequest.get(pull_request_id)
503 pull_request = PullRequest.get(pull_request_id)
504
504
505 # target_ref has to point to the ancestor's commit_id in order to
505 # target_ref has to point to the ancestor's commit_id in order to
506 # show the correct diff
506 # show the correct diff
507 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
507 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
508 assert pull_request.target_ref == expected_target_ref
508 assert pull_request.target_ref == expected_target_ref
509
509
510 # Check generated diff contents
510 # Check generated diff contents
511 response = response.follow()
511 response = response.follow()
512 assert 'content_of_ancestor' not in response.body
512 assert 'content_of_ancestor' not in response.body
513 assert 'content_of_ancestor-child' not in response.body
513 assert 'content_of_ancestor-child' not in response.body
514 assert 'content_of_change' in response.body
514 assert 'content_of_change' in response.body
515
515
516 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
516 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
517 # Clear any previous calls to rcextensions
517 # Clear any previous calls to rcextensions
518 rhodecode.EXTENSIONS.calls.clear()
518 rhodecode.EXTENSIONS.calls.clear()
519
519
520 pull_request = pr_util.create_pull_request(
520 pull_request = pr_util.create_pull_request(
521 approved=True, mergeable=True)
521 approved=True, mergeable=True)
522 pull_request_id = pull_request.pull_request_id
522 pull_request_id = pull_request.pull_request_id
523 repo_name = pull_request.target_repo.scm_instance().name,
523 repo_name = pull_request.target_repo.scm_instance().name,
524
524
525 response = self.app.post(
525 response = self.app.post(
526 url(controller='pullrequests',
526 url(controller='pullrequests',
527 action='merge',
527 action='merge',
528 repo_name=str(repo_name[0]),
528 repo_name=str(repo_name[0]),
529 pull_request_id=str(pull_request_id)),
529 pull_request_id=str(pull_request_id)),
530 params={'csrf_token': csrf_token}).follow()
530 params={'csrf_token': csrf_token}).follow()
531
531
532 pull_request = PullRequest.get(pull_request_id)
532 pull_request = PullRequest.get(pull_request_id)
533
533
534 assert response.status_int == 200
534 assert response.status_int == 200
535 assert pull_request.is_closed()
535 assert pull_request.is_closed()
536 assert_pull_request_status(
536 assert_pull_request_status(
537 pull_request, ChangesetStatus.STATUS_APPROVED)
537 pull_request, ChangesetStatus.STATUS_APPROVED)
538
538
539 # Check the relevant log entries were added
539 # Check the relevant log entries were added
540 user_logs = UserLog.query() \
540 user_logs = UserLog.query() \
541 .filter(UserLog.version == UserLog.VERSION_1) \
541 .filter(UserLog.version == UserLog.VERSION_1) \
542 .order_by('-user_log_id').limit(4)
542 .order_by('-user_log_id').limit(3)
543 actions = [log.action for log in user_logs]
543 actions = [log.action for log in user_logs]
544 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
544 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
545 expected_actions = [
545 expected_actions = [
546 u'user_closed_pull_request:%d' % pull_request_id,
546 u'user_closed_pull_request:%d' % pull_request_id,
547 u'user_merged_pull_request:%d' % pull_request_id,
547 u'user_merged_pull_request:%d' % pull_request_id,
548 # The action below reflect that the post push actions were executed
548 # The action below reflect that the post push actions were executed
549 u'user_commented_pull_request:%d' % pull_request_id,
549 u'user_commented_pull_request:%d' % pull_request_id,
550 u'push:%s' % ','.join(pr_commit_ids),
551 ]
550 ]
552 assert actions == expected_actions
551 assert actions == expected_actions
553
552
553 user_logs = UserLog.query() \
554 .filter(UserLog.version == UserLog.VERSION_2) \
555 .order_by('-user_log_id').limit(1)
556 actions = [log.action for log in user_logs]
557 assert actions == ['user.push']
558 assert user_logs[0].action_data['commit_ids'] == pr_commit_ids
559
554 # Check post_push rcextension was really executed
560 # Check post_push rcextension was really executed
555 push_calls = rhodecode.EXTENSIONS.calls['post_push']
561 push_calls = rhodecode.EXTENSIONS.calls['post_push']
556 assert len(push_calls) == 1
562 assert len(push_calls) == 1
557 unused_last_call_args, last_call_kwargs = push_calls[0]
563 unused_last_call_args, last_call_kwargs = push_calls[0]
558 assert last_call_kwargs['action'] == 'push'
564 assert last_call_kwargs['action'] == 'push'
559 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
565 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
560
566
561 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
567 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
562 pull_request = pr_util.create_pull_request(mergeable=False)
568 pull_request = pr_util.create_pull_request(mergeable=False)
563 pull_request_id = pull_request.pull_request_id
569 pull_request_id = pull_request.pull_request_id
564 pull_request = PullRequest.get(pull_request_id)
570 pull_request = PullRequest.get(pull_request_id)
565
571
566 response = self.app.post(
572 response = self.app.post(
567 url(controller='pullrequests',
573 url(controller='pullrequests',
568 action='merge',
574 action='merge',
569 repo_name=pull_request.target_repo.scm_instance().name,
575 repo_name=pull_request.target_repo.scm_instance().name,
570 pull_request_id=str(pull_request.pull_request_id)),
576 pull_request_id=str(pull_request.pull_request_id)),
571 params={'csrf_token': csrf_token}).follow()
577 params={'csrf_token': csrf_token}).follow()
572
578
573 assert response.status_int == 200
579 assert response.status_int == 200
574 response.mustcontain(
580 response.mustcontain(
575 'Merge is not currently possible because of below failed checks.')
581 'Merge is not currently possible because of below failed checks.')
576 response.mustcontain('Server-side pull request merging is disabled.')
582 response.mustcontain('Server-side pull request merging is disabled.')
577
583
578 @pytest.mark.skip_backends('svn')
584 @pytest.mark.skip_backends('svn')
579 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
585 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
580 pull_request = pr_util.create_pull_request(mergeable=True)
586 pull_request = pr_util.create_pull_request(mergeable=True)
581 pull_request_id = pull_request.pull_request_id
587 pull_request_id = pull_request.pull_request_id
582 repo_name = pull_request.target_repo.scm_instance().name,
588 repo_name = pull_request.target_repo.scm_instance().name,
583
589
584 response = self.app.post(
590 response = self.app.post(
585 url(controller='pullrequests',
591 url(controller='pullrequests',
586 action='merge',
592 action='merge',
587 repo_name=str(repo_name[0]),
593 repo_name=str(repo_name[0]),
588 pull_request_id=str(pull_request_id)),
594 pull_request_id=str(pull_request_id)),
589 params={'csrf_token': csrf_token}).follow()
595 params={'csrf_token': csrf_token}).follow()
590
596
591 assert response.status_int == 200
597 assert response.status_int == 200
592
598
593 response.mustcontain(
599 response.mustcontain(
594 'Merge is not currently possible because of below failed checks.')
600 'Merge is not currently possible because of below failed checks.')
595 response.mustcontain('Pull request reviewer approval is pending.')
601 response.mustcontain('Pull request reviewer approval is pending.')
596
602
597 def test_update_source_revision(self, backend, csrf_token):
603 def test_update_source_revision(self, backend, csrf_token):
598 commits = [
604 commits = [
599 {'message': 'ancestor'},
605 {'message': 'ancestor'},
600 {'message': 'change'},
606 {'message': 'change'},
601 {'message': 'change-2'},
607 {'message': 'change-2'},
602 ]
608 ]
603 commit_ids = backend.create_master_repo(commits)
609 commit_ids = backend.create_master_repo(commits)
604 target = backend.create_repo(heads=['ancestor'])
610 target = backend.create_repo(heads=['ancestor'])
605 source = backend.create_repo(heads=['change'])
611 source = backend.create_repo(heads=['change'])
606
612
607 # create pr from a in source to A in target
613 # create pr from a in source to A in target
608 pull_request = PullRequest()
614 pull_request = PullRequest()
609 pull_request.source_repo = source
615 pull_request.source_repo = source
610 # TODO: johbo: Make sure that we write the source ref this way!
616 # TODO: johbo: Make sure that we write the source ref this way!
611 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
617 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
612 branch=backend.default_branch_name, commit_id=commit_ids['change'])
618 branch=backend.default_branch_name, commit_id=commit_ids['change'])
613 pull_request.target_repo = target
619 pull_request.target_repo = target
614
620
615 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
621 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
616 branch=backend.default_branch_name,
622 branch=backend.default_branch_name,
617 commit_id=commit_ids['ancestor'])
623 commit_id=commit_ids['ancestor'])
618 pull_request.revisions = [commit_ids['change']]
624 pull_request.revisions = [commit_ids['change']]
619 pull_request.title = u"Test"
625 pull_request.title = u"Test"
620 pull_request.description = u"Description"
626 pull_request.description = u"Description"
621 pull_request.author = UserModel().get_by_username(
627 pull_request.author = UserModel().get_by_username(
622 TEST_USER_ADMIN_LOGIN)
628 TEST_USER_ADMIN_LOGIN)
623 Session().add(pull_request)
629 Session().add(pull_request)
624 Session().commit()
630 Session().commit()
625 pull_request_id = pull_request.pull_request_id
631 pull_request_id = pull_request.pull_request_id
626
632
627 # source has ancestor - change - change-2
633 # source has ancestor - change - change-2
628 backend.pull_heads(source, heads=['change-2'])
634 backend.pull_heads(source, heads=['change-2'])
629
635
630 # update PR
636 # update PR
631 self.app.post(
637 self.app.post(
632 url(controller='pullrequests', action='update',
638 url(controller='pullrequests', action='update',
633 repo_name=target.repo_name,
639 repo_name=target.repo_name,
634 pull_request_id=str(pull_request_id)),
640 pull_request_id=str(pull_request_id)),
635 params={'update_commits': 'true', '_method': 'put',
641 params={'update_commits': 'true', '_method': 'put',
636 'csrf_token': csrf_token})
642 'csrf_token': csrf_token})
637
643
638 # check that we have now both revisions
644 # check that we have now both revisions
639 pull_request = PullRequest.get(pull_request_id)
645 pull_request = PullRequest.get(pull_request_id)
640 assert pull_request.revisions == [
646 assert pull_request.revisions == [
641 commit_ids['change-2'], commit_ids['change']]
647 commit_ids['change-2'], commit_ids['change']]
642
648
643 # TODO: johbo: this should be a test on its own
649 # TODO: johbo: this should be a test on its own
644 response = self.app.get(url(
650 response = self.app.get(url(
645 controller='pullrequests', action='index',
651 controller='pullrequests', action='index',
646 repo_name=target.repo_name))
652 repo_name=target.repo_name))
647 assert response.status_int == 200
653 assert response.status_int == 200
648 assert 'Pull request updated to' in response.body
654 assert 'Pull request updated to' in response.body
649 assert 'with 1 added, 0 removed commits.' in response.body
655 assert 'with 1 added, 0 removed commits.' in response.body
650
656
651 def test_update_target_revision(self, backend, csrf_token):
657 def test_update_target_revision(self, backend, csrf_token):
652 commits = [
658 commits = [
653 {'message': 'ancestor'},
659 {'message': 'ancestor'},
654 {'message': 'change'},
660 {'message': 'change'},
655 {'message': 'ancestor-new', 'parents': ['ancestor']},
661 {'message': 'ancestor-new', 'parents': ['ancestor']},
656 {'message': 'change-rebased'},
662 {'message': 'change-rebased'},
657 ]
663 ]
658 commit_ids = backend.create_master_repo(commits)
664 commit_ids = backend.create_master_repo(commits)
659 target = backend.create_repo(heads=['ancestor'])
665 target = backend.create_repo(heads=['ancestor'])
660 source = backend.create_repo(heads=['change'])
666 source = backend.create_repo(heads=['change'])
661
667
662 # create pr from a in source to A in target
668 # create pr from a in source to A in target
663 pull_request = PullRequest()
669 pull_request = PullRequest()
664 pull_request.source_repo = source
670 pull_request.source_repo = source
665 # TODO: johbo: Make sure that we write the source ref this way!
671 # TODO: johbo: Make sure that we write the source ref this way!
666 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
672 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
667 branch=backend.default_branch_name, commit_id=commit_ids['change'])
673 branch=backend.default_branch_name, commit_id=commit_ids['change'])
668 pull_request.target_repo = target
674 pull_request.target_repo = target
669 # TODO: johbo: Target ref should be branch based, since tip can jump
675 # TODO: johbo: Target ref should be branch based, since tip can jump
670 # from branch to branch
676 # from branch to branch
671 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
677 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
672 branch=backend.default_branch_name,
678 branch=backend.default_branch_name,
673 commit_id=commit_ids['ancestor'])
679 commit_id=commit_ids['ancestor'])
674 pull_request.revisions = [commit_ids['change']]
680 pull_request.revisions = [commit_ids['change']]
675 pull_request.title = u"Test"
681 pull_request.title = u"Test"
676 pull_request.description = u"Description"
682 pull_request.description = u"Description"
677 pull_request.author = UserModel().get_by_username(
683 pull_request.author = UserModel().get_by_username(
678 TEST_USER_ADMIN_LOGIN)
684 TEST_USER_ADMIN_LOGIN)
679 Session().add(pull_request)
685 Session().add(pull_request)
680 Session().commit()
686 Session().commit()
681 pull_request_id = pull_request.pull_request_id
687 pull_request_id = pull_request.pull_request_id
682
688
683 # target has ancestor - ancestor-new
689 # target has ancestor - ancestor-new
684 # source has ancestor - ancestor-new - change-rebased
690 # source has ancestor - ancestor-new - change-rebased
685 backend.pull_heads(target, heads=['ancestor-new'])
691 backend.pull_heads(target, heads=['ancestor-new'])
686 backend.pull_heads(source, heads=['change-rebased'])
692 backend.pull_heads(source, heads=['change-rebased'])
687
693
688 # update PR
694 # update PR
689 self.app.post(
695 self.app.post(
690 url(controller='pullrequests', action='update',
696 url(controller='pullrequests', action='update',
691 repo_name=target.repo_name,
697 repo_name=target.repo_name,
692 pull_request_id=str(pull_request_id)),
698 pull_request_id=str(pull_request_id)),
693 params={'update_commits': 'true', '_method': 'put',
699 params={'update_commits': 'true', '_method': 'put',
694 'csrf_token': csrf_token},
700 'csrf_token': csrf_token},
695 status=200)
701 status=200)
696
702
697 # check that we have now both revisions
703 # check that we have now both revisions
698 pull_request = PullRequest.get(pull_request_id)
704 pull_request = PullRequest.get(pull_request_id)
699 assert pull_request.revisions == [commit_ids['change-rebased']]
705 assert pull_request.revisions == [commit_ids['change-rebased']]
700 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
706 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
701 branch=backend.default_branch_name,
707 branch=backend.default_branch_name,
702 commit_id=commit_ids['ancestor-new'])
708 commit_id=commit_ids['ancestor-new'])
703
709
704 # TODO: johbo: This should be a test on its own
710 # TODO: johbo: This should be a test on its own
705 response = self.app.get(url(
711 response = self.app.get(url(
706 controller='pullrequests', action='index',
712 controller='pullrequests', action='index',
707 repo_name=target.repo_name))
713 repo_name=target.repo_name))
708 assert response.status_int == 200
714 assert response.status_int == 200
709 assert 'Pull request updated to' in response.body
715 assert 'Pull request updated to' in response.body
710 assert 'with 1 added, 1 removed commits.' in response.body
716 assert 'with 1 added, 1 removed commits.' in response.body
711
717
712 def test_update_of_ancestor_reference(self, backend, csrf_token):
718 def test_update_of_ancestor_reference(self, backend, csrf_token):
713 commits = [
719 commits = [
714 {'message': 'ancestor'},
720 {'message': 'ancestor'},
715 {'message': 'change'},
721 {'message': 'change'},
716 {'message': 'change-2'},
722 {'message': 'change-2'},
717 {'message': 'ancestor-new', 'parents': ['ancestor']},
723 {'message': 'ancestor-new', 'parents': ['ancestor']},
718 {'message': 'change-rebased'},
724 {'message': 'change-rebased'},
719 ]
725 ]
720 commit_ids = backend.create_master_repo(commits)
726 commit_ids = backend.create_master_repo(commits)
721 target = backend.create_repo(heads=['ancestor'])
727 target = backend.create_repo(heads=['ancestor'])
722 source = backend.create_repo(heads=['change'])
728 source = backend.create_repo(heads=['change'])
723
729
724 # create pr from a in source to A in target
730 # create pr from a in source to A in target
725 pull_request = PullRequest()
731 pull_request = PullRequest()
726 pull_request.source_repo = source
732 pull_request.source_repo = source
727 # TODO: johbo: Make sure that we write the source ref this way!
733 # TODO: johbo: Make sure that we write the source ref this way!
728 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
734 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
729 branch=backend.default_branch_name,
735 branch=backend.default_branch_name,
730 commit_id=commit_ids['change'])
736 commit_id=commit_ids['change'])
731 pull_request.target_repo = target
737 pull_request.target_repo = target
732 # TODO: johbo: Target ref should be branch based, since tip can jump
738 # TODO: johbo: Target ref should be branch based, since tip can jump
733 # from branch to branch
739 # from branch to branch
734 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
740 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
735 branch=backend.default_branch_name,
741 branch=backend.default_branch_name,
736 commit_id=commit_ids['ancestor'])
742 commit_id=commit_ids['ancestor'])
737 pull_request.revisions = [commit_ids['change']]
743 pull_request.revisions = [commit_ids['change']]
738 pull_request.title = u"Test"
744 pull_request.title = u"Test"
739 pull_request.description = u"Description"
745 pull_request.description = u"Description"
740 pull_request.author = UserModel().get_by_username(
746 pull_request.author = UserModel().get_by_username(
741 TEST_USER_ADMIN_LOGIN)
747 TEST_USER_ADMIN_LOGIN)
742 Session().add(pull_request)
748 Session().add(pull_request)
743 Session().commit()
749 Session().commit()
744 pull_request_id = pull_request.pull_request_id
750 pull_request_id = pull_request.pull_request_id
745
751
746 # target has ancestor - ancestor-new
752 # target has ancestor - ancestor-new
747 # source has ancestor - ancestor-new - change-rebased
753 # source has ancestor - ancestor-new - change-rebased
748 backend.pull_heads(target, heads=['ancestor-new'])
754 backend.pull_heads(target, heads=['ancestor-new'])
749 backend.pull_heads(source, heads=['change-rebased'])
755 backend.pull_heads(source, heads=['change-rebased'])
750
756
751 # update PR
757 # update PR
752 self.app.post(
758 self.app.post(
753 url(controller='pullrequests', action='update',
759 url(controller='pullrequests', action='update',
754 repo_name=target.repo_name,
760 repo_name=target.repo_name,
755 pull_request_id=str(pull_request_id)),
761 pull_request_id=str(pull_request_id)),
756 params={'update_commits': 'true', '_method': 'put',
762 params={'update_commits': 'true', '_method': 'put',
757 'csrf_token': csrf_token},
763 'csrf_token': csrf_token},
758 status=200)
764 status=200)
759
765
760 # Expect the target reference to be updated correctly
766 # Expect the target reference to be updated correctly
761 pull_request = PullRequest.get(pull_request_id)
767 pull_request = PullRequest.get(pull_request_id)
762 assert pull_request.revisions == [commit_ids['change-rebased']]
768 assert pull_request.revisions == [commit_ids['change-rebased']]
763 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
769 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
764 branch=backend.default_branch_name,
770 branch=backend.default_branch_name,
765 commit_id=commit_ids['ancestor-new'])
771 commit_id=commit_ids['ancestor-new'])
766 assert pull_request.target_ref == expected_target_ref
772 assert pull_request.target_ref == expected_target_ref
767
773
768 def test_remove_pull_request_branch(self, backend_git, csrf_token):
774 def test_remove_pull_request_branch(self, backend_git, csrf_token):
769 branch_name = 'development'
775 branch_name = 'development'
770 commits = [
776 commits = [
771 {'message': 'initial-commit'},
777 {'message': 'initial-commit'},
772 {'message': 'old-feature'},
778 {'message': 'old-feature'},
773 {'message': 'new-feature', 'branch': branch_name},
779 {'message': 'new-feature', 'branch': branch_name},
774 ]
780 ]
775 repo = backend_git.create_repo(commits)
781 repo = backend_git.create_repo(commits)
776 commit_ids = backend_git.commit_ids
782 commit_ids = backend_git.commit_ids
777
783
778 pull_request = PullRequest()
784 pull_request = PullRequest()
779 pull_request.source_repo = repo
785 pull_request.source_repo = repo
780 pull_request.target_repo = repo
786 pull_request.target_repo = repo
781 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
787 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
782 branch=branch_name, commit_id=commit_ids['new-feature'])
788 branch=branch_name, commit_id=commit_ids['new-feature'])
783 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
789 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
784 branch=backend_git.default_branch_name,
790 branch=backend_git.default_branch_name,
785 commit_id=commit_ids['old-feature'])
791 commit_id=commit_ids['old-feature'])
786 pull_request.revisions = [commit_ids['new-feature']]
792 pull_request.revisions = [commit_ids['new-feature']]
787 pull_request.title = u"Test"
793 pull_request.title = u"Test"
788 pull_request.description = u"Description"
794 pull_request.description = u"Description"
789 pull_request.author = UserModel().get_by_username(
795 pull_request.author = UserModel().get_by_username(
790 TEST_USER_ADMIN_LOGIN)
796 TEST_USER_ADMIN_LOGIN)
791 Session().add(pull_request)
797 Session().add(pull_request)
792 Session().commit()
798 Session().commit()
793
799
794 vcs = repo.scm_instance()
800 vcs = repo.scm_instance()
795 vcs.remove_ref('refs/heads/{}'.format(branch_name))
801 vcs.remove_ref('refs/heads/{}'.format(branch_name))
796
802
797 response = self.app.get(url(
803 response = self.app.get(url(
798 controller='pullrequests', action='show',
804 controller='pullrequests', action='show',
799 repo_name=repo.repo_name,
805 repo_name=repo.repo_name,
800 pull_request_id=str(pull_request.pull_request_id)))
806 pull_request_id=str(pull_request.pull_request_id)))
801
807
802 assert response.status_int == 200
808 assert response.status_int == 200
803 assert_response = AssertResponse(response)
809 assert_response = AssertResponse(response)
804 assert_response.element_contains(
810 assert_response.element_contains(
805 '#changeset_compare_view_content .alert strong',
811 '#changeset_compare_view_content .alert strong',
806 'Missing commits')
812 'Missing commits')
807 assert_response.element_contains(
813 assert_response.element_contains(
808 '#changeset_compare_view_content .alert',
814 '#changeset_compare_view_content .alert',
809 'This pull request cannot be displayed, because one or more'
815 'This pull request cannot be displayed, because one or more'
810 ' commits no longer exist in the source repository.')
816 ' commits no longer exist in the source repository.')
811
817
812 def test_strip_commits_from_pull_request(
818 def test_strip_commits_from_pull_request(
813 self, backend, pr_util, csrf_token):
819 self, backend, pr_util, csrf_token):
814 commits = [
820 commits = [
815 {'message': 'initial-commit'},
821 {'message': 'initial-commit'},
816 {'message': 'old-feature'},
822 {'message': 'old-feature'},
817 {'message': 'new-feature', 'parents': ['initial-commit']},
823 {'message': 'new-feature', 'parents': ['initial-commit']},
818 ]
824 ]
819 pull_request = pr_util.create_pull_request(
825 pull_request = pr_util.create_pull_request(
820 commits, target_head='initial-commit', source_head='new-feature',
826 commits, target_head='initial-commit', source_head='new-feature',
821 revisions=['new-feature'])
827 revisions=['new-feature'])
822
828
823 vcs = pr_util.source_repository.scm_instance()
829 vcs = pr_util.source_repository.scm_instance()
824 if backend.alias == 'git':
830 if backend.alias == 'git':
825 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
831 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
826 else:
832 else:
827 vcs.strip(pr_util.commit_ids['new-feature'])
833 vcs.strip(pr_util.commit_ids['new-feature'])
828
834
829 response = self.app.get(url(
835 response = self.app.get(url(
830 controller='pullrequests', action='show',
836 controller='pullrequests', action='show',
831 repo_name=pr_util.target_repository.repo_name,
837 repo_name=pr_util.target_repository.repo_name,
832 pull_request_id=str(pull_request.pull_request_id)))
838 pull_request_id=str(pull_request.pull_request_id)))
833
839
834 assert response.status_int == 200
840 assert response.status_int == 200
835 assert_response = AssertResponse(response)
841 assert_response = AssertResponse(response)
836 assert_response.element_contains(
842 assert_response.element_contains(
837 '#changeset_compare_view_content .alert strong',
843 '#changeset_compare_view_content .alert strong',
838 'Missing commits')
844 'Missing commits')
839 assert_response.element_contains(
845 assert_response.element_contains(
840 '#changeset_compare_view_content .alert',
846 '#changeset_compare_view_content .alert',
841 'This pull request cannot be displayed, because one or more'
847 'This pull request cannot be displayed, because one or more'
842 ' commits no longer exist in the source repository.')
848 ' commits no longer exist in the source repository.')
843 assert_response.element_contains(
849 assert_response.element_contains(
844 '#update_commits',
850 '#update_commits',
845 'Update commits')
851 'Update commits')
846
852
847 def test_strip_commits_and_update(
853 def test_strip_commits_and_update(
848 self, backend, pr_util, csrf_token):
854 self, backend, pr_util, csrf_token):
849 commits = [
855 commits = [
850 {'message': 'initial-commit'},
856 {'message': 'initial-commit'},
851 {'message': 'old-feature'},
857 {'message': 'old-feature'},
852 {'message': 'new-feature', 'parents': ['old-feature']},
858 {'message': 'new-feature', 'parents': ['old-feature']},
853 ]
859 ]
854 pull_request = pr_util.create_pull_request(
860 pull_request = pr_util.create_pull_request(
855 commits, target_head='old-feature', source_head='new-feature',
861 commits, target_head='old-feature', source_head='new-feature',
856 revisions=['new-feature'], mergeable=True)
862 revisions=['new-feature'], mergeable=True)
857
863
858 vcs = pr_util.source_repository.scm_instance()
864 vcs = pr_util.source_repository.scm_instance()
859 if backend.alias == 'git':
865 if backend.alias == 'git':
860 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
866 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
861 else:
867 else:
862 vcs.strip(pr_util.commit_ids['new-feature'])
868 vcs.strip(pr_util.commit_ids['new-feature'])
863
869
864 response = self.app.post(
870 response = self.app.post(
865 url(controller='pullrequests', action='update',
871 url(controller='pullrequests', action='update',
866 repo_name=pull_request.target_repo.repo_name,
872 repo_name=pull_request.target_repo.repo_name,
867 pull_request_id=str(pull_request.pull_request_id)),
873 pull_request_id=str(pull_request.pull_request_id)),
868 params={'update_commits': 'true', '_method': 'put',
874 params={'update_commits': 'true', '_method': 'put',
869 'csrf_token': csrf_token})
875 'csrf_token': csrf_token})
870
876
871 assert response.status_int == 200
877 assert response.status_int == 200
872 assert response.body == 'true'
878 assert response.body == 'true'
873
879
874 # Make sure that after update, it won't raise 500 errors
880 # Make sure that after update, it won't raise 500 errors
875 response = self.app.get(url(
881 response = self.app.get(url(
876 controller='pullrequests', action='show',
882 controller='pullrequests', action='show',
877 repo_name=pr_util.target_repository.repo_name,
883 repo_name=pr_util.target_repository.repo_name,
878 pull_request_id=str(pull_request.pull_request_id)))
884 pull_request_id=str(pull_request.pull_request_id)))
879
885
880 assert response.status_int == 200
886 assert response.status_int == 200
881 assert_response = AssertResponse(response)
887 assert_response = AssertResponse(response)
882 assert_response.element_contains(
888 assert_response.element_contains(
883 '#changeset_compare_view_content .alert strong',
889 '#changeset_compare_view_content .alert strong',
884 'Missing commits')
890 'Missing commits')
885
891
886 def test_branch_is_a_link(self, pr_util):
892 def test_branch_is_a_link(self, pr_util):
887 pull_request = pr_util.create_pull_request()
893 pull_request = pr_util.create_pull_request()
888 pull_request.source_ref = 'branch:origin:1234567890abcdef'
894 pull_request.source_ref = 'branch:origin:1234567890abcdef'
889 pull_request.target_ref = 'branch:target:abcdef1234567890'
895 pull_request.target_ref = 'branch:target:abcdef1234567890'
890 Session().add(pull_request)
896 Session().add(pull_request)
891 Session().commit()
897 Session().commit()
892
898
893 response = self.app.get(url(
899 response = self.app.get(url(
894 controller='pullrequests', action='show',
900 controller='pullrequests', action='show',
895 repo_name=pull_request.target_repo.scm_instance().name,
901 repo_name=pull_request.target_repo.scm_instance().name,
896 pull_request_id=str(pull_request.pull_request_id)))
902 pull_request_id=str(pull_request.pull_request_id)))
897 assert response.status_int == 200
903 assert response.status_int == 200
898 assert_response = AssertResponse(response)
904 assert_response = AssertResponse(response)
899
905
900 origin = assert_response.get_element('.pr-origininfo .tag')
906 origin = assert_response.get_element('.pr-origininfo .tag')
901 origin_children = origin.getchildren()
907 origin_children = origin.getchildren()
902 assert len(origin_children) == 1
908 assert len(origin_children) == 1
903 target = assert_response.get_element('.pr-targetinfo .tag')
909 target = assert_response.get_element('.pr-targetinfo .tag')
904 target_children = target.getchildren()
910 target_children = target.getchildren()
905 assert len(target_children) == 1
911 assert len(target_children) == 1
906
912
907 expected_origin_link = url(
913 expected_origin_link = url(
908 'changelog_home',
914 'changelog_home',
909 repo_name=pull_request.source_repo.scm_instance().name,
915 repo_name=pull_request.source_repo.scm_instance().name,
910 branch='origin')
916 branch='origin')
911 expected_target_link = url(
917 expected_target_link = url(
912 'changelog_home',
918 'changelog_home',
913 repo_name=pull_request.target_repo.scm_instance().name,
919 repo_name=pull_request.target_repo.scm_instance().name,
914 branch='target')
920 branch='target')
915 assert origin_children[0].attrib['href'] == expected_origin_link
921 assert origin_children[0].attrib['href'] == expected_origin_link
916 assert origin_children[0].text == 'branch: origin'
922 assert origin_children[0].text == 'branch: origin'
917 assert target_children[0].attrib['href'] == expected_target_link
923 assert target_children[0].attrib['href'] == expected_target_link
918 assert target_children[0].text == 'branch: target'
924 assert target_children[0].text == 'branch: target'
919
925
920 def test_bookmark_is_not_a_link(self, pr_util):
926 def test_bookmark_is_not_a_link(self, pr_util):
921 pull_request = pr_util.create_pull_request()
927 pull_request = pr_util.create_pull_request()
922 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
928 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
923 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
929 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
924 Session().add(pull_request)
930 Session().add(pull_request)
925 Session().commit()
931 Session().commit()
926
932
927 response = self.app.get(url(
933 response = self.app.get(url(
928 controller='pullrequests', action='show',
934 controller='pullrequests', action='show',
929 repo_name=pull_request.target_repo.scm_instance().name,
935 repo_name=pull_request.target_repo.scm_instance().name,
930 pull_request_id=str(pull_request.pull_request_id)))
936 pull_request_id=str(pull_request.pull_request_id)))
931 assert response.status_int == 200
937 assert response.status_int == 200
932 assert_response = AssertResponse(response)
938 assert_response = AssertResponse(response)
933
939
934 origin = assert_response.get_element('.pr-origininfo .tag')
940 origin = assert_response.get_element('.pr-origininfo .tag')
935 assert origin.text.strip() == 'bookmark: origin'
941 assert origin.text.strip() == 'bookmark: origin'
936 assert origin.getchildren() == []
942 assert origin.getchildren() == []
937
943
938 target = assert_response.get_element('.pr-targetinfo .tag')
944 target = assert_response.get_element('.pr-targetinfo .tag')
939 assert target.text.strip() == 'bookmark: target'
945 assert target.text.strip() == 'bookmark: target'
940 assert target.getchildren() == []
946 assert target.getchildren() == []
941
947
942 def test_tag_is_not_a_link(self, pr_util):
948 def test_tag_is_not_a_link(self, pr_util):
943 pull_request = pr_util.create_pull_request()
949 pull_request = pr_util.create_pull_request()
944 pull_request.source_ref = 'tag:origin:1234567890abcdef'
950 pull_request.source_ref = 'tag:origin:1234567890abcdef'
945 pull_request.target_ref = 'tag:target:abcdef1234567890'
951 pull_request.target_ref = 'tag:target:abcdef1234567890'
946 Session().add(pull_request)
952 Session().add(pull_request)
947 Session().commit()
953 Session().commit()
948
954
949 response = self.app.get(url(
955 response = self.app.get(url(
950 controller='pullrequests', action='show',
956 controller='pullrequests', action='show',
951 repo_name=pull_request.target_repo.scm_instance().name,
957 repo_name=pull_request.target_repo.scm_instance().name,
952 pull_request_id=str(pull_request.pull_request_id)))
958 pull_request_id=str(pull_request.pull_request_id)))
953 assert response.status_int == 200
959 assert response.status_int == 200
954 assert_response = AssertResponse(response)
960 assert_response = AssertResponse(response)
955
961
956 origin = assert_response.get_element('.pr-origininfo .tag')
962 origin = assert_response.get_element('.pr-origininfo .tag')
957 assert origin.text.strip() == 'tag: origin'
963 assert origin.text.strip() == 'tag: origin'
958 assert origin.getchildren() == []
964 assert origin.getchildren() == []
959
965
960 target = assert_response.get_element('.pr-targetinfo .tag')
966 target = assert_response.get_element('.pr-targetinfo .tag')
961 assert target.text.strip() == 'tag: target'
967 assert target.text.strip() == 'tag: target'
962 assert target.getchildren() == []
968 assert target.getchildren() == []
963
969
964 def test_description_is_escaped_on_index_page(self, backend, pr_util):
970 def test_description_is_escaped_on_index_page(self, backend, pr_util):
965 xss_description = "<script>alert('Hi!')</script>"
971 xss_description = "<script>alert('Hi!')</script>"
966 pull_request = pr_util.create_pull_request(description=xss_description)
972 pull_request = pr_util.create_pull_request(description=xss_description)
967 response = self.app.get(url(
973 response = self.app.get(url(
968 controller='pullrequests', action='show_all',
974 controller='pullrequests', action='show_all',
969 repo_name=pull_request.target_repo.repo_name))
975 repo_name=pull_request.target_repo.repo_name))
970 response.mustcontain(
976 response.mustcontain(
971 "&lt;script&gt;alert(&#39;Hi!&#39;)&lt;/script&gt;")
977 "&lt;script&gt;alert(&#39;Hi!&#39;)&lt;/script&gt;")
972
978
973 @pytest.mark.parametrize('mergeable', [True, False])
979 @pytest.mark.parametrize('mergeable', [True, False])
974 def test_shadow_repository_link(
980 def test_shadow_repository_link(
975 self, mergeable, pr_util, http_host_stub):
981 self, mergeable, pr_util, http_host_stub):
976 """
982 """
977 Check that the pull request summary page displays a link to the shadow
983 Check that the pull request summary page displays a link to the shadow
978 repository if the pull request is mergeable. If it is not mergeable
984 repository if the pull request is mergeable. If it is not mergeable
979 the link should not be displayed.
985 the link should not be displayed.
980 """
986 """
981 pull_request = pr_util.create_pull_request(
987 pull_request = pr_util.create_pull_request(
982 mergeable=mergeable, enable_notifications=False)
988 mergeable=mergeable, enable_notifications=False)
983 target_repo = pull_request.target_repo.scm_instance()
989 target_repo = pull_request.target_repo.scm_instance()
984 pr_id = pull_request.pull_request_id
990 pr_id = pull_request.pull_request_id
985 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
991 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
986 host=http_host_stub, repo=target_repo.name, pr_id=pr_id)
992 host=http_host_stub, repo=target_repo.name, pr_id=pr_id)
987
993
988 response = self.app.get(url(
994 response = self.app.get(url(
989 controller='pullrequests', action='show',
995 controller='pullrequests', action='show',
990 repo_name=target_repo.name,
996 repo_name=target_repo.name,
991 pull_request_id=str(pr_id)))
997 pull_request_id=str(pr_id)))
992
998
993 assertr = AssertResponse(response)
999 assertr = AssertResponse(response)
994 if mergeable:
1000 if mergeable:
995 assertr.element_value_contains(
1001 assertr.element_value_contains(
996 'div.pr-mergeinfo input', shadow_url)
1002 'div.pr-mergeinfo input', shadow_url)
997 assertr.element_value_contains(
1003 assertr.element_value_contains(
998 'div.pr-mergeinfo input', 'pr-merge')
1004 'div.pr-mergeinfo input', 'pr-merge')
999 else:
1005 else:
1000 assertr.no_element_exists('div.pr-mergeinfo')
1006 assertr.no_element_exists('div.pr-mergeinfo')
1001
1007
1002
1008
1003 @pytest.mark.usefixtures('app')
1009 @pytest.mark.usefixtures('app')
1004 @pytest.mark.backends("git", "hg")
1010 @pytest.mark.backends("git", "hg")
1005 class TestPullrequestsControllerDelete(object):
1011 class TestPullrequestsControllerDelete(object):
1006 def test_pull_request_delete_button_permissions_admin(
1012 def test_pull_request_delete_button_permissions_admin(
1007 self, autologin_user, user_admin, pr_util):
1013 self, autologin_user, user_admin, pr_util):
1008 pull_request = pr_util.create_pull_request(
1014 pull_request = pr_util.create_pull_request(
1009 author=user_admin.username, enable_notifications=False)
1015 author=user_admin.username, enable_notifications=False)
1010
1016
1011 response = self.app.get(url(
1017 response = self.app.get(url(
1012 controller='pullrequests', action='show',
1018 controller='pullrequests', action='show',
1013 repo_name=pull_request.target_repo.scm_instance().name,
1019 repo_name=pull_request.target_repo.scm_instance().name,
1014 pull_request_id=str(pull_request.pull_request_id)))
1020 pull_request_id=str(pull_request.pull_request_id)))
1015
1021
1016 response.mustcontain('id="delete_pullrequest"')
1022 response.mustcontain('id="delete_pullrequest"')
1017 response.mustcontain('Confirm to delete this pull request')
1023 response.mustcontain('Confirm to delete this pull request')
1018
1024
1019 def test_pull_request_delete_button_permissions_owner(
1025 def test_pull_request_delete_button_permissions_owner(
1020 self, autologin_regular_user, user_regular, pr_util):
1026 self, autologin_regular_user, user_regular, pr_util):
1021 pull_request = pr_util.create_pull_request(
1027 pull_request = pr_util.create_pull_request(
1022 author=user_regular.username, enable_notifications=False)
1028 author=user_regular.username, enable_notifications=False)
1023
1029
1024 response = self.app.get(url(
1030 response = self.app.get(url(
1025 controller='pullrequests', action='show',
1031 controller='pullrequests', action='show',
1026 repo_name=pull_request.target_repo.scm_instance().name,
1032 repo_name=pull_request.target_repo.scm_instance().name,
1027 pull_request_id=str(pull_request.pull_request_id)))
1033 pull_request_id=str(pull_request.pull_request_id)))
1028
1034
1029 response.mustcontain('id="delete_pullrequest"')
1035 response.mustcontain('id="delete_pullrequest"')
1030 response.mustcontain('Confirm to delete this pull request')
1036 response.mustcontain('Confirm to delete this pull request')
1031
1037
1032 def test_pull_request_delete_button_permissions_forbidden(
1038 def test_pull_request_delete_button_permissions_forbidden(
1033 self, autologin_regular_user, user_regular, user_admin, pr_util):
1039 self, autologin_regular_user, user_regular, user_admin, pr_util):
1034 pull_request = pr_util.create_pull_request(
1040 pull_request = pr_util.create_pull_request(
1035 author=user_admin.username, enable_notifications=False)
1041 author=user_admin.username, enable_notifications=False)
1036
1042
1037 response = self.app.get(url(
1043 response = self.app.get(url(
1038 controller='pullrequests', action='show',
1044 controller='pullrequests', action='show',
1039 repo_name=pull_request.target_repo.scm_instance().name,
1045 repo_name=pull_request.target_repo.scm_instance().name,
1040 pull_request_id=str(pull_request.pull_request_id)))
1046 pull_request_id=str(pull_request.pull_request_id)))
1041 response.mustcontain(no=['id="delete_pullrequest"'])
1047 response.mustcontain(no=['id="delete_pullrequest"'])
1042 response.mustcontain(no=['Confirm to delete this pull request'])
1048 response.mustcontain(no=['Confirm to delete this pull request'])
1043
1049
1044 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1050 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1045 self, autologin_regular_user, user_regular, user_admin, pr_util,
1051 self, autologin_regular_user, user_regular, user_admin, pr_util,
1046 user_util):
1052 user_util):
1047
1053
1048 pull_request = pr_util.create_pull_request(
1054 pull_request = pr_util.create_pull_request(
1049 author=user_admin.username, enable_notifications=False)
1055 author=user_admin.username, enable_notifications=False)
1050
1056
1051 user_util.grant_user_permission_to_repo(
1057 user_util.grant_user_permission_to_repo(
1052 pull_request.target_repo, user_regular,
1058 pull_request.target_repo, user_regular,
1053 'repository.write')
1059 'repository.write')
1054
1060
1055 response = self.app.get(url(
1061 response = self.app.get(url(
1056 controller='pullrequests', action='show',
1062 controller='pullrequests', action='show',
1057 repo_name=pull_request.target_repo.scm_instance().name,
1063 repo_name=pull_request.target_repo.scm_instance().name,
1058 pull_request_id=str(pull_request.pull_request_id)))
1064 pull_request_id=str(pull_request.pull_request_id)))
1059
1065
1060 response.mustcontain('id="open_edit_pullrequest"')
1066 response.mustcontain('id="open_edit_pullrequest"')
1061 response.mustcontain('id="delete_pullrequest"')
1067 response.mustcontain('id="delete_pullrequest"')
1062 response.mustcontain(no=['Confirm to delete this pull request'])
1068 response.mustcontain(no=['Confirm to delete this pull request'])
1063
1069
1064
1070
1065 def assert_pull_request_status(pull_request, expected_status):
1071 def assert_pull_request_status(pull_request, expected_status):
1066 status = ChangesetStatusModel().calculated_review_status(
1072 status = ChangesetStatusModel().calculated_review_status(
1067 pull_request=pull_request)
1073 pull_request=pull_request)
1068 assert status == expected_status
1074 assert status == expected_status
1069
1075
1070
1076
1071 @pytest.mark.parametrize('action', ['show_all', 'index', 'create'])
1077 @pytest.mark.parametrize('action', ['show_all', 'index', 'create'])
1072 @pytest.mark.usefixtures("autologin_user")
1078 @pytest.mark.usefixtures("autologin_user")
1073 def test_redirects_to_repo_summary_for_svn_repositories(
1079 def test_redirects_to_repo_summary_for_svn_repositories(
1074 backend_svn, app, action):
1080 backend_svn, app, action):
1075 denied_actions = ['show_all', 'index', 'create']
1081 denied_actions = ['show_all', 'index', 'create']
1076 for action in denied_actions:
1082 for action in denied_actions:
1077 response = app.get(url(
1083 response = app.get(url(
1078 controller='pullrequests', action=action,
1084 controller='pullrequests', action=action,
1079 repo_name=backend_svn.repo_name))
1085 repo_name=backend_svn.repo_name))
1080 assert response.status_int == 302
1086 assert response.status_int == 302
1081
1087
1082 # Not allowed, redirect to the summary
1088 # Not allowed, redirect to the summary
1083 redirected = response.follow()
1089 redirected = response.follow()
1084 summary_url = url('summary_home', repo_name=backend_svn.repo_name)
1090 summary_url = url('summary_home', repo_name=backend_svn.repo_name)
1085
1091
1086 # URL adds leading slash and path doesn't have it
1092 # URL adds leading slash and path doesn't have it
1087 assert redirected.req.path == summary_url
1093 assert redirected.req.path == summary_url
1088
1094
1089
1095
1090 def test_delete_comment_returns_404_if_comment_does_not_exist(pylonsapp):
1096 def test_delete_comment_returns_404_if_comment_does_not_exist(pylonsapp):
1091 # TODO: johbo: Global import not possible because models.forms blows up
1097 # TODO: johbo: Global import not possible because models.forms blows up
1092 from rhodecode.controllers.pullrequests import PullrequestsController
1098 from rhodecode.controllers.pullrequests import PullrequestsController
1093 controller = PullrequestsController()
1099 controller = PullrequestsController()
1094 patcher = mock.patch(
1100 patcher = mock.patch(
1095 'rhodecode.model.db.BaseModel.get', return_value=None)
1101 'rhodecode.model.db.BaseModel.get', return_value=None)
1096 with pytest.raises(HTTPNotFound), patcher:
1102 with pytest.raises(HTTPNotFound), patcher:
1097 controller._delete_comment(1)
1103 controller._delete_comment(1)
@@ -1,144 +1,141 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23 from rhodecode.model.db import Session, UserLog
24 from rhodecode.lib import hooks_base, utils2
24 from rhodecode.lib import hooks_base, utils2
25
25
26
26
27 @mock.patch.multiple(
28 hooks_base,
29 action_logger=mock.Mock(),
30 post_push_extension=mock.Mock(),
31 Repository=mock.Mock())
32 def test_post_push_truncates_commits(user_regular, repo_stub):
27 def test_post_push_truncates_commits(user_regular, repo_stub):
33 extras = {
28 extras = {
34 'ip': '127.0.0.1',
29 'ip': '127.0.0.1',
35 'username': user_regular.username,
30 'username': user_regular.username,
36 'action': 'push_local',
31 'action': 'push_local',
37 'repository': repo_stub.repo_name,
32 'repository': repo_stub.repo_name,
38 'scm': 'git',
33 'scm': 'git',
39 'config': '',
34 'config': '',
40 'server_url': 'http://example.com',
35 'server_url': 'http://example.com',
41 'make_lock': None,
36 'make_lock': None,
42 'user_agent': 'some-client',
37 'user_agent': 'some-client',
43 'locked_by': [None],
38 'locked_by': [None],
44 'commit_ids': ['abcde12345' * 4] * 30000,
39 'commit_ids': ['abcde12345' * 4] * 30000,
45 'is_shadow_repo': False,
40 'is_shadow_repo': False,
46 }
41 }
47 extras = utils2.AttributeDict(extras)
42 extras = utils2.AttributeDict(extras)
48
43
49 hooks_base.post_push(extras)
44 hooks_base.post_push(extras)
50
45
51 # Calculate appropriate action string here
46 # Calculate appropriate action string here
52 expected_action = 'push_local:%s' % ','.join(extras.commit_ids[:29000])
47 commit_ids = extras.commit_ids[:10000]
53
48
54 hooks_base.action_logger.assert_called_with(
49 entry = UserLog.query().order_by('-user_log_id').first()
55 extras.username, expected_action, extras.repository, extras.ip,
50 assert entry.action == 'user.push'
56 commit=True)
51 assert entry.action_data['commit_ids'] == commit_ids
52 Session().delete(entry)
53 Session().commit()
57
54
58
55
59 def assert_called_with_mock(callable_, expected_mock_name):
56 def assert_called_with_mock(callable_, expected_mock_name):
60 mock_obj = callable_.call_args[0][0]
57 mock_obj = callable_.call_args[0][0]
61 mock_name = mock_obj._mock_new_parent._mock_new_name
58 mock_name = mock_obj._mock_new_parent._mock_new_name
62 assert mock_name == expected_mock_name
59 assert mock_name == expected_mock_name
63
60
64
61
65 @pytest.fixture
62 @pytest.fixture
66 def hook_extras(user_regular, repo_stub):
63 def hook_extras(user_regular, repo_stub):
67 extras = utils2.AttributeDict({
64 extras = utils2.AttributeDict({
68 'ip': '127.0.0.1',
65 'ip': '127.0.0.1',
69 'username': user_regular.username,
66 'username': user_regular.username,
70 'action': 'push',
67 'action': 'push',
71 'repository': repo_stub.repo_name,
68 'repository': repo_stub.repo_name,
72 'scm': '',
69 'scm': '',
73 'config': '',
70 'config': '',
74 'server_url': 'http://example.com',
71 'server_url': 'http://example.com',
75 'make_lock': None,
72 'make_lock': None,
76 'user_agent': 'some-client',
73 'user_agent': 'some-client',
77 'locked_by': [None],
74 'locked_by': [None],
78 'commit_ids': [],
75 'commit_ids': [],
79 'is_shadow_repo': False,
76 'is_shadow_repo': False,
80 })
77 })
81 return extras
78 return extras
82
79
83
80
84 @pytest.mark.parametrize('func, extension, event', [
81 @pytest.mark.parametrize('func, extension, event', [
85 (hooks_base.pre_push, 'pre_push_extension', 'RepoPrePushEvent'),
82 (hooks_base.pre_push, 'pre_push_extension', 'RepoPrePushEvent'),
86 (hooks_base.post_push, 'post_pull_extension', 'RepoPushEvent'),
83 (hooks_base.post_push, 'post_pull_extension', 'RepoPushEvent'),
87 (hooks_base.pre_pull, 'pre_pull_extension', 'RepoPrePullEvent'),
84 (hooks_base.pre_pull, 'pre_pull_extension', 'RepoPrePullEvent'),
88 (hooks_base.post_pull, 'post_push_extension', 'RepoPullEvent'),
85 (hooks_base.post_pull, 'post_push_extension', 'RepoPullEvent'),
89 ])
86 ])
90 def test_hooks_propagate(func, extension, event, hook_extras):
87 def test_hooks_propagate(func, extension, event, hook_extras):
91 """
88 """
92 Tests that our hook code propagates to rhodecode extensions and triggers
89 Tests that our hook code propagates to rhodecode extensions and triggers
93 the appropriate event.
90 the appropriate event.
94 """
91 """
95 extension_mock = mock.Mock()
92 extension_mock = mock.Mock()
96 events_mock = mock.Mock()
93 events_mock = mock.Mock()
97 patches = {
94 patches = {
98 'Repository': mock.Mock(),
95 'Repository': mock.Mock(),
99 'events': events_mock,
96 'events': events_mock,
100 extension: extension_mock,
97 extension: extension_mock,
101 }
98 }
102
99
103 # Clear shadow repo flag.
100 # Clear shadow repo flag.
104 hook_extras.is_shadow_repo = False
101 hook_extras.is_shadow_repo = False
105
102
106 # Execute hook function.
103 # Execute hook function.
107 with mock.patch.multiple(hooks_base, **patches):
104 with mock.patch.multiple(hooks_base, **patches):
108 func(hook_extras)
105 func(hook_extras)
109
106
110 # Assert that extensions are called and event was fired.
107 # Assert that extensions are called and event was fired.
111 extension_mock.called_once()
108 extension_mock.called_once()
112 assert_called_with_mock(events_mock.trigger, event)
109 assert_called_with_mock(events_mock.trigger, event)
113
110
114
111
115 @pytest.mark.parametrize('func, extension, event', [
112 @pytest.mark.parametrize('func, extension, event', [
116 (hooks_base.pre_push, 'pre_push_extension', 'RepoPrePushEvent'),
113 (hooks_base.pre_push, 'pre_push_extension', 'RepoPrePushEvent'),
117 (hooks_base.post_push, 'post_pull_extension', 'RepoPushEvent'),
114 (hooks_base.post_push, 'post_pull_extension', 'RepoPushEvent'),
118 (hooks_base.pre_pull, 'pre_pull_extension', 'RepoPrePullEvent'),
115 (hooks_base.pre_pull, 'pre_pull_extension', 'RepoPrePullEvent'),
119 (hooks_base.post_pull, 'post_push_extension', 'RepoPullEvent'),
116 (hooks_base.post_pull, 'post_push_extension', 'RepoPullEvent'),
120 ])
117 ])
121 def test_hooks_propagates_not_on_shadow(func, extension, event, hook_extras):
118 def test_hooks_propagates_not_on_shadow(func, extension, event, hook_extras):
122 """
119 """
123 If hooks are called by a request to a shadow repo we only want to run our
120 If hooks are called by a request to a shadow repo we only want to run our
124 internal hooks code but not external ones like rhodecode extensions or
121 internal hooks code but not external ones like rhodecode extensions or
125 trigger an event.
122 trigger an event.
126 """
123 """
127 extension_mock = mock.Mock()
124 extension_mock = mock.Mock()
128 events_mock = mock.Mock()
125 events_mock = mock.Mock()
129 patches = {
126 patches = {
130 'Repository': mock.Mock(),
127 'Repository': mock.Mock(),
131 'events': events_mock,
128 'events': events_mock,
132 extension: extension_mock,
129 extension: extension_mock,
133 }
130 }
134
131
135 # Set shadow repo flag.
132 # Set shadow repo flag.
136 hook_extras.is_shadow_repo = True
133 hook_extras.is_shadow_repo = True
137
134
138 # Execute hook function.
135 # Execute hook function.
139 with mock.patch.multiple(hooks_base, **patches):
136 with mock.patch.multiple(hooks_base, **patches):
140 func(hook_extras)
137 func(hook_extras)
141
138
142 # Assert that extensions are *not* called and event was *not* fired.
139 # Assert that extensions are *not* called and event was *not* fired.
143 assert not extension_mock.called
140 assert not extension_mock.called
144 assert not events_mock.trigger.called
141 assert not events_mock.trigger.called
General Comments 0
You need to be logged in to leave comments. Login now