##// END OF EJS Templates
tests: made few tests for comment edits more stable.
marcink -
r4410:c028d607 default
parent child Browse files
Show More
@@ -1,1426 +1,1435 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 import mock
21 21 import pytest
22 22
23 23 import rhodecode
24 24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
25 25 from rhodecode.lib.vcs.nodes import FileNode
26 26 from rhodecode.lib import helpers as h
27 27 from rhodecode.model.changeset_status import ChangesetStatusModel
28 28 from rhodecode.model.db import (
29 29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment, Repository)
30 30 from rhodecode.model.meta import Session
31 31 from rhodecode.model.pull_request import PullRequestModel
32 32 from rhodecode.model.user import UserModel
33 33 from rhodecode.model.comment import CommentsModel
34 34 from rhodecode.tests import (
35 35 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
36 36
37 37
38 38 def route_path(name, params=None, **kwargs):
39 39 import urllib
40 40
41 41 base_url = {
42 42 'repo_changelog': '/{repo_name}/changelog',
43 43 'repo_changelog_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
44 44 'repo_commits': '/{repo_name}/commits',
45 45 'repo_commits_file': '/{repo_name}/commits/{commit_id}/{f_path}',
46 46 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
47 47 'pullrequest_show_all': '/{repo_name}/pull-request',
48 48 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
49 49 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
50 50 'pullrequest_repo_targets': '/{repo_name}/pull-request/repo-destinations',
51 51 'pullrequest_new': '/{repo_name}/pull-request/new',
52 52 'pullrequest_create': '/{repo_name}/pull-request/create',
53 53 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
54 54 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
55 55 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
56 56 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
57 57 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
58 58 'pullrequest_comment_edit': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/edit',
59 59 }[name].format(**kwargs)
60 60
61 61 if params:
62 62 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
63 63 return base_url
64 64
65 65
66 66 @pytest.mark.usefixtures('app', 'autologin_user')
67 67 @pytest.mark.backends("git", "hg")
68 68 class TestPullrequestsView(object):
69 69
70 70 def test_index(self, backend):
71 71 self.app.get(route_path(
72 72 'pullrequest_new',
73 73 repo_name=backend.repo_name))
74 74
75 75 def test_option_menu_create_pull_request_exists(self, backend):
76 76 repo_name = backend.repo_name
77 77 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
78 78
79 79 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
80 80 'pullrequest_new', repo_name=repo_name)
81 81 response.mustcontain(create_pr_link)
82 82
83 83 def test_create_pr_form_with_raw_commit_id(self, backend):
84 84 repo = backend.repo
85 85
86 86 self.app.get(
87 87 route_path('pullrequest_new', repo_name=repo.repo_name,
88 88 commit=repo.get_commit().raw_id),
89 89 status=200)
90 90
91 91 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
92 92 @pytest.mark.parametrize('range_diff', ["0", "1"])
93 93 def test_show(self, pr_util, pr_merge_enabled, range_diff):
94 94 pull_request = pr_util.create_pull_request(
95 95 mergeable=pr_merge_enabled, enable_notifications=False)
96 96
97 97 response = self.app.get(route_path(
98 98 'pullrequest_show',
99 99 repo_name=pull_request.target_repo.scm_instance().name,
100 100 pull_request_id=pull_request.pull_request_id,
101 101 params={'range-diff': range_diff}))
102 102
103 103 for commit_id in pull_request.revisions:
104 104 response.mustcontain(commit_id)
105 105
106 106 response.mustcontain(pull_request.target_ref_parts.type)
107 107 response.mustcontain(pull_request.target_ref_parts.name)
108 108
109 109 response.mustcontain('class="pull-request-merge"')
110 110
111 111 if pr_merge_enabled:
112 112 response.mustcontain('Pull request reviewer approval is pending')
113 113 else:
114 114 response.mustcontain('Server-side pull request merging is disabled.')
115 115
116 116 if range_diff == "1":
117 117 response.mustcontain('Turn off: Show the diff as commit range')
118 118
119 119 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
120 120 # Logout
121 121 response = self.app.post(
122 122 h.route_path('logout'),
123 123 params={'csrf_token': csrf_token})
124 124 # Login as regular user
125 125 response = self.app.post(h.route_path('login'),
126 126 {'username': TEST_USER_REGULAR_LOGIN,
127 127 'password': 'test12'})
128 128
129 129 pull_request = pr_util.create_pull_request(
130 130 author=TEST_USER_REGULAR_LOGIN)
131 131
132 132 response = self.app.get(route_path(
133 133 'pullrequest_show',
134 134 repo_name=pull_request.target_repo.scm_instance().name,
135 135 pull_request_id=pull_request.pull_request_id))
136 136
137 137 response.mustcontain('Server-side pull request merging is disabled.')
138 138
139 139 assert_response = response.assert_response()
140 140 # for regular user without a merge permissions, we don't see it
141 141 assert_response.no_element_exists('#close-pull-request-action')
142 142
143 143 user_util.grant_user_permission_to_repo(
144 144 pull_request.target_repo,
145 145 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
146 146 'repository.write')
147 147 response = self.app.get(route_path(
148 148 'pullrequest_show',
149 149 repo_name=pull_request.target_repo.scm_instance().name,
150 150 pull_request_id=pull_request.pull_request_id))
151 151
152 152 response.mustcontain('Server-side pull request merging is disabled.')
153 153
154 154 assert_response = response.assert_response()
155 155 # now regular user has a merge permissions, we have CLOSE button
156 156 assert_response.one_element_exists('#close-pull-request-action')
157 157
158 158 def test_show_invalid_commit_id(self, pr_util):
159 159 # Simulating invalid revisions which will cause a lookup error
160 160 pull_request = pr_util.create_pull_request()
161 161 pull_request.revisions = ['invalid']
162 162 Session().add(pull_request)
163 163 Session().commit()
164 164
165 165 response = self.app.get(route_path(
166 166 'pullrequest_show',
167 167 repo_name=pull_request.target_repo.scm_instance().name,
168 168 pull_request_id=pull_request.pull_request_id))
169 169
170 170 for commit_id in pull_request.revisions:
171 171 response.mustcontain(commit_id)
172 172
173 173 def test_show_invalid_source_reference(self, pr_util):
174 174 pull_request = pr_util.create_pull_request()
175 175 pull_request.source_ref = 'branch:b:invalid'
176 176 Session().add(pull_request)
177 177 Session().commit()
178 178
179 179 self.app.get(route_path(
180 180 'pullrequest_show',
181 181 repo_name=pull_request.target_repo.scm_instance().name,
182 182 pull_request_id=pull_request.pull_request_id))
183 183
184 184 def test_edit_title_description(self, pr_util, csrf_token):
185 185 pull_request = pr_util.create_pull_request()
186 186 pull_request_id = pull_request.pull_request_id
187 187
188 188 response = self.app.post(
189 189 route_path('pullrequest_update',
190 190 repo_name=pull_request.target_repo.repo_name,
191 191 pull_request_id=pull_request_id),
192 192 params={
193 193 'edit_pull_request': 'true',
194 194 'title': 'New title',
195 195 'description': 'New description',
196 196 'csrf_token': csrf_token})
197 197
198 198 assert_session_flash(
199 199 response, u'Pull request title & description updated.',
200 200 category='success')
201 201
202 202 pull_request = PullRequest.get(pull_request_id)
203 203 assert pull_request.title == 'New title'
204 204 assert pull_request.description == 'New description'
205 205
206 206 def test_edit_title_description_closed(self, pr_util, csrf_token):
207 207 pull_request = pr_util.create_pull_request()
208 208 pull_request_id = pull_request.pull_request_id
209 209 repo_name = pull_request.target_repo.repo_name
210 210 pr_util.close()
211 211
212 212 response = self.app.post(
213 213 route_path('pullrequest_update',
214 214 repo_name=repo_name, pull_request_id=pull_request_id),
215 215 params={
216 216 'edit_pull_request': 'true',
217 217 'title': 'New title',
218 218 'description': 'New description',
219 219 'csrf_token': csrf_token}, status=200)
220 220 assert_session_flash(
221 221 response, u'Cannot update closed pull requests.',
222 222 category='error')
223 223
224 224 def test_update_invalid_source_reference(self, pr_util, csrf_token):
225 225 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
226 226
227 227 pull_request = pr_util.create_pull_request()
228 228 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
229 229 Session().add(pull_request)
230 230 Session().commit()
231 231
232 232 pull_request_id = pull_request.pull_request_id
233 233
234 234 response = self.app.post(
235 235 route_path('pullrequest_update',
236 236 repo_name=pull_request.target_repo.repo_name,
237 237 pull_request_id=pull_request_id),
238 238 params={'update_commits': 'true', 'csrf_token': csrf_token})
239 239
240 240 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
241 241 UpdateFailureReason.MISSING_SOURCE_REF])
242 242 assert_session_flash(response, expected_msg, category='error')
243 243
244 244 def test_missing_target_reference(self, pr_util, csrf_token):
245 245 from rhodecode.lib.vcs.backends.base import MergeFailureReason
246 246 pull_request = pr_util.create_pull_request(
247 247 approved=True, mergeable=True)
248 248 unicode_reference = u'branch:invalid-branch:invalid-commit-id'
249 249 pull_request.target_ref = unicode_reference
250 250 Session().add(pull_request)
251 251 Session().commit()
252 252
253 253 pull_request_id = pull_request.pull_request_id
254 254 pull_request_url = route_path(
255 255 'pullrequest_show',
256 256 repo_name=pull_request.target_repo.repo_name,
257 257 pull_request_id=pull_request_id)
258 258
259 259 response = self.app.get(pull_request_url)
260 260 target_ref_id = 'invalid-branch'
261 261 merge_resp = MergeResponse(
262 262 True, True, '', MergeFailureReason.MISSING_TARGET_REF,
263 263 metadata={'target_ref': PullRequest.unicode_to_reference(unicode_reference)})
264 264 response.assert_response().element_contains(
265 265 'div[data-role="merge-message"]', merge_resp.merge_status_message)
266 266
267 267 def test_comment_and_close_pull_request_custom_message_approved(
268 268 self, pr_util, csrf_token, xhr_header):
269 269
270 270 pull_request = pr_util.create_pull_request(approved=True)
271 271 pull_request_id = pull_request.pull_request_id
272 272 author = pull_request.user_id
273 273 repo = pull_request.target_repo.repo_id
274 274
275 275 self.app.post(
276 276 route_path('pullrequest_comment_create',
277 277 repo_name=pull_request.target_repo.scm_instance().name,
278 278 pull_request_id=pull_request_id),
279 279 params={
280 280 'close_pull_request': '1',
281 281 'text': 'Closing a PR',
282 282 'csrf_token': csrf_token},
283 283 extra_environ=xhr_header,)
284 284
285 285 journal = UserLog.query()\
286 286 .filter(UserLog.user_id == author)\
287 287 .filter(UserLog.repository_id == repo) \
288 288 .order_by(UserLog.user_log_id.asc()) \
289 289 .all()
290 290 assert journal[-1].action == 'repo.pull_request.close'
291 291
292 292 pull_request = PullRequest.get(pull_request_id)
293 293 assert pull_request.is_closed()
294 294
295 295 status = ChangesetStatusModel().get_status(
296 296 pull_request.source_repo, pull_request=pull_request)
297 297 assert status == ChangesetStatus.STATUS_APPROVED
298 298 comments = ChangesetComment().query() \
299 299 .filter(ChangesetComment.pull_request == pull_request) \
300 300 .order_by(ChangesetComment.comment_id.asc())\
301 301 .all()
302 302 assert comments[-1].text == 'Closing a PR'
303 303
304 304 def test_comment_force_close_pull_request_rejected(
305 305 self, pr_util, csrf_token, xhr_header):
306 306 pull_request = pr_util.create_pull_request()
307 307 pull_request_id = pull_request.pull_request_id
308 308 PullRequestModel().update_reviewers(
309 309 pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
310 310 pull_request.author)
311 311 author = pull_request.user_id
312 312 repo = pull_request.target_repo.repo_id
313 313
314 314 self.app.post(
315 315 route_path('pullrequest_comment_create',
316 316 repo_name=pull_request.target_repo.scm_instance().name,
317 317 pull_request_id=pull_request_id),
318 318 params={
319 319 'close_pull_request': '1',
320 320 'csrf_token': csrf_token},
321 321 extra_environ=xhr_header)
322 322
323 323 pull_request = PullRequest.get(pull_request_id)
324 324
325 325 journal = UserLog.query()\
326 326 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
327 327 .order_by(UserLog.user_log_id.asc()) \
328 328 .all()
329 329 assert journal[-1].action == 'repo.pull_request.close'
330 330
331 331 # check only the latest status, not the review status
332 332 status = ChangesetStatusModel().get_status(
333 333 pull_request.source_repo, pull_request=pull_request)
334 334 assert status == ChangesetStatus.STATUS_REJECTED
335 335
336 336 def test_comment_and_close_pull_request(
337 337 self, pr_util, csrf_token, xhr_header):
338 338 pull_request = pr_util.create_pull_request()
339 339 pull_request_id = pull_request.pull_request_id
340 340
341 341 response = self.app.post(
342 342 route_path('pullrequest_comment_create',
343 343 repo_name=pull_request.target_repo.scm_instance().name,
344 344 pull_request_id=pull_request.pull_request_id),
345 345 params={
346 346 'close_pull_request': 'true',
347 347 'csrf_token': csrf_token},
348 348 extra_environ=xhr_header)
349 349
350 350 assert response.json
351 351
352 352 pull_request = PullRequest.get(pull_request_id)
353 353 assert pull_request.is_closed()
354 354
355 355 # check only the latest status, not the review status
356 356 status = ChangesetStatusModel().get_status(
357 357 pull_request.source_repo, pull_request=pull_request)
358 358 assert status == ChangesetStatus.STATUS_REJECTED
359 359
360 360 def test_comment_and_close_pull_request_try_edit_comment(
361 361 self, pr_util, csrf_token, xhr_header
362 362 ):
363 363 pull_request = pr_util.create_pull_request()
364 364 pull_request_id = pull_request.pull_request_id
365 target_scm = pull_request.target_repo.scm_instance()
366 target_scm_name = target_scm.name
365 367
366 368 response = self.app.post(
367 369 route_path(
368 370 'pullrequest_comment_create',
369 repo_name=pull_request.target_repo.scm_instance().name,
370 pull_request_id=pull_request.pull_request_id,
371 repo_name=target_scm_name,
372 pull_request_id=pull_request_id,
371 373 ),
372 374 params={
373 375 'close_pull_request': 'true',
374 376 'csrf_token': csrf_token,
375 377 },
376 378 extra_environ=xhr_header)
377 379
378 380 assert response.json
379 381
380 382 pull_request = PullRequest.get(pull_request_id)
383 target_scm = pull_request.target_repo.scm_instance()
384 target_scm_name = target_scm.name
381 385 assert pull_request.is_closed()
382 386
383 387 # check only the latest status, not the review status
384 388 status = ChangesetStatusModel().get_status(
385 389 pull_request.source_repo, pull_request=pull_request)
386 390 assert status == ChangesetStatus.STATUS_REJECTED
387 391
388 392 comment_id = response.json.get('comment_id', None)
389 393 test_text = 'test'
390 394 response = self.app.post(
391 395 route_path(
392 396 'pullrequest_comment_edit',
393 repo_name=pull_request.target_repo.scm_instance().name,
394 pull_request_id=pull_request.pull_request_id,
397 repo_name=target_scm_name,
398 pull_request_id=pull_request_id,
395 399 comment_id=comment_id,
396 400 ),
397 401 extra_environ=xhr_header,
398 402 params={
399 403 'csrf_token': csrf_token,
400 404 'text': test_text,
401 405 },
402 406 status=403,
403 407 )
404 408 assert response.status_int == 403
405 409
406 def test_comment_and_comment_edit(
407 self, pr_util, csrf_token, xhr_header
408 ):
410 def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header):
409 411 pull_request = pr_util.create_pull_request()
412 target_scm = pull_request.target_repo.scm_instance()
413 target_scm_name = target_scm.name
414
410 415 response = self.app.post(
411 416 route_path(
412 417 'pullrequest_comment_create',
413 repo_name=pull_request.target_repo.scm_instance().name,
418 repo_name=target_scm_name,
414 419 pull_request_id=pull_request.pull_request_id),
415 420 params={
416 421 'csrf_token': csrf_token,
417 422 'text': 'init',
418 423 },
419 424 extra_environ=xhr_header,
420 425 )
421 426 assert response.json
422 427
423 428 comment_id = response.json.get('comment_id', None)
424 429 assert comment_id
425 430 test_text = 'test'
426 431 self.app.post(
427 432 route_path(
428 433 'pullrequest_comment_edit',
429 repo_name=pull_request.target_repo.scm_instance().name,
434 repo_name=target_scm_name,
430 435 pull_request_id=pull_request.pull_request_id,
431 436 comment_id=comment_id,
432 437 ),
433 438 extra_environ=xhr_header,
434 439 params={
435 440 'csrf_token': csrf_token,
436 441 'text': test_text,
437 442 'version': '0',
438 443 },
439 444
440 445 )
441 446 text_form_db = ChangesetComment.query().filter(
442 447 ChangesetComment.comment_id == comment_id).first().text
443 448 assert test_text == text_form_db
444 449
445 def test_comment_and_comment_edit(
446 self, pr_util, csrf_token, xhr_header
447 ):
450 def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header):
448 451 pull_request = pr_util.create_pull_request()
452 target_scm = pull_request.target_repo.scm_instance()
453 target_scm_name = target_scm.name
454
449 455 response = self.app.post(
450 456 route_path(
451 457 'pullrequest_comment_create',
452 repo_name=pull_request.target_repo.scm_instance().name,
458 repo_name=target_scm_name,
453 459 pull_request_id=pull_request.pull_request_id),
454 460 params={
455 461 'csrf_token': csrf_token,
456 462 'text': 'init',
457 463 },
458 464 extra_environ=xhr_header,
459 465 )
460 466 assert response.json
461 467
462 468 comment_id = response.json.get('comment_id', None)
463 469 assert comment_id
464 470 test_text = 'init'
465 471 response = self.app.post(
466 472 route_path(
467 473 'pullrequest_comment_edit',
468 repo_name=pull_request.target_repo.scm_instance().name,
474 repo_name=target_scm_name,
469 475 pull_request_id=pull_request.pull_request_id,
470 476 comment_id=comment_id,
471 477 ),
472 478 extra_environ=xhr_header,
473 479 params={
474 480 'csrf_token': csrf_token,
475 481 'text': test_text,
476 482 'version': '0',
477 483 },
478 484 status=404,
479 485
480 486 )
481 487 assert response.status_int == 404
482 488
483 489 def test_comment_and_try_edit_already_edited(self, pr_util, csrf_token, xhr_header):
484 490 pull_request = pr_util.create_pull_request()
491 target_scm = pull_request.target_repo.scm_instance()
492 target_scm_name = target_scm.name
493
485 494 response = self.app.post(
486 495 route_path(
487 496 'pullrequest_comment_create',
488 repo_name=pull_request.target_repo.scm_instance().name,
497 repo_name=target_scm_name,
489 498 pull_request_id=pull_request.pull_request_id),
490 499 params={
491 500 'csrf_token': csrf_token,
492 501 'text': 'init',
493 502 },
494 503 extra_environ=xhr_header,
495 504 )
496 505 assert response.json
497 506 comment_id = response.json.get('comment_id', None)
498 507 assert comment_id
499 508
500 509 test_text = 'test'
501 510 self.app.post(
502 511 route_path(
503 512 'pullrequest_comment_edit',
504 repo_name=pull_request.target_repo.scm_instance().name,
513 repo_name=target_scm_name,
505 514 pull_request_id=pull_request.pull_request_id,
506 515 comment_id=comment_id,
507 516 ),
508 517 extra_environ=xhr_header,
509 518 params={
510 519 'csrf_token': csrf_token,
511 520 'text': test_text,
512 521 'version': '0',
513 522 },
514 523
515 524 )
516 525 test_text_v2 = 'test_v2'
517 526 response = self.app.post(
518 527 route_path(
519 528 'pullrequest_comment_edit',
520 repo_name=pull_request.target_repo.scm_instance().name,
529 repo_name=target_scm_name,
521 530 pull_request_id=pull_request.pull_request_id,
522 531 comment_id=comment_id,
523 532 ),
524 533 extra_environ=xhr_header,
525 534 params={
526 535 'csrf_token': csrf_token,
527 536 'text': test_text_v2,
528 537 'version': '0',
529 538 },
530 539 status=409,
531 540 )
532 541 assert response.status_int == 409
533 542
534 543 text_form_db = ChangesetComment.query().filter(
535 544 ChangesetComment.comment_id == comment_id).first().text
536 545
537 546 assert test_text == text_form_db
538 547 assert test_text_v2 != text_form_db
539 548
540 549 def test_comment_and_comment_edit_permissions_forbidden(
541 550 self, autologin_regular_user, user_regular, user_admin, pr_util,
542 551 csrf_token, xhr_header):
543 552 pull_request = pr_util.create_pull_request(
544 553 author=user_admin.username, enable_notifications=False)
545 554 comment = CommentsModel().create(
546 555 text='test',
547 556 repo=pull_request.target_repo.scm_instance().name,
548 557 user=user_admin,
549 558 pull_request=pull_request,
550 559 )
551 560 response = self.app.post(
552 561 route_path(
553 562 'pullrequest_comment_edit',
554 563 repo_name=pull_request.target_repo.scm_instance().name,
555 564 pull_request_id=pull_request.pull_request_id,
556 565 comment_id=comment.comment_id,
557 566 ),
558 567 extra_environ=xhr_header,
559 568 params={
560 569 'csrf_token': csrf_token,
561 570 'text': 'test_text',
562 571 },
563 572 status=403,
564 573 )
565 574 assert response.status_int == 403
566 575
567 576 def test_create_pull_request(self, backend, csrf_token):
568 577 commits = [
569 578 {'message': 'ancestor'},
570 579 {'message': 'change'},
571 580 {'message': 'change2'},
572 581 ]
573 582 commit_ids = backend.create_master_repo(commits)
574 583 target = backend.create_repo(heads=['ancestor'])
575 584 source = backend.create_repo(heads=['change2'])
576 585
577 586 response = self.app.post(
578 587 route_path('pullrequest_create', repo_name=source.repo_name),
579 588 [
580 589 ('source_repo', source.repo_name),
581 590 ('source_ref', 'branch:default:' + commit_ids['change2']),
582 591 ('target_repo', target.repo_name),
583 592 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
584 593 ('common_ancestor', commit_ids['ancestor']),
585 594 ('pullrequest_title', 'Title'),
586 595 ('pullrequest_desc', 'Description'),
587 596 ('description_renderer', 'markdown'),
588 597 ('__start__', 'review_members:sequence'),
589 598 ('__start__', 'reviewer:mapping'),
590 599 ('user_id', '1'),
591 600 ('__start__', 'reasons:sequence'),
592 601 ('reason', 'Some reason'),
593 602 ('__end__', 'reasons:sequence'),
594 603 ('__start__', 'rules:sequence'),
595 604 ('__end__', 'rules:sequence'),
596 605 ('mandatory', 'False'),
597 606 ('__end__', 'reviewer:mapping'),
598 607 ('__end__', 'review_members:sequence'),
599 608 ('__start__', 'revisions:sequence'),
600 609 ('revisions', commit_ids['change']),
601 610 ('revisions', commit_ids['change2']),
602 611 ('__end__', 'revisions:sequence'),
603 612 ('user', ''),
604 613 ('csrf_token', csrf_token),
605 614 ],
606 615 status=302)
607 616
608 617 location = response.headers['Location']
609 618 pull_request_id = location.rsplit('/', 1)[1]
610 619 assert pull_request_id != 'new'
611 620 pull_request = PullRequest.get(int(pull_request_id))
612 621
613 622 # check that we have now both revisions
614 623 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
615 624 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
616 625 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
617 626 assert pull_request.target_ref == expected_target_ref
618 627
619 628 def test_reviewer_notifications(self, backend, csrf_token):
620 629 # We have to use the app.post for this test so it will create the
621 630 # notifications properly with the new PR
622 631 commits = [
623 632 {'message': 'ancestor',
624 633 'added': [FileNode('file_A', content='content_of_ancestor')]},
625 634 {'message': 'change',
626 635 'added': [FileNode('file_a', content='content_of_change')]},
627 636 {'message': 'change-child'},
628 637 {'message': 'ancestor-child', 'parents': ['ancestor'],
629 638 'added': [
630 639 FileNode('file_B', content='content_of_ancestor_child')]},
631 640 {'message': 'ancestor-child-2'},
632 641 ]
633 642 commit_ids = backend.create_master_repo(commits)
634 643 target = backend.create_repo(heads=['ancestor-child'])
635 644 source = backend.create_repo(heads=['change'])
636 645
637 646 response = self.app.post(
638 647 route_path('pullrequest_create', repo_name=source.repo_name),
639 648 [
640 649 ('source_repo', source.repo_name),
641 650 ('source_ref', 'branch:default:' + commit_ids['change']),
642 651 ('target_repo', target.repo_name),
643 652 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
644 653 ('common_ancestor', commit_ids['ancestor']),
645 654 ('pullrequest_title', 'Title'),
646 655 ('pullrequest_desc', 'Description'),
647 656 ('description_renderer', 'markdown'),
648 657 ('__start__', 'review_members:sequence'),
649 658 ('__start__', 'reviewer:mapping'),
650 659 ('user_id', '2'),
651 660 ('__start__', 'reasons:sequence'),
652 661 ('reason', 'Some reason'),
653 662 ('__end__', 'reasons:sequence'),
654 663 ('__start__', 'rules:sequence'),
655 664 ('__end__', 'rules:sequence'),
656 665 ('mandatory', 'False'),
657 666 ('__end__', 'reviewer:mapping'),
658 667 ('__end__', 'review_members:sequence'),
659 668 ('__start__', 'revisions:sequence'),
660 669 ('revisions', commit_ids['change']),
661 670 ('__end__', 'revisions:sequence'),
662 671 ('user', ''),
663 672 ('csrf_token', csrf_token),
664 673 ],
665 674 status=302)
666 675
667 676 location = response.headers['Location']
668 677
669 678 pull_request_id = location.rsplit('/', 1)[1]
670 679 assert pull_request_id != 'new'
671 680 pull_request = PullRequest.get(int(pull_request_id))
672 681
673 682 # Check that a notification was made
674 683 notifications = Notification.query()\
675 684 .filter(Notification.created_by == pull_request.author.user_id,
676 685 Notification.type_ == Notification.TYPE_PULL_REQUEST,
677 686 Notification.subject.contains(
678 687 "requested a pull request review. !%s" % pull_request_id))
679 688 assert len(notifications.all()) == 1
680 689
681 690 # Change reviewers and check that a notification was made
682 691 PullRequestModel().update_reviewers(
683 692 pull_request.pull_request_id, [(1, [], False, [])],
684 693 pull_request.author)
685 694 assert len(notifications.all()) == 2
686 695
687 696 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
688 697 csrf_token):
689 698 commits = [
690 699 {'message': 'ancestor',
691 700 'added': [FileNode('file_A', content='content_of_ancestor')]},
692 701 {'message': 'change',
693 702 'added': [FileNode('file_a', content='content_of_change')]},
694 703 {'message': 'change-child'},
695 704 {'message': 'ancestor-child', 'parents': ['ancestor'],
696 705 'added': [
697 706 FileNode('file_B', content='content_of_ancestor_child')]},
698 707 {'message': 'ancestor-child-2'},
699 708 ]
700 709 commit_ids = backend.create_master_repo(commits)
701 710 target = backend.create_repo(heads=['ancestor-child'])
702 711 source = backend.create_repo(heads=['change'])
703 712
704 713 response = self.app.post(
705 714 route_path('pullrequest_create', repo_name=source.repo_name),
706 715 [
707 716 ('source_repo', source.repo_name),
708 717 ('source_ref', 'branch:default:' + commit_ids['change']),
709 718 ('target_repo', target.repo_name),
710 719 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
711 720 ('common_ancestor', commit_ids['ancestor']),
712 721 ('pullrequest_title', 'Title'),
713 722 ('pullrequest_desc', 'Description'),
714 723 ('description_renderer', 'markdown'),
715 724 ('__start__', 'review_members:sequence'),
716 725 ('__start__', 'reviewer:mapping'),
717 726 ('user_id', '1'),
718 727 ('__start__', 'reasons:sequence'),
719 728 ('reason', 'Some reason'),
720 729 ('__end__', 'reasons:sequence'),
721 730 ('__start__', 'rules:sequence'),
722 731 ('__end__', 'rules:sequence'),
723 732 ('mandatory', 'False'),
724 733 ('__end__', 'reviewer:mapping'),
725 734 ('__end__', 'review_members:sequence'),
726 735 ('__start__', 'revisions:sequence'),
727 736 ('revisions', commit_ids['change']),
728 737 ('__end__', 'revisions:sequence'),
729 738 ('user', ''),
730 739 ('csrf_token', csrf_token),
731 740 ],
732 741 status=302)
733 742
734 743 location = response.headers['Location']
735 744
736 745 pull_request_id = location.rsplit('/', 1)[1]
737 746 assert pull_request_id != 'new'
738 747 pull_request = PullRequest.get(int(pull_request_id))
739 748
740 749 # target_ref has to point to the ancestor's commit_id in order to
741 750 # show the correct diff
742 751 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
743 752 assert pull_request.target_ref == expected_target_ref
744 753
745 754 # Check generated diff contents
746 755 response = response.follow()
747 756 response.mustcontain(no=['content_of_ancestor'])
748 757 response.mustcontain(no=['content_of_ancestor-child'])
749 758 response.mustcontain('content_of_change')
750 759
751 760 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
752 761 # Clear any previous calls to rcextensions
753 762 rhodecode.EXTENSIONS.calls.clear()
754 763
755 764 pull_request = pr_util.create_pull_request(
756 765 approved=True, mergeable=True)
757 766 pull_request_id = pull_request.pull_request_id
758 767 repo_name = pull_request.target_repo.scm_instance().name,
759 768
760 769 url = route_path('pullrequest_merge',
761 770 repo_name=str(repo_name[0]),
762 771 pull_request_id=pull_request_id)
763 772 response = self.app.post(url, params={'csrf_token': csrf_token}).follow()
764 773
765 774 pull_request = PullRequest.get(pull_request_id)
766 775
767 776 assert response.status_int == 200
768 777 assert pull_request.is_closed()
769 778 assert_pull_request_status(
770 779 pull_request, ChangesetStatus.STATUS_APPROVED)
771 780
772 781 # Check the relevant log entries were added
773 782 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(3)
774 783 actions = [log.action for log in user_logs]
775 784 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
776 785 expected_actions = [
777 786 u'repo.pull_request.close',
778 787 u'repo.pull_request.merge',
779 788 u'repo.pull_request.comment.create'
780 789 ]
781 790 assert actions == expected_actions
782 791
783 792 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(4)
784 793 actions = [log for log in user_logs]
785 794 assert actions[-1].action == 'user.push'
786 795 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
787 796
788 797 # Check post_push rcextension was really executed
789 798 push_calls = rhodecode.EXTENSIONS.calls['_push_hook']
790 799 assert len(push_calls) == 1
791 800 unused_last_call_args, last_call_kwargs = push_calls[0]
792 801 assert last_call_kwargs['action'] == 'push'
793 802 assert last_call_kwargs['commit_ids'] == pr_commit_ids
794 803
795 804 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
796 805 pull_request = pr_util.create_pull_request(mergeable=False)
797 806 pull_request_id = pull_request.pull_request_id
798 807 pull_request = PullRequest.get(pull_request_id)
799 808
800 809 response = self.app.post(
801 810 route_path('pullrequest_merge',
802 811 repo_name=pull_request.target_repo.scm_instance().name,
803 812 pull_request_id=pull_request.pull_request_id),
804 813 params={'csrf_token': csrf_token}).follow()
805 814
806 815 assert response.status_int == 200
807 816 response.mustcontain(
808 817 'Merge is not currently possible because of below failed checks.')
809 818 response.mustcontain('Server-side pull request merging is disabled.')
810 819
811 820 @pytest.mark.skip_backends('svn')
812 821 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
813 822 pull_request = pr_util.create_pull_request(mergeable=True)
814 823 pull_request_id = pull_request.pull_request_id
815 824 repo_name = pull_request.target_repo.scm_instance().name
816 825
817 826 response = self.app.post(
818 827 route_path('pullrequest_merge',
819 828 repo_name=repo_name, pull_request_id=pull_request_id),
820 829 params={'csrf_token': csrf_token}).follow()
821 830
822 831 assert response.status_int == 200
823 832
824 833 response.mustcontain(
825 834 'Merge is not currently possible because of below failed checks.')
826 835 response.mustcontain('Pull request reviewer approval is pending.')
827 836
828 837 def test_merge_pull_request_renders_failure_reason(
829 838 self, user_regular, csrf_token, pr_util):
830 839 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
831 840 pull_request_id = pull_request.pull_request_id
832 841 repo_name = pull_request.target_repo.scm_instance().name
833 842
834 843 merge_resp = MergeResponse(True, False, 'STUB_COMMIT_ID',
835 844 MergeFailureReason.PUSH_FAILED,
836 845 metadata={'target': 'shadow repo',
837 846 'merge_commit': 'xxx'})
838 847 model_patcher = mock.patch.multiple(
839 848 PullRequestModel,
840 849 merge_repo=mock.Mock(return_value=merge_resp),
841 850 merge_status=mock.Mock(return_value=(None, True, 'WRONG_MESSAGE')))
842 851
843 852 with model_patcher:
844 853 response = self.app.post(
845 854 route_path('pullrequest_merge',
846 855 repo_name=repo_name,
847 856 pull_request_id=pull_request_id),
848 857 params={'csrf_token': csrf_token}, status=302)
849 858
850 859 merge_resp = MergeResponse(True, True, '', MergeFailureReason.PUSH_FAILED,
851 860 metadata={'target': 'shadow repo',
852 861 'merge_commit': 'xxx'})
853 862 assert_session_flash(response, merge_resp.merge_status_message)
854 863
855 864 def test_update_source_revision(self, backend, csrf_token):
856 865 commits = [
857 866 {'message': 'ancestor'},
858 867 {'message': 'change'},
859 868 {'message': 'change-2'},
860 869 ]
861 870 commit_ids = backend.create_master_repo(commits)
862 871 target = backend.create_repo(heads=['ancestor'])
863 872 source = backend.create_repo(heads=['change'])
864 873
865 874 # create pr from a in source to A in target
866 875 pull_request = PullRequest()
867 876
868 877 pull_request.source_repo = source
869 878 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
870 879 branch=backend.default_branch_name, commit_id=commit_ids['change'])
871 880
872 881 pull_request.target_repo = target
873 882 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
874 883 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
875 884
876 885 pull_request.revisions = [commit_ids['change']]
877 886 pull_request.title = u"Test"
878 887 pull_request.description = u"Description"
879 888 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
880 889 pull_request.pull_request_state = PullRequest.STATE_CREATED
881 890 Session().add(pull_request)
882 891 Session().commit()
883 892 pull_request_id = pull_request.pull_request_id
884 893
885 894 # source has ancestor - change - change-2
886 895 backend.pull_heads(source, heads=['change-2'])
887 896
888 897 # update PR
889 898 self.app.post(
890 899 route_path('pullrequest_update',
891 900 repo_name=target.repo_name, pull_request_id=pull_request_id),
892 901 params={'update_commits': 'true', 'csrf_token': csrf_token})
893 902
894 903 response = self.app.get(
895 904 route_path('pullrequest_show',
896 905 repo_name=target.repo_name,
897 906 pull_request_id=pull_request.pull_request_id))
898 907
899 908 assert response.status_int == 200
900 909 response.mustcontain('Pull request updated to')
901 910 response.mustcontain('with 1 added, 0 removed commits.')
902 911
903 912 # check that we have now both revisions
904 913 pull_request = PullRequest.get(pull_request_id)
905 914 assert pull_request.revisions == [commit_ids['change-2'], commit_ids['change']]
906 915
907 916 def test_update_target_revision(self, backend, csrf_token):
908 917 commits = [
909 918 {'message': 'ancestor'},
910 919 {'message': 'change'},
911 920 {'message': 'ancestor-new', 'parents': ['ancestor']},
912 921 {'message': 'change-rebased'},
913 922 ]
914 923 commit_ids = backend.create_master_repo(commits)
915 924 target = backend.create_repo(heads=['ancestor'])
916 925 source = backend.create_repo(heads=['change'])
917 926
918 927 # create pr from a in source to A in target
919 928 pull_request = PullRequest()
920 929
921 930 pull_request.source_repo = source
922 931 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
923 932 branch=backend.default_branch_name, commit_id=commit_ids['change'])
924 933
925 934 pull_request.target_repo = target
926 935 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
927 936 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
928 937
929 938 pull_request.revisions = [commit_ids['change']]
930 939 pull_request.title = u"Test"
931 940 pull_request.description = u"Description"
932 941 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
933 942 pull_request.pull_request_state = PullRequest.STATE_CREATED
934 943
935 944 Session().add(pull_request)
936 945 Session().commit()
937 946 pull_request_id = pull_request.pull_request_id
938 947
939 948 # target has ancestor - ancestor-new
940 949 # source has ancestor - ancestor-new - change-rebased
941 950 backend.pull_heads(target, heads=['ancestor-new'])
942 951 backend.pull_heads(source, heads=['change-rebased'])
943 952
944 953 # update PR
945 954 url = route_path('pullrequest_update',
946 955 repo_name=target.repo_name,
947 956 pull_request_id=pull_request_id)
948 957 self.app.post(url,
949 958 params={'update_commits': 'true', 'csrf_token': csrf_token},
950 959 status=200)
951 960
952 961 # check that we have now both revisions
953 962 pull_request = PullRequest.get(pull_request_id)
954 963 assert pull_request.revisions == [commit_ids['change-rebased']]
955 964 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
956 965 branch=backend.default_branch_name, commit_id=commit_ids['ancestor-new'])
957 966
958 967 response = self.app.get(
959 968 route_path('pullrequest_show',
960 969 repo_name=target.repo_name,
961 970 pull_request_id=pull_request.pull_request_id))
962 971 assert response.status_int == 200
963 972 response.mustcontain('Pull request updated to')
964 973 response.mustcontain('with 1 added, 1 removed commits.')
965 974
966 975 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
967 976 backend = backend_git
968 977 commits = [
969 978 {'message': 'master-commit-1'},
970 979 {'message': 'master-commit-2-change-1'},
971 980 {'message': 'master-commit-3-change-2'},
972 981
973 982 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
974 983 {'message': 'feat-commit-2'},
975 984 ]
976 985 commit_ids = backend.create_master_repo(commits)
977 986 target = backend.create_repo(heads=['master-commit-3-change-2'])
978 987 source = backend.create_repo(heads=['feat-commit-2'])
979 988
980 989 # create pr from a in source to A in target
981 990 pull_request = PullRequest()
982 991 pull_request.source_repo = source
983 992
984 993 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
985 994 branch=backend.default_branch_name,
986 995 commit_id=commit_ids['master-commit-3-change-2'])
987 996
988 997 pull_request.target_repo = target
989 998 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
990 999 branch=backend.default_branch_name, commit_id=commit_ids['feat-commit-2'])
991 1000
992 1001 pull_request.revisions = [
993 1002 commit_ids['feat-commit-1'],
994 1003 commit_ids['feat-commit-2']
995 1004 ]
996 1005 pull_request.title = u"Test"
997 1006 pull_request.description = u"Description"
998 1007 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
999 1008 pull_request.pull_request_state = PullRequest.STATE_CREATED
1000 1009 Session().add(pull_request)
1001 1010 Session().commit()
1002 1011 pull_request_id = pull_request.pull_request_id
1003 1012
1004 1013 # PR is created, now we simulate a force-push into target,
1005 1014 # that drops a 2 last commits
1006 1015 vcsrepo = target.scm_instance()
1007 1016 vcsrepo.config.clear_section('hooks')
1008 1017 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
1009 1018
1010 1019 # update PR
1011 1020 url = route_path('pullrequest_update',
1012 1021 repo_name=target.repo_name,
1013 1022 pull_request_id=pull_request_id)
1014 1023 self.app.post(url,
1015 1024 params={'update_commits': 'true', 'csrf_token': csrf_token},
1016 1025 status=200)
1017 1026
1018 1027 response = self.app.get(route_path('pullrequest_new', repo_name=target.repo_name))
1019 1028 assert response.status_int == 200
1020 1029 response.mustcontain('Pull request updated to')
1021 1030 response.mustcontain('with 0 added, 0 removed commits.')
1022 1031
1023 1032 def test_update_of_ancestor_reference(self, backend, csrf_token):
1024 1033 commits = [
1025 1034 {'message': 'ancestor'},
1026 1035 {'message': 'change'},
1027 1036 {'message': 'change-2'},
1028 1037 {'message': 'ancestor-new', 'parents': ['ancestor']},
1029 1038 {'message': 'change-rebased'},
1030 1039 ]
1031 1040 commit_ids = backend.create_master_repo(commits)
1032 1041 target = backend.create_repo(heads=['ancestor'])
1033 1042 source = backend.create_repo(heads=['change'])
1034 1043
1035 1044 # create pr from a in source to A in target
1036 1045 pull_request = PullRequest()
1037 1046 pull_request.source_repo = source
1038 1047
1039 1048 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1040 1049 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1041 1050 pull_request.target_repo = target
1042 1051 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1043 1052 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1044 1053 pull_request.revisions = [commit_ids['change']]
1045 1054 pull_request.title = u"Test"
1046 1055 pull_request.description = u"Description"
1047 1056 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1048 1057 pull_request.pull_request_state = PullRequest.STATE_CREATED
1049 1058 Session().add(pull_request)
1050 1059 Session().commit()
1051 1060 pull_request_id = pull_request.pull_request_id
1052 1061
1053 1062 # target has ancestor - ancestor-new
1054 1063 # source has ancestor - ancestor-new - change-rebased
1055 1064 backend.pull_heads(target, heads=['ancestor-new'])
1056 1065 backend.pull_heads(source, heads=['change-rebased'])
1057 1066
1058 1067 # update PR
1059 1068 self.app.post(
1060 1069 route_path('pullrequest_update',
1061 1070 repo_name=target.repo_name, pull_request_id=pull_request_id),
1062 1071 params={'update_commits': 'true', 'csrf_token': csrf_token},
1063 1072 status=200)
1064 1073
1065 1074 # Expect the target reference to be updated correctly
1066 1075 pull_request = PullRequest.get(pull_request_id)
1067 1076 assert pull_request.revisions == [commit_ids['change-rebased']]
1068 1077 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
1069 1078 branch=backend.default_branch_name,
1070 1079 commit_id=commit_ids['ancestor-new'])
1071 1080 assert pull_request.target_ref == expected_target_ref
1072 1081
1073 1082 def test_remove_pull_request_branch(self, backend_git, csrf_token):
1074 1083 branch_name = 'development'
1075 1084 commits = [
1076 1085 {'message': 'initial-commit'},
1077 1086 {'message': 'old-feature'},
1078 1087 {'message': 'new-feature', 'branch': branch_name},
1079 1088 ]
1080 1089 repo = backend_git.create_repo(commits)
1081 1090 repo_name = repo.repo_name
1082 1091 commit_ids = backend_git.commit_ids
1083 1092
1084 1093 pull_request = PullRequest()
1085 1094 pull_request.source_repo = repo
1086 1095 pull_request.target_repo = repo
1087 1096 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1088 1097 branch=branch_name, commit_id=commit_ids['new-feature'])
1089 1098 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1090 1099 branch=backend_git.default_branch_name, commit_id=commit_ids['old-feature'])
1091 1100 pull_request.revisions = [commit_ids['new-feature']]
1092 1101 pull_request.title = u"Test"
1093 1102 pull_request.description = u"Description"
1094 1103 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1095 1104 pull_request.pull_request_state = PullRequest.STATE_CREATED
1096 1105 Session().add(pull_request)
1097 1106 Session().commit()
1098 1107
1099 1108 pull_request_id = pull_request.pull_request_id
1100 1109
1101 1110 vcs = repo.scm_instance()
1102 1111 vcs.remove_ref('refs/heads/{}'.format(branch_name))
1103 1112 # NOTE(marcink): run GC to ensure the commits are gone
1104 1113 vcs.run_gc()
1105 1114
1106 1115 response = self.app.get(route_path(
1107 1116 'pullrequest_show',
1108 1117 repo_name=repo_name,
1109 1118 pull_request_id=pull_request_id))
1110 1119
1111 1120 assert response.status_int == 200
1112 1121
1113 1122 response.assert_response().element_contains(
1114 1123 '#changeset_compare_view_content .alert strong',
1115 1124 'Missing commits')
1116 1125 response.assert_response().element_contains(
1117 1126 '#changeset_compare_view_content .alert',
1118 1127 'This pull request cannot be displayed, because one or more'
1119 1128 ' commits no longer exist in the source repository.')
1120 1129
1121 1130 def test_strip_commits_from_pull_request(
1122 1131 self, backend, pr_util, csrf_token):
1123 1132 commits = [
1124 1133 {'message': 'initial-commit'},
1125 1134 {'message': 'old-feature'},
1126 1135 {'message': 'new-feature', 'parents': ['initial-commit']},
1127 1136 ]
1128 1137 pull_request = pr_util.create_pull_request(
1129 1138 commits, target_head='initial-commit', source_head='new-feature',
1130 1139 revisions=['new-feature'])
1131 1140
1132 1141 vcs = pr_util.source_repository.scm_instance()
1133 1142 if backend.alias == 'git':
1134 1143 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
1135 1144 else:
1136 1145 vcs.strip(pr_util.commit_ids['new-feature'])
1137 1146
1138 1147 response = self.app.get(route_path(
1139 1148 'pullrequest_show',
1140 1149 repo_name=pr_util.target_repository.repo_name,
1141 1150 pull_request_id=pull_request.pull_request_id))
1142 1151
1143 1152 assert response.status_int == 200
1144 1153
1145 1154 response.assert_response().element_contains(
1146 1155 '#changeset_compare_view_content .alert strong',
1147 1156 'Missing commits')
1148 1157 response.assert_response().element_contains(
1149 1158 '#changeset_compare_view_content .alert',
1150 1159 'This pull request cannot be displayed, because one or more'
1151 1160 ' commits no longer exist in the source repository.')
1152 1161 response.assert_response().element_contains(
1153 1162 '#update_commits',
1154 1163 'Update commits')
1155 1164
1156 1165 def test_strip_commits_and_update(
1157 1166 self, backend, pr_util, csrf_token):
1158 1167 commits = [
1159 1168 {'message': 'initial-commit'},
1160 1169 {'message': 'old-feature'},
1161 1170 {'message': 'new-feature', 'parents': ['old-feature']},
1162 1171 ]
1163 1172 pull_request = pr_util.create_pull_request(
1164 1173 commits, target_head='old-feature', source_head='new-feature',
1165 1174 revisions=['new-feature'], mergeable=True)
1166 1175
1167 1176 vcs = pr_util.source_repository.scm_instance()
1168 1177 if backend.alias == 'git':
1169 1178 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
1170 1179 else:
1171 1180 vcs.strip(pr_util.commit_ids['new-feature'])
1172 1181
1173 1182 url = route_path('pullrequest_update',
1174 1183 repo_name=pull_request.target_repo.repo_name,
1175 1184 pull_request_id=pull_request.pull_request_id)
1176 1185 response = self.app.post(url,
1177 1186 params={'update_commits': 'true',
1178 1187 'csrf_token': csrf_token})
1179 1188
1180 1189 assert response.status_int == 200
1181 1190 assert response.body == '{"response": true, "redirect_url": null}'
1182 1191
1183 1192 # Make sure that after update, it won't raise 500 errors
1184 1193 response = self.app.get(route_path(
1185 1194 'pullrequest_show',
1186 1195 repo_name=pr_util.target_repository.repo_name,
1187 1196 pull_request_id=pull_request.pull_request_id))
1188 1197
1189 1198 assert response.status_int == 200
1190 1199 response.assert_response().element_contains(
1191 1200 '#changeset_compare_view_content .alert strong',
1192 1201 'Missing commits')
1193 1202
1194 1203 def test_branch_is_a_link(self, pr_util):
1195 1204 pull_request = pr_util.create_pull_request()
1196 1205 pull_request.source_ref = 'branch:origin:1234567890abcdef'
1197 1206 pull_request.target_ref = 'branch:target:abcdef1234567890'
1198 1207 Session().add(pull_request)
1199 1208 Session().commit()
1200 1209
1201 1210 response = self.app.get(route_path(
1202 1211 'pullrequest_show',
1203 1212 repo_name=pull_request.target_repo.scm_instance().name,
1204 1213 pull_request_id=pull_request.pull_request_id))
1205 1214 assert response.status_int == 200
1206 1215
1207 1216 source = response.assert_response().get_element('.pr-source-info')
1208 1217 source_parent = source.getparent()
1209 1218 assert len(source_parent) == 1
1210 1219
1211 1220 target = response.assert_response().get_element('.pr-target-info')
1212 1221 target_parent = target.getparent()
1213 1222 assert len(target_parent) == 1
1214 1223
1215 1224 expected_origin_link = route_path(
1216 1225 'repo_commits',
1217 1226 repo_name=pull_request.source_repo.scm_instance().name,
1218 1227 params=dict(branch='origin'))
1219 1228 expected_target_link = route_path(
1220 1229 'repo_commits',
1221 1230 repo_name=pull_request.target_repo.scm_instance().name,
1222 1231 params=dict(branch='target'))
1223 1232 assert source_parent.attrib['href'] == expected_origin_link
1224 1233 assert target_parent.attrib['href'] == expected_target_link
1225 1234
1226 1235 def test_bookmark_is_not_a_link(self, pr_util):
1227 1236 pull_request = pr_util.create_pull_request()
1228 1237 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1229 1238 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1230 1239 Session().add(pull_request)
1231 1240 Session().commit()
1232 1241
1233 1242 response = self.app.get(route_path(
1234 1243 'pullrequest_show',
1235 1244 repo_name=pull_request.target_repo.scm_instance().name,
1236 1245 pull_request_id=pull_request.pull_request_id))
1237 1246 assert response.status_int == 200
1238 1247
1239 1248 source = response.assert_response().get_element('.pr-source-info')
1240 1249 assert source.text.strip() == 'bookmark:origin'
1241 1250 assert source.getparent().attrib.get('href') is None
1242 1251
1243 1252 target = response.assert_response().get_element('.pr-target-info')
1244 1253 assert target.text.strip() == 'bookmark:target'
1245 1254 assert target.getparent().attrib.get('href') is None
1246 1255
1247 1256 def test_tag_is_not_a_link(self, pr_util):
1248 1257 pull_request = pr_util.create_pull_request()
1249 1258 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1250 1259 pull_request.target_ref = 'tag:target:abcdef1234567890'
1251 1260 Session().add(pull_request)
1252 1261 Session().commit()
1253 1262
1254 1263 response = self.app.get(route_path(
1255 1264 'pullrequest_show',
1256 1265 repo_name=pull_request.target_repo.scm_instance().name,
1257 1266 pull_request_id=pull_request.pull_request_id))
1258 1267 assert response.status_int == 200
1259 1268
1260 1269 source = response.assert_response().get_element('.pr-source-info')
1261 1270 assert source.text.strip() == 'tag:origin'
1262 1271 assert source.getparent().attrib.get('href') is None
1263 1272
1264 1273 target = response.assert_response().get_element('.pr-target-info')
1265 1274 assert target.text.strip() == 'tag:target'
1266 1275 assert target.getparent().attrib.get('href') is None
1267 1276
1268 1277 @pytest.mark.parametrize('mergeable', [True, False])
1269 1278 def test_shadow_repository_link(
1270 1279 self, mergeable, pr_util, http_host_only_stub):
1271 1280 """
1272 1281 Check that the pull request summary page displays a link to the shadow
1273 1282 repository if the pull request is mergeable. If it is not mergeable
1274 1283 the link should not be displayed.
1275 1284 """
1276 1285 pull_request = pr_util.create_pull_request(
1277 1286 mergeable=mergeable, enable_notifications=False)
1278 1287 target_repo = pull_request.target_repo.scm_instance()
1279 1288 pr_id = pull_request.pull_request_id
1280 1289 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1281 1290 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1282 1291
1283 1292 response = self.app.get(route_path(
1284 1293 'pullrequest_show',
1285 1294 repo_name=target_repo.name,
1286 1295 pull_request_id=pr_id))
1287 1296
1288 1297 if mergeable:
1289 1298 response.assert_response().element_value_contains(
1290 1299 'input.pr-mergeinfo', shadow_url)
1291 1300 response.assert_response().element_value_contains(
1292 1301 'input.pr-mergeinfo ', 'pr-merge')
1293 1302 else:
1294 1303 response.assert_response().no_element_exists('.pr-mergeinfo')
1295 1304
1296 1305
1297 1306 @pytest.mark.usefixtures('app')
1298 1307 @pytest.mark.backends("git", "hg")
1299 1308 class TestPullrequestsControllerDelete(object):
1300 1309 def test_pull_request_delete_button_permissions_admin(
1301 1310 self, autologin_user, user_admin, pr_util):
1302 1311 pull_request = pr_util.create_pull_request(
1303 1312 author=user_admin.username, enable_notifications=False)
1304 1313
1305 1314 response = self.app.get(route_path(
1306 1315 'pullrequest_show',
1307 1316 repo_name=pull_request.target_repo.scm_instance().name,
1308 1317 pull_request_id=pull_request.pull_request_id))
1309 1318
1310 1319 response.mustcontain('id="delete_pullrequest"')
1311 1320 response.mustcontain('Confirm to delete this pull request')
1312 1321
1313 1322 def test_pull_request_delete_button_permissions_owner(
1314 1323 self, autologin_regular_user, user_regular, pr_util):
1315 1324 pull_request = pr_util.create_pull_request(
1316 1325 author=user_regular.username, enable_notifications=False)
1317 1326
1318 1327 response = self.app.get(route_path(
1319 1328 'pullrequest_show',
1320 1329 repo_name=pull_request.target_repo.scm_instance().name,
1321 1330 pull_request_id=pull_request.pull_request_id))
1322 1331
1323 1332 response.mustcontain('id="delete_pullrequest"')
1324 1333 response.mustcontain('Confirm to delete this pull request')
1325 1334
1326 1335 def test_pull_request_delete_button_permissions_forbidden(
1327 1336 self, autologin_regular_user, user_regular, user_admin, pr_util):
1328 1337 pull_request = pr_util.create_pull_request(
1329 1338 author=user_admin.username, enable_notifications=False)
1330 1339
1331 1340 response = self.app.get(route_path(
1332 1341 'pullrequest_show',
1333 1342 repo_name=pull_request.target_repo.scm_instance().name,
1334 1343 pull_request_id=pull_request.pull_request_id))
1335 1344 response.mustcontain(no=['id="delete_pullrequest"'])
1336 1345 response.mustcontain(no=['Confirm to delete this pull request'])
1337 1346
1338 1347 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1339 1348 self, autologin_regular_user, user_regular, user_admin, pr_util,
1340 1349 user_util):
1341 1350
1342 1351 pull_request = pr_util.create_pull_request(
1343 1352 author=user_admin.username, enable_notifications=False)
1344 1353
1345 1354 user_util.grant_user_permission_to_repo(
1346 1355 pull_request.target_repo, user_regular,
1347 1356 'repository.write')
1348 1357
1349 1358 response = self.app.get(route_path(
1350 1359 'pullrequest_show',
1351 1360 repo_name=pull_request.target_repo.scm_instance().name,
1352 1361 pull_request_id=pull_request.pull_request_id))
1353 1362
1354 1363 response.mustcontain('id="open_edit_pullrequest"')
1355 1364 response.mustcontain('id="delete_pullrequest"')
1356 1365 response.mustcontain(no=['Confirm to delete this pull request'])
1357 1366
1358 1367 def test_delete_comment_returns_404_if_comment_does_not_exist(
1359 1368 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1360 1369
1361 1370 pull_request = pr_util.create_pull_request(
1362 1371 author=user_admin.username, enable_notifications=False)
1363 1372
1364 1373 self.app.post(
1365 1374 route_path(
1366 1375 'pullrequest_comment_delete',
1367 1376 repo_name=pull_request.target_repo.scm_instance().name,
1368 1377 pull_request_id=pull_request.pull_request_id,
1369 1378 comment_id=1024404),
1370 1379 extra_environ=xhr_header,
1371 1380 params={'csrf_token': csrf_token},
1372 1381 status=404
1373 1382 )
1374 1383
1375 1384 def test_delete_comment(
1376 1385 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1377 1386
1378 1387 pull_request = pr_util.create_pull_request(
1379 1388 author=user_admin.username, enable_notifications=False)
1380 1389 comment = pr_util.create_comment()
1381 1390 comment_id = comment.comment_id
1382 1391
1383 1392 response = self.app.post(
1384 1393 route_path(
1385 1394 'pullrequest_comment_delete',
1386 1395 repo_name=pull_request.target_repo.scm_instance().name,
1387 1396 pull_request_id=pull_request.pull_request_id,
1388 1397 comment_id=comment_id),
1389 1398 extra_environ=xhr_header,
1390 1399 params={'csrf_token': csrf_token},
1391 1400 status=200
1392 1401 )
1393 1402 assert response.body == 'true'
1394 1403
1395 1404 @pytest.mark.parametrize('url_type', [
1396 1405 'pullrequest_new',
1397 1406 'pullrequest_create',
1398 1407 'pullrequest_update',
1399 1408 'pullrequest_merge',
1400 1409 ])
1401 1410 def test_pull_request_is_forbidden_on_archived_repo(
1402 1411 self, autologin_user, backend, xhr_header, user_util, url_type):
1403 1412
1404 1413 # create a temporary repo
1405 1414 source = user_util.create_repo(repo_type=backend.alias)
1406 1415 repo_name = source.repo_name
1407 1416 repo = Repository.get_by_repo_name(repo_name)
1408 1417 repo.archived = True
1409 1418 Session().commit()
1410 1419
1411 1420 response = self.app.get(
1412 1421 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302)
1413 1422
1414 1423 msg = 'Action not supported for archived repository.'
1415 1424 assert_session_flash(response, msg)
1416 1425
1417 1426
1418 1427 def assert_pull_request_status(pull_request, expected_status):
1419 1428 status = ChangesetStatusModel().calculated_review_status(pull_request=pull_request)
1420 1429 assert status == expected_status
1421 1430
1422 1431
1423 1432 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1424 1433 @pytest.mark.usefixtures("autologin_user")
1425 1434 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1426 1435 app.get(route_path(route, repo_name=backend_svn.repo_name), status=404)
General Comments 0
You need to be logged in to leave comments. Login now