##// END OF EJS Templates
tests: fixed some tests after sidebar introduction
marcink -
r4486:8b48fea3 default
parent child Browse files
Show More
@@ -1,667 +1,672 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
21 21 import mock
22 22 import pytest
23 23 import lxml.html
24 24
25 25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 26 from rhodecode.tests import assert_session_flash
27 27 from rhodecode.tests.utils import AssertResponse, commit_change
28 28
29 29
30 30 def route_path(name, params=None, **kwargs):
31 31 import urllib
32 32
33 33 base_url = {
34 34 'repo_compare_select': '/{repo_name}/compare',
35 35 'repo_compare': '/{repo_name}/compare/{source_ref_type}@{source_ref}...{target_ref_type}@{target_ref}',
36 36 }[name].format(**kwargs)
37 37
38 38 if params:
39 39 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
40 40 return base_url
41 41
42 42
43 43 @pytest.mark.usefixtures("autologin_user", "app")
44 44 class TestCompareView(object):
45 45
46 46 def test_compare_index_is_reached_at_least_once(self, backend):
47 47 repo = backend.repo
48 48 self.app.get(
49 49 route_path('repo_compare_select', repo_name=repo.repo_name))
50 50
51 51 @pytest.mark.xfail_backends("svn", reason="Requires pull")
52 52 def test_compare_remote_with_different_commit_indexes(self, backend):
53 53 # Preparing the following repository structure:
54 54 #
55 55 # Origin repository has two commits:
56 56 #
57 57 # 0 1
58 58 # A -- D
59 59 #
60 60 # The fork of it has a few more commits and "D" has a commit index
61 61 # which does not exist in origin.
62 62 #
63 63 # 0 1 2 3 4
64 64 # A -- -- -- D -- E
65 65 # \- B -- C
66 66 #
67 67
68 68 fork = backend.create_repo()
69 69
70 70 # prepare fork
71 71 commit0 = commit_change(
72 72 fork.repo_name, filename='file1', content='A',
73 73 message='A', vcs_type=backend.alias, parent=None, newfile=True)
74 74
75 75 commit1 = commit_change(
76 76 fork.repo_name, filename='file1', content='B',
77 77 message='B, child of A', vcs_type=backend.alias, parent=commit0)
78 78
79 79 commit_change( # commit 2
80 80 fork.repo_name, filename='file1', content='C',
81 81 message='C, child of B', vcs_type=backend.alias, parent=commit1)
82 82
83 83 commit3 = commit_change(
84 84 fork.repo_name, filename='file1', content='D',
85 85 message='D, child of A', vcs_type=backend.alias, parent=commit0)
86 86
87 87 commit4 = commit_change(
88 88 fork.repo_name, filename='file1', content='E',
89 89 message='E, child of D', vcs_type=backend.alias, parent=commit3)
90 90
91 91 # prepare origin repository, taking just the history up to D
92 92 origin = backend.create_repo()
93 93
94 94 origin_repo = origin.scm_instance(cache=False)
95 95 origin_repo.config.clear_section('hooks')
96 96 origin_repo.pull(fork.repo_full_path, commit_ids=[commit3.raw_id])
97 97 origin_repo = origin.scm_instance(cache=False) # cache rebuild
98 98
99 99 # Verify test fixture setup
100 100 # This does not work for git
101 101 if backend.alias != 'git':
102 102 assert 5 == len(fork.scm_instance().commit_ids)
103 103 assert 2 == len(origin_repo.commit_ids)
104 104
105 105 # Comparing the revisions
106 106 response = self.app.get(
107 107 route_path('repo_compare',
108 108 repo_name=origin.repo_name,
109 109 source_ref_type="rev", source_ref=commit3.raw_id,
110 110 target_ref_type="rev", target_ref=commit4.raw_id,
111 111 params=dict(merge='1', target_repo=fork.repo_name)
112 112 ))
113 113
114 114 compare_page = ComparePage(response)
115 115 compare_page.contains_commits([commit4])
116 116
117 117 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
118 118 def test_compare_forks_on_branch_extra_commits(self, backend):
119 119 repo1 = backend.create_repo()
120 120
121 121 # commit something !
122 122 commit0 = commit_change(
123 123 repo1.repo_name, filename='file1', content='line1\n',
124 124 message='commit1', vcs_type=backend.alias, parent=None,
125 125 newfile=True)
126 126
127 127 # fork this repo
128 128 repo2 = backend.create_fork()
129 129
130 130 # add two extra commit into fork
131 131 commit1 = commit_change(
132 132 repo2.repo_name, filename='file1', content='line1\nline2\n',
133 133 message='commit2', vcs_type=backend.alias, parent=commit0)
134 134
135 135 commit2 = commit_change(
136 136 repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
137 137 message='commit3', vcs_type=backend.alias, parent=commit1)
138 138
139 139 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
140 140 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
141 141
142 142 response = self.app.get(
143 143 route_path('repo_compare',
144 144 repo_name=repo1.repo_name,
145 145 source_ref_type="branch", source_ref=commit_id2,
146 146 target_ref_type="branch", target_ref=commit_id1,
147 147 params=dict(merge='1', target_repo=repo2.repo_name)
148 148 ))
149 149
150 150 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id2))
151 151 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id1))
152 152
153 153 compare_page = ComparePage(response)
154 154 compare_page.contains_change_summary(1, 2, 0)
155 155 compare_page.contains_commits([commit1, commit2])
156 156
157 157 anchor = 'a_c-{}-826e8142e6ba'.format(commit0.short_id)
158 158 compare_page.contains_file_links_and_anchors([('file1', anchor), ])
159 159
160 160 # Swap is removed when comparing branches since it's a PR feature and
161 161 # it is then a preview mode
162 162 compare_page.swap_is_hidden()
163 163 compare_page.target_source_are_disabled()
164 164
165 165 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
166 166 def test_compare_forks_on_branch_extra_commits_origin_has_incomming(self, backend):
167 167 repo1 = backend.create_repo()
168 168
169 169 # commit something !
170 170 commit0 = commit_change(
171 171 repo1.repo_name, filename='file1', content='line1\n',
172 172 message='commit1', vcs_type=backend.alias, parent=None,
173 173 newfile=True)
174 174
175 175 # fork this repo
176 176 repo2 = backend.create_fork()
177 177
178 178 # now commit something to origin repo
179 179 commit_change(
180 180 repo1.repo_name, filename='file2', content='line1file2\n',
181 181 message='commit2', vcs_type=backend.alias, parent=commit0,
182 182 newfile=True)
183 183
184 184 # add two extra commit into fork
185 185 commit1 = commit_change(
186 186 repo2.repo_name, filename='file1', content='line1\nline2\n',
187 187 message='commit2', vcs_type=backend.alias, parent=commit0)
188 188
189 189 commit2 = commit_change(
190 190 repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
191 191 message='commit3', vcs_type=backend.alias, parent=commit1)
192 192
193 193 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
194 194 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
195 195
196 196 response = self.app.get(
197 197 route_path('repo_compare',
198 198 repo_name=repo1.repo_name,
199 199 source_ref_type="branch", source_ref=commit_id2,
200 200 target_ref_type="branch", target_ref=commit_id1,
201 201 params=dict(merge='1', target_repo=repo2.repo_name),
202 202 ))
203 203
204 204 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id2))
205 205 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id1))
206 206
207 207 compare_page = ComparePage(response)
208 208 compare_page.contains_change_summary(1, 2, 0)
209 209 compare_page.contains_commits([commit1, commit2])
210 210 anchor = 'a_c-{}-826e8142e6ba'.format(commit0.short_id)
211 211 compare_page.contains_file_links_and_anchors([('file1', anchor), ])
212 212
213 213 # Swap is removed when comparing branches since it's a PR feature and
214 214 # it is then a preview mode
215 215 compare_page.swap_is_hidden()
216 216 compare_page.target_source_are_disabled()
217 217
218 218 @pytest.mark.xfail_backends("svn")
219 219 # TODO(marcink): no svn support for compare two seperate repos
220 220 def test_compare_of_unrelated_forks(self, backend):
221 221 orig = backend.create_repo(number_of_commits=1)
222 222 fork = backend.create_repo(number_of_commits=1)
223 223
224 224 response = self.app.get(
225 225 route_path('repo_compare',
226 226 repo_name=orig.repo_name,
227 227 source_ref_type="rev", source_ref="tip",
228 228 target_ref_type="rev", target_ref="tip",
229 229 params=dict(merge='1', target_repo=fork.repo_name),
230 230 ),
231 231 status=302)
232 232 response = response.follow()
233 233 response.mustcontain("Repositories unrelated.")
234 234
235 235 @pytest.mark.xfail_backends("svn")
236 236 def test_compare_cherry_pick_commits_from_bottom(self, backend):
237 237
238 238 # repo1:
239 239 # commit0:
240 240 # commit1:
241 241 # repo1-fork- in which we will cherry pick bottom commits
242 242 # commit0:
243 243 # commit1:
244 244 # commit2: x
245 245 # commit3: x
246 246 # commit4: x
247 247 # commit5:
248 248 # make repo1, and commit1+commit2
249 249
250 250 repo1 = backend.create_repo()
251 251
252 252 # commit something !
253 253 commit0 = commit_change(
254 254 repo1.repo_name, filename='file1', content='line1\n',
255 255 message='commit1', vcs_type=backend.alias, parent=None,
256 256 newfile=True)
257 257 commit1 = commit_change(
258 258 repo1.repo_name, filename='file1', content='line1\nline2\n',
259 259 message='commit2', vcs_type=backend.alias, parent=commit0)
260 260
261 261 # fork this repo
262 262 repo2 = backend.create_fork()
263 263
264 264 # now make commit3-6
265 265 commit2 = commit_change(
266 266 repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
267 267 message='commit3', vcs_type=backend.alias, parent=commit1)
268 268 commit3 = commit_change(
269 269 repo1.repo_name, filename='file1',
270 270 content='line1\nline2\nline3\nline4\n', message='commit4',
271 271 vcs_type=backend.alias, parent=commit2)
272 272 commit4 = commit_change(
273 273 repo1.repo_name, filename='file1',
274 274 content='line1\nline2\nline3\nline4\nline5\n', message='commit5',
275 275 vcs_type=backend.alias, parent=commit3)
276 276 commit_change( # commit 5
277 277 repo1.repo_name, filename='file1',
278 278 content='line1\nline2\nline3\nline4\nline5\nline6\n',
279 279 message='commit6', vcs_type=backend.alias, parent=commit4)
280 280
281 281 response = self.app.get(
282 282 route_path('repo_compare',
283 283 repo_name=repo2.repo_name,
284 284 # parent of commit2, in target repo2
285 285 source_ref_type="rev", source_ref=commit1.raw_id,
286 286 target_ref_type="rev", target_ref=commit4.raw_id,
287 287 params=dict(merge='1', target_repo=repo1.repo_name),
288 288 ))
289 289 response.mustcontain('%s@%s' % (repo2.repo_name, commit1.short_id))
290 290 response.mustcontain('%s@%s' % (repo1.repo_name, commit4.short_id))
291 291
292 292 # files
293 293 compare_page = ComparePage(response)
294 294 compare_page.contains_change_summary(1, 3, 0)
295 295 compare_page.contains_commits([commit2, commit3, commit4])
296 296 anchor = 'a_c-{}-826e8142e6ba'.format(commit1.short_id)
297 297 compare_page.contains_file_links_and_anchors([('file1', anchor),])
298 298
299 299 @pytest.mark.xfail_backends("svn")
300 300 def test_compare_cherry_pick_commits_from_top(self, backend):
301 301 # repo1:
302 302 # commit0:
303 303 # commit1:
304 304 # repo1-fork- in which we will cherry pick bottom commits
305 305 # commit0:
306 306 # commit1:
307 307 # commit2:
308 308 # commit3: x
309 309 # commit4: x
310 310 # commit5: x
311 311
312 312 # make repo1, and commit1+commit2
313 313 repo1 = backend.create_repo()
314 314
315 315 # commit something !
316 316 commit0 = commit_change(
317 317 repo1.repo_name, filename='file1', content='line1\n',
318 318 message='commit1', vcs_type=backend.alias, parent=None,
319 319 newfile=True)
320 320 commit1 = commit_change(
321 321 repo1.repo_name, filename='file1', content='line1\nline2\n',
322 322 message='commit2', vcs_type=backend.alias, parent=commit0)
323 323
324 324 # fork this repo
325 325 backend.create_fork()
326 326
327 327 # now make commit3-6
328 328 commit2 = commit_change(
329 329 repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
330 330 message='commit3', vcs_type=backend.alias, parent=commit1)
331 331 commit3 = commit_change(
332 332 repo1.repo_name, filename='file1',
333 333 content='line1\nline2\nline3\nline4\n', message='commit4',
334 334 vcs_type=backend.alias, parent=commit2)
335 335 commit4 = commit_change(
336 336 repo1.repo_name, filename='file1',
337 337 content='line1\nline2\nline3\nline4\nline5\n', message='commit5',
338 338 vcs_type=backend.alias, parent=commit3)
339 339 commit5 = commit_change(
340 340 repo1.repo_name, filename='file1',
341 341 content='line1\nline2\nline3\nline4\nline5\nline6\n',
342 342 message='commit6', vcs_type=backend.alias, parent=commit4)
343 343
344 344 response = self.app.get(
345 345 route_path('repo_compare',
346 346 repo_name=repo1.repo_name,
347 347 # parent of commit3, not in source repo2
348 348 source_ref_type="rev", source_ref=commit2.raw_id,
349 349 target_ref_type="rev", target_ref=commit5.raw_id,
350 350 params=dict(merge='1'),))
351 351
352 352 response.mustcontain('%s@%s' % (repo1.repo_name, commit2.short_id))
353 353 response.mustcontain('%s@%s' % (repo1.repo_name, commit5.short_id))
354 354
355 355 compare_page = ComparePage(response)
356 356 compare_page.contains_change_summary(1, 3, 0)
357 357 compare_page.contains_commits([commit3, commit4, commit5])
358 358
359 359 # files
360 360 anchor = 'a_c-{}-826e8142e6ba'.format(commit2.short_id)
361 361 compare_page.contains_file_links_and_anchors([('file1', anchor),])
362 362
363 363 @pytest.mark.xfail_backends("svn")
364 364 def test_compare_remote_branches(self, backend):
365 365 repo1 = backend.repo
366 366 repo2 = backend.create_fork()
367 367
368 368 commit_id1 = repo1.get_commit(commit_idx=3).raw_id
369 369 commit_id1_short = repo1.get_commit(commit_idx=3).short_id
370 370 commit_id2 = repo1.get_commit(commit_idx=6).raw_id
371 371 commit_id2_short = repo1.get_commit(commit_idx=6).short_id
372 372
373 373 response = self.app.get(
374 374 route_path('repo_compare',
375 375 repo_name=repo1.repo_name,
376 376 source_ref_type="rev", source_ref=commit_id1,
377 377 target_ref_type="rev", target_ref=commit_id2,
378 378 params=dict(merge='1', target_repo=repo2.repo_name),
379 379 ))
380 380
381 381 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id1))
382 382 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id2))
383 383
384 384 compare_page = ComparePage(response)
385 385
386 386 # outgoing commits between those commits
387 387 compare_page.contains_commits(
388 388 [repo2.get_commit(commit_idx=x) for x in [4, 5, 6]])
389 389
390 390 # files
391 391 compare_page.contains_file_links_and_anchors([
392 392 ('vcs/backends/hg.py', 'a_c-{}-9c390eb52cd6'.format(commit_id2_short)),
393 393 ('vcs/backends/__init__.py', 'a_c-{}-41b41c1f2796'.format(commit_id1_short)),
394 394 ('vcs/backends/base.py', 'a_c-{}-2f574d260608'.format(commit_id1_short)),
395 395 ])
396 396
397 397 @pytest.mark.xfail_backends("svn")
398 398 def test_source_repo_new_commits_after_forking_simple_diff(self, backend):
399 399 repo1 = backend.create_repo()
400 400 r1_name = repo1.repo_name
401 401
402 402 commit0 = commit_change(
403 403 repo=r1_name, filename='file1',
404 404 content='line1', message='commit1', vcs_type=backend.alias,
405 405 newfile=True)
406 406 assert repo1.scm_instance().commit_ids == [commit0.raw_id]
407 407
408 408 # fork the repo1
409 409 repo2 = backend.create_fork()
410 410 assert repo2.scm_instance().commit_ids == [commit0.raw_id]
411 411
412 412 self.r2_id = repo2.repo_id
413 413 r2_name = repo2.repo_name
414 414
415 415 commit1 = commit_change(
416 416 repo=r2_name, filename='file1-fork',
417 417 content='file1-line1-from-fork', message='commit1-fork',
418 418 vcs_type=backend.alias, parent=repo2.scm_instance()[-1],
419 419 newfile=True)
420 420
421 421 commit2 = commit_change(
422 422 repo=r2_name, filename='file2-fork',
423 423 content='file2-line1-from-fork', message='commit2-fork',
424 424 vcs_type=backend.alias, parent=commit1,
425 425 newfile=True)
426 426
427 427 commit_change( # commit 3
428 428 repo=r2_name, filename='file3-fork',
429 429 content='file3-line1-from-fork', message='commit3-fork',
430 430 vcs_type=backend.alias, parent=commit2, newfile=True)
431 431
432 432 # compare !
433 433 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
434 434 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
435 435
436 436 response = self.app.get(
437 437 route_path('repo_compare',
438 438 repo_name=r2_name,
439 439 source_ref_type="branch", source_ref=commit_id1,
440 440 target_ref_type="branch", target_ref=commit_id2,
441 441 params=dict(merge='1', target_repo=r1_name),
442 442 ))
443 443
444 444 response.mustcontain('%s@%s' % (r2_name, commit_id1))
445 445 response.mustcontain('%s@%s' % (r1_name, commit_id2))
446 446 response.mustcontain('No files')
447 447 response.mustcontain('No commits in this compare')
448 448
449 449 commit0 = commit_change(
450 450 repo=r1_name, filename='file2',
451 451 content='line1-added-after-fork', message='commit2-parent',
452 452 vcs_type=backend.alias, parent=None, newfile=True)
453 453
454 454 # compare !
455 455 response = self.app.get(
456 456 route_path('repo_compare',
457 457 repo_name=r2_name,
458 458 source_ref_type="branch", source_ref=commit_id1,
459 459 target_ref_type="branch", target_ref=commit_id2,
460 460 params=dict(merge='1', target_repo=r1_name),
461 461 ))
462 462
463 463 response.mustcontain('%s@%s' % (r2_name, commit_id1))
464 464 response.mustcontain('%s@%s' % (r1_name, commit_id2))
465 465
466 466 response.mustcontain("""commit2-parent""")
467 467 response.mustcontain("""line1-added-after-fork""")
468 468 compare_page = ComparePage(response)
469 469 compare_page.contains_change_summary(1, 1, 0)
470 470
471 471 @pytest.mark.xfail_backends("svn")
472 472 def test_compare_commits(self, backend, xhr_header):
473 473 commit0 = backend.repo.get_commit(commit_idx=0)
474 474 commit1 = backend.repo.get_commit(commit_idx=1)
475 475
476 476 response = self.app.get(
477 477 route_path('repo_compare',
478 478 repo_name=backend.repo_name,
479 479 source_ref_type="rev", source_ref=commit0.raw_id,
480 480 target_ref_type="rev", target_ref=commit1.raw_id,
481 481 params=dict(merge='1')
482 482 ),
483 483 extra_environ=xhr_header, )
484 484
485 485 # outgoing commits between those commits
486 486 compare_page = ComparePage(response)
487 487 compare_page.contains_commits(commits=[commit1])
488 488
489 489 def test_errors_when_comparing_unknown_source_repo(self, backend):
490 490 repo = backend.repo
491 491 badrepo = 'badrepo'
492 492
493 493 response = self.app.get(
494 494 route_path('repo_compare',
495 495 repo_name=badrepo,
496 496 source_ref_type="rev", source_ref='tip',
497 497 target_ref_type="rev", target_ref='tip',
498 498 params=dict(merge='1', target_repo=repo.repo_name)
499 499 ),
500 500 status=404)
501 501
502 502 def test_errors_when_comparing_unknown_target_repo(self, backend):
503 503 repo = backend.repo
504 504 badrepo = 'badrepo'
505 505
506 506 response = self.app.get(
507 507 route_path('repo_compare',
508 508 repo_name=repo.repo_name,
509 509 source_ref_type="rev", source_ref='tip',
510 510 target_ref_type="rev", target_ref='tip',
511 511 params=dict(merge='1', target_repo=badrepo),
512 512 ),
513 513 status=302)
514 514 redirected = response.follow()
515 515 redirected.mustcontain(
516 516 'Could not find the target repo: `{}`'.format(badrepo))
517 517
518 518 def test_compare_not_in_preview_mode(self, backend_stub):
519 519 commit0 = backend_stub.repo.get_commit(commit_idx=0)
520 520 commit1 = backend_stub.repo.get_commit(commit_idx=1)
521 521
522 522 response = self.app.get(
523 523 route_path('repo_compare',
524 524 repo_name=backend_stub.repo_name,
525 525 source_ref_type="rev", source_ref=commit0.raw_id,
526 526 target_ref_type="rev", target_ref=commit1.raw_id,
527 527 ))
528 528
529 529 # outgoing commits between those commits
530 530 compare_page = ComparePage(response)
531 531 compare_page.swap_is_visible()
532 532 compare_page.target_source_are_enabled()
533 533
534 534 def test_compare_of_fork_with_largefiles(self, backend_hg, settings_util):
535 535 orig = backend_hg.create_repo(number_of_commits=1)
536 536 fork = backend_hg.create_fork()
537 537
538 538 settings_util.create_repo_rhodecode_ui(
539 539 orig, 'extensions', value='', key='largefiles', active=False)
540 540 settings_util.create_repo_rhodecode_ui(
541 541 fork, 'extensions', value='', key='largefiles', active=True)
542 542
543 543 compare_module = ('rhodecode.lib.vcs.backends.hg.repository.'
544 544 'MercurialRepository.compare')
545 545 with mock.patch(compare_module) as compare_mock:
546 546 compare_mock.side_effect = RepositoryRequirementError()
547 547
548 548 response = self.app.get(
549 549 route_path('repo_compare',
550 550 repo_name=orig.repo_name,
551 551 source_ref_type="rev", source_ref="tip",
552 552 target_ref_type="rev", target_ref="tip",
553 553 params=dict(merge='1', target_repo=fork.repo_name),
554 554 ),
555 555 status=302)
556 556
557 557 assert_session_flash(
558 558 response,
559 559 'Could not compare repos with different large file settings')
560 560
561 561
562 562 @pytest.mark.usefixtures("autologin_user")
563 563 class TestCompareControllerSvn(object):
564 564
565 565 def test_supports_references_with_path(self, app, backend_svn):
566 566 repo = backend_svn['svn-simple-layout']
567 567 commit_id = repo.get_commit(commit_idx=-1).raw_id
568 568 response = app.get(
569 569 route_path('repo_compare',
570 570 repo_name=repo.repo_name,
571 571 source_ref_type="tag",
572 572 source_ref="%s@%s" % ('tags/v0.1', commit_id),
573 573 target_ref_type="tag",
574 574 target_ref="%s@%s" % ('tags/v0.2', commit_id),
575 575 params=dict(merge='1'),
576 576 ),
577 577 status=200)
578 578
579 579 # Expecting no commits, since both paths are at the same revision
580 580 response.mustcontain('No commits in this compare')
581 581
582 582 # Should find only one file changed when comparing those two tags
583 583 response.mustcontain('example.py')
584 584 compare_page = ComparePage(response)
585 585 compare_page.contains_change_summary(1, 5, 1)
586 586
587 587 def test_shows_commits_if_different_ids(self, app, backend_svn):
588 588 repo = backend_svn['svn-simple-layout']
589 589 source_id = repo.get_commit(commit_idx=-6).raw_id
590 590 target_id = repo.get_commit(commit_idx=-1).raw_id
591 591 response = app.get(
592 592 route_path('repo_compare',
593 593 repo_name=repo.repo_name,
594 594 source_ref_type="tag",
595 595 source_ref="%s@%s" % ('tags/v0.1', source_id),
596 596 target_ref_type="tag",
597 597 target_ref="%s@%s" % ('tags/v0.2', target_id),
598 598 params=dict(merge='1')
599 599 ),
600 600 status=200)
601 601
602 602 # It should show commits
603 603 assert 'No commits in this compare' not in response.body
604 604
605 605 # Should find only one file changed when comparing those two tags
606 606 response.mustcontain('example.py')
607 607 compare_page = ComparePage(response)
608 608 compare_page.contains_change_summary(1, 5, 1)
609 609
610 610
611 611 class ComparePage(AssertResponse):
612 612 """
613 613 Abstracts the page template from the tests
614 614 """
615 615
616 616 def contains_file_links_and_anchors(self, files):
617 617 doc = lxml.html.fromstring(self.response.body)
618 618 for filename, file_id in files:
619 619 self.contains_one_anchor(file_id)
620 620 diffblock = doc.cssselect('[data-f-path="%s"]' % filename)
621 621 assert len(diffblock) == 2
622 assert len(diffblock[0].cssselect('a[href="#%s"]' % file_id)) == 1
622 for lnk in diffblock[0].cssselect('a'):
623 if 'permalink' in lnk.text:
624 assert '#{}'.format(file_id) in lnk.attrib['href']
625 break
626 else:
627 pytest.fail('Unable to find permalink')
623 628
624 629 def contains_change_summary(self, files_changed, inserted, deleted):
625 630 template = (
626 631 '{files_changed} file{plural} changed: '
627 632 '<span class="op-added">{inserted} inserted</span>, <span class="op-deleted">{deleted} deleted</span>')
628 633 self.response.mustcontain(template.format(
629 634 files_changed=files_changed,
630 635 plural="s" if files_changed > 1 else "",
631 636 inserted=inserted,
632 637 deleted=deleted))
633 638
634 639 def contains_commits(self, commits, ancestors=None):
635 640 response = self.response
636 641
637 642 for commit in commits:
638 643 # Expecting to see the commit message in an element which
639 644 # has the ID "c-{commit.raw_id}"
640 645 self.element_contains('#c-' + commit.raw_id, commit.message)
641 646 self.contains_one_link(
642 647 'r%s:%s' % (commit.idx, commit.short_id),
643 648 self._commit_url(commit))
644 649
645 650 if ancestors:
646 651 response.mustcontain('Ancestor')
647 652 for ancestor in ancestors:
648 653 self.contains_one_link(
649 654 ancestor.short_id, self._commit_url(ancestor))
650 655
651 656 def _commit_url(self, commit):
652 657 return '/%s/changeset/%s' % (commit.repository.name, commit.raw_id)
653 658
654 659 def swap_is_hidden(self):
655 660 assert '<a id="btn-swap"' not in self.response.text
656 661
657 662 def swap_is_visible(self):
658 663 assert '<a id="btn-swap"' in self.response.text
659 664
660 665 def target_source_are_disabled(self):
661 666 response = self.response
662 667 response.mustcontain("var enable_fields = false;")
663 668 response.mustcontain('.select2("enable", enable_fields)')
664 669
665 670 def target_source_are_enabled(self):
666 671 response = self.response
667 672 response.mustcontain("var enable_fields = true;")
@@ -1,1652 +1,1658 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_show_versions_of_pr(self, backend, csrf_token):
120 120 commits = [
121 121 {'message': 'initial-commit',
122 122 'added': [FileNode('test-file.txt', 'LINE1\n')]},
123 123
124 124 {'message': 'commit-1',
125 125 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\n')]},
126 126 # Above is the initial version of PR that changes a single line
127 127
128 128 # from now on we'll add 3x commit adding a nother line on each step
129 129 {'message': 'commit-2',
130 130 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\nLINE3\n')]},
131 131
132 132 {'message': 'commit-3',
133 133 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\nLINE3\nLINE4\n')]},
134 134
135 135 {'message': 'commit-4',
136 136 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\nLINE3\nLINE4\nLINE5\n')]},
137 137 ]
138 138
139 139 commit_ids = backend.create_master_repo(commits)
140 140 target = backend.create_repo(heads=['initial-commit'])
141 141 source = backend.create_repo(heads=['commit-1'])
142 142 source_repo_name = source.repo_name
143 143 target_repo_name = target.repo_name
144 144
145 145 target_ref = 'branch:{branch}:{commit_id}'.format(
146 146 branch=backend.default_branch_name, commit_id=commit_ids['initial-commit'])
147 147 source_ref = 'branch:{branch}:{commit_id}'.format(
148 148 branch=backend.default_branch_name, commit_id=commit_ids['commit-1'])
149 149
150 150 response = self.app.post(
151 151 route_path('pullrequest_create', repo_name=source.repo_name),
152 152 [
153 ('source_repo', source.repo_name),
153 ('source_repo', source_repo_name),
154 154 ('source_ref', source_ref),
155 ('target_repo', target.repo_name),
155 ('target_repo', target_repo_name),
156 156 ('target_ref', target_ref),
157 157 ('common_ancestor', commit_ids['initial-commit']),
158 158 ('pullrequest_title', 'Title'),
159 159 ('pullrequest_desc', 'Description'),
160 160 ('description_renderer', 'markdown'),
161 161 ('__start__', 'review_members:sequence'),
162 162 ('__start__', 'reviewer:mapping'),
163 163 ('user_id', '1'),
164 164 ('__start__', 'reasons:sequence'),
165 165 ('reason', 'Some reason'),
166 166 ('__end__', 'reasons:sequence'),
167 167 ('__start__', 'rules:sequence'),
168 168 ('__end__', 'rules:sequence'),
169 169 ('mandatory', 'False'),
170 170 ('__end__', 'reviewer:mapping'),
171 171 ('__end__', 'review_members:sequence'),
172 172 ('__start__', 'revisions:sequence'),
173 173 ('revisions', commit_ids['commit-1']),
174 174 ('__end__', 'revisions:sequence'),
175 175 ('user', ''),
176 176 ('csrf_token', csrf_token),
177 177 ],
178 178 status=302)
179 179
180 180 location = response.headers['Location']
181 181
182 182 pull_request_id = location.rsplit('/', 1)[1]
183 183 assert pull_request_id != 'new'
184 184 pull_request = PullRequest.get(int(pull_request_id))
185 185
186 186 pull_request_id = pull_request.pull_request_id
187 187
188 188 # Show initial version of PR
189 189 response = self.app.get(
190 190 route_path('pullrequest_show',
191 191 repo_name=target_repo_name,
192 192 pull_request_id=pull_request_id))
193 193
194 194 response.mustcontain('commit-1')
195 195 response.mustcontain(no=['commit-2'])
196 196 response.mustcontain(no=['commit-3'])
197 197 response.mustcontain(no=['commit-4'])
198 198
199 199 response.mustcontain('cb-addition"></span><span>LINE2</span>')
200 200 response.mustcontain(no=['LINE3'])
201 201 response.mustcontain(no=['LINE4'])
202 202 response.mustcontain(no=['LINE5'])
203 203
204 204 # update PR #1
205 205 source_repo = Repository.get_by_repo_name(source_repo_name)
206 206 backend.pull_heads(source_repo, heads=['commit-2'])
207 207 response = self.app.post(
208 208 route_path('pullrequest_update',
209 209 repo_name=target_repo_name, pull_request_id=pull_request_id),
210 210 params={'update_commits': 'true', 'csrf_token': csrf_token})
211 211
212 212 # update PR #2
213 213 source_repo = Repository.get_by_repo_name(source_repo_name)
214 214 backend.pull_heads(source_repo, heads=['commit-3'])
215 215 response = self.app.post(
216 216 route_path('pullrequest_update',
217 217 repo_name=target_repo_name, pull_request_id=pull_request_id),
218 218 params={'update_commits': 'true', 'csrf_token': csrf_token})
219 219
220 220 # update PR #3
221 221 source_repo = Repository.get_by_repo_name(source_repo_name)
222 222 backend.pull_heads(source_repo, heads=['commit-4'])
223 223 response = self.app.post(
224 224 route_path('pullrequest_update',
225 225 repo_name=target_repo_name, pull_request_id=pull_request_id),
226 226 params={'update_commits': 'true', 'csrf_token': csrf_token})
227 227
228 228 # Show final version !
229 229 response = self.app.get(
230 230 route_path('pullrequest_show',
231 231 repo_name=target_repo_name,
232 232 pull_request_id=pull_request_id))
233 233
234 234 # 3 updates, and the latest == 4
235 235 response.mustcontain('4 versions available for this pull request')
236 236 response.mustcontain(no=['rhodecode diff rendering error'])
237 237
238 238 # initial show must have 3 commits, and 3 adds
239 239 response.mustcontain('commit-1')
240 240 response.mustcontain('commit-2')
241 241 response.mustcontain('commit-3')
242 242 response.mustcontain('commit-4')
243 243
244 244 response.mustcontain('cb-addition"></span><span>LINE2</span>')
245 245 response.mustcontain('cb-addition"></span><span>LINE3</span>')
246 246 response.mustcontain('cb-addition"></span><span>LINE4</span>')
247 247 response.mustcontain('cb-addition"></span><span>LINE5</span>')
248 248
249 249 # fetch versions
250 250 pr = PullRequest.get(pull_request_id)
251 251 versions = [x.pull_request_version_id for x in pr.versions.all()]
252 252 assert len(versions) == 3
253 253
254 254 # show v1,v2,v3,v4
255 255 def cb_line(text):
256 256 return 'cb-addition"></span><span>{}</span>'.format(text)
257 257
258 258 def cb_context(text):
259 259 return '<span class="cb-code"><span class="cb-action cb-context">' \
260 260 '</span><span>{}</span></span>'.format(text)
261 261
262 262 commit_tests = {
263 263 # in response, not in response
264 264 1: (['commit-1'], ['commit-2', 'commit-3', 'commit-4']),
265 265 2: (['commit-1', 'commit-2'], ['commit-3', 'commit-4']),
266 266 3: (['commit-1', 'commit-2', 'commit-3'], ['commit-4']),
267 267 4: (['commit-1', 'commit-2', 'commit-3', 'commit-4'], []),
268 268 }
269 269 diff_tests = {
270 270 1: (['LINE2'], ['LINE3', 'LINE4', 'LINE5']),
271 271 2: (['LINE2', 'LINE3'], ['LINE4', 'LINE5']),
272 272 3: (['LINE2', 'LINE3', 'LINE4'], ['LINE5']),
273 273 4: (['LINE2', 'LINE3', 'LINE4', 'LINE5'], []),
274 274 }
275 275 for idx, ver in enumerate(versions, 1):
276 276
277 277 response = self.app.get(
278 278 route_path('pullrequest_show',
279 279 repo_name=target_repo_name,
280 280 pull_request_id=pull_request_id,
281 281 params={'version': ver}))
282 282
283 283 response.mustcontain(no=['rhodecode diff rendering error'])
284 284 response.mustcontain('Showing changes at v{}'.format(idx))
285 285
286 286 yes, no = commit_tests[idx]
287 287 for y in yes:
288 288 response.mustcontain(y)
289 289 for n in no:
290 290 response.mustcontain(no=n)
291 291
292 292 yes, no = diff_tests[idx]
293 293 for y in yes:
294 294 response.mustcontain(cb_line(y))
295 295 for n in no:
296 296 response.mustcontain(no=n)
297 297
298 298 # show diff between versions
299 299 diff_compare_tests = {
300 300 1: (['LINE3'], ['LINE1', 'LINE2']),
301 301 2: (['LINE3', 'LINE4'], ['LINE1', 'LINE2']),
302 302 3: (['LINE3', 'LINE4', 'LINE5'], ['LINE1', 'LINE2']),
303 303 }
304 304 for idx, ver in enumerate(versions, 1):
305 305 adds, context = diff_compare_tests[idx]
306 306
307 307 to_ver = ver+1
308 308 if idx == 3:
309 309 to_ver = 'latest'
310 310
311 311 response = self.app.get(
312 312 route_path('pullrequest_show',
313 313 repo_name=target_repo_name,
314 314 pull_request_id=pull_request_id,
315 315 params={'from_version': versions[0], 'version': to_ver}))
316 316
317 317 response.mustcontain(no=['rhodecode diff rendering error'])
318 318
319 319 for a in adds:
320 320 response.mustcontain(cb_line(a))
321 321 for c in context:
322 322 response.mustcontain(cb_context(c))
323 323
324 324 # test version v2 -> v3
325 325 response = self.app.get(
326 326 route_path('pullrequest_show',
327 327 repo_name=target_repo_name,
328 328 pull_request_id=pull_request_id,
329 329 params={'from_version': versions[1], 'version': versions[2]}))
330 330
331 331 response.mustcontain(cb_context('LINE1'))
332 332 response.mustcontain(cb_context('LINE2'))
333 333 response.mustcontain(cb_context('LINE3'))
334 334 response.mustcontain(cb_line('LINE4'))
335 335
336 336 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
337 337 # Logout
338 338 response = self.app.post(
339 339 h.route_path('logout'),
340 340 params={'csrf_token': csrf_token})
341 341 # Login as regular user
342 342 response = self.app.post(h.route_path('login'),
343 343 {'username': TEST_USER_REGULAR_LOGIN,
344 344 'password': 'test12'})
345 345
346 346 pull_request = pr_util.create_pull_request(
347 347 author=TEST_USER_REGULAR_LOGIN)
348 348
349 349 response = self.app.get(route_path(
350 350 'pullrequest_show',
351 351 repo_name=pull_request.target_repo.scm_instance().name,
352 352 pull_request_id=pull_request.pull_request_id))
353 353
354 354 response.mustcontain('Server-side pull request merging is disabled.')
355 355
356 356 assert_response = response.assert_response()
357 357 # for regular user without a merge permissions, we don't see it
358 358 assert_response.no_element_exists('#close-pull-request-action')
359 359
360 360 user_util.grant_user_permission_to_repo(
361 361 pull_request.target_repo,
362 362 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
363 363 'repository.write')
364 364 response = self.app.get(route_path(
365 365 'pullrequest_show',
366 366 repo_name=pull_request.target_repo.scm_instance().name,
367 367 pull_request_id=pull_request.pull_request_id))
368 368
369 369 response.mustcontain('Server-side pull request merging is disabled.')
370 370
371 371 assert_response = response.assert_response()
372 372 # now regular user has a merge permissions, we have CLOSE button
373 373 assert_response.one_element_exists('#close-pull-request-action')
374 374
375 375 def test_show_invalid_commit_id(self, pr_util):
376 376 # Simulating invalid revisions which will cause a lookup error
377 377 pull_request = pr_util.create_pull_request()
378 378 pull_request.revisions = ['invalid']
379 379 Session().add(pull_request)
380 380 Session().commit()
381 381
382 382 response = self.app.get(route_path(
383 383 'pullrequest_show',
384 384 repo_name=pull_request.target_repo.scm_instance().name,
385 385 pull_request_id=pull_request.pull_request_id))
386 386
387 387 for commit_id in pull_request.revisions:
388 388 response.mustcontain(commit_id)
389 389
390 390 def test_show_invalid_source_reference(self, pr_util):
391 391 pull_request = pr_util.create_pull_request()
392 392 pull_request.source_ref = 'branch:b:invalid'
393 393 Session().add(pull_request)
394 394 Session().commit()
395 395
396 396 self.app.get(route_path(
397 397 'pullrequest_show',
398 398 repo_name=pull_request.target_repo.scm_instance().name,
399 399 pull_request_id=pull_request.pull_request_id))
400 400
401 401 def test_edit_title_description(self, pr_util, csrf_token):
402 402 pull_request = pr_util.create_pull_request()
403 403 pull_request_id = pull_request.pull_request_id
404 404
405 405 response = self.app.post(
406 406 route_path('pullrequest_update',
407 407 repo_name=pull_request.target_repo.repo_name,
408 408 pull_request_id=pull_request_id),
409 409 params={
410 410 'edit_pull_request': 'true',
411 411 'title': 'New title',
412 412 'description': 'New description',
413 413 'csrf_token': csrf_token})
414 414
415 415 assert_session_flash(
416 416 response, u'Pull request title & description updated.',
417 417 category='success')
418 418
419 419 pull_request = PullRequest.get(pull_request_id)
420 420 assert pull_request.title == 'New title'
421 421 assert pull_request.description == 'New description'
422 422
423 423 def test_edit_title_description_closed(self, pr_util, csrf_token):
424 424 pull_request = pr_util.create_pull_request()
425 425 pull_request_id = pull_request.pull_request_id
426 426 repo_name = pull_request.target_repo.repo_name
427 427 pr_util.close()
428 428
429 429 response = self.app.post(
430 430 route_path('pullrequest_update',
431 431 repo_name=repo_name, pull_request_id=pull_request_id),
432 432 params={
433 433 'edit_pull_request': 'true',
434 434 'title': 'New title',
435 435 'description': 'New description',
436 436 'csrf_token': csrf_token}, status=200)
437 437 assert_session_flash(
438 438 response, u'Cannot update closed pull requests.',
439 439 category='error')
440 440
441 441 def test_update_invalid_source_reference(self, pr_util, csrf_token):
442 442 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
443 443
444 444 pull_request = pr_util.create_pull_request()
445 445 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
446 446 Session().add(pull_request)
447 447 Session().commit()
448 448
449 449 pull_request_id = pull_request.pull_request_id
450 450
451 451 response = self.app.post(
452 452 route_path('pullrequest_update',
453 453 repo_name=pull_request.target_repo.repo_name,
454 454 pull_request_id=pull_request_id),
455 455 params={'update_commits': 'true', 'csrf_token': csrf_token})
456 456
457 457 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
458 458 UpdateFailureReason.MISSING_SOURCE_REF])
459 459 assert_session_flash(response, expected_msg, category='error')
460 460
461 461 def test_missing_target_reference(self, pr_util, csrf_token):
462 462 from rhodecode.lib.vcs.backends.base import MergeFailureReason
463 463 pull_request = pr_util.create_pull_request(
464 464 approved=True, mergeable=True)
465 465 unicode_reference = u'branch:invalid-branch:invalid-commit-id'
466 466 pull_request.target_ref = unicode_reference
467 467 Session().add(pull_request)
468 468 Session().commit()
469 469
470 470 pull_request_id = pull_request.pull_request_id
471 471 pull_request_url = route_path(
472 472 'pullrequest_show',
473 473 repo_name=pull_request.target_repo.repo_name,
474 474 pull_request_id=pull_request_id)
475 475
476 476 response = self.app.get(pull_request_url)
477 477 target_ref_id = 'invalid-branch'
478 478 merge_resp = MergeResponse(
479 479 True, True, '', MergeFailureReason.MISSING_TARGET_REF,
480 480 metadata={'target_ref': PullRequest.unicode_to_reference(unicode_reference)})
481 481 response.assert_response().element_contains(
482 482 'div[data-role="merge-message"]', merge_resp.merge_status_message)
483 483
484 484 def test_comment_and_close_pull_request_custom_message_approved(
485 485 self, pr_util, csrf_token, xhr_header):
486 486
487 487 pull_request = pr_util.create_pull_request(approved=True)
488 488 pull_request_id = pull_request.pull_request_id
489 489 author = pull_request.user_id
490 490 repo = pull_request.target_repo.repo_id
491 491
492 492 self.app.post(
493 493 route_path('pullrequest_comment_create',
494 494 repo_name=pull_request.target_repo.scm_instance().name,
495 495 pull_request_id=pull_request_id),
496 496 params={
497 497 'close_pull_request': '1',
498 498 'text': 'Closing a PR',
499 499 'csrf_token': csrf_token},
500 500 extra_environ=xhr_header,)
501 501
502 502 journal = UserLog.query()\
503 503 .filter(UserLog.user_id == author)\
504 504 .filter(UserLog.repository_id == repo) \
505 505 .order_by(UserLog.user_log_id.asc()) \
506 506 .all()
507 507 assert journal[-1].action == 'repo.pull_request.close'
508 508
509 509 pull_request = PullRequest.get(pull_request_id)
510 510 assert pull_request.is_closed()
511 511
512 512 status = ChangesetStatusModel().get_status(
513 513 pull_request.source_repo, pull_request=pull_request)
514 514 assert status == ChangesetStatus.STATUS_APPROVED
515 515 comments = ChangesetComment().query() \
516 516 .filter(ChangesetComment.pull_request == pull_request) \
517 517 .order_by(ChangesetComment.comment_id.asc())\
518 518 .all()
519 519 assert comments[-1].text == 'Closing a PR'
520 520
521 521 def test_comment_force_close_pull_request_rejected(
522 522 self, pr_util, csrf_token, xhr_header):
523 523 pull_request = pr_util.create_pull_request()
524 524 pull_request_id = pull_request.pull_request_id
525 525 PullRequestModel().update_reviewers(
526 526 pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
527 527 pull_request.author)
528 528 author = pull_request.user_id
529 529 repo = pull_request.target_repo.repo_id
530 530
531 531 self.app.post(
532 532 route_path('pullrequest_comment_create',
533 533 repo_name=pull_request.target_repo.scm_instance().name,
534 534 pull_request_id=pull_request_id),
535 535 params={
536 536 'close_pull_request': '1',
537 537 'csrf_token': csrf_token},
538 538 extra_environ=xhr_header)
539 539
540 540 pull_request = PullRequest.get(pull_request_id)
541 541
542 542 journal = UserLog.query()\
543 543 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
544 544 .order_by(UserLog.user_log_id.asc()) \
545 545 .all()
546 546 assert journal[-1].action == 'repo.pull_request.close'
547 547
548 548 # check only the latest status, not the review status
549 549 status = ChangesetStatusModel().get_status(
550 550 pull_request.source_repo, pull_request=pull_request)
551 551 assert status == ChangesetStatus.STATUS_REJECTED
552 552
553 553 def test_comment_and_close_pull_request(
554 554 self, pr_util, csrf_token, xhr_header):
555 555 pull_request = pr_util.create_pull_request()
556 556 pull_request_id = pull_request.pull_request_id
557 557
558 558 response = self.app.post(
559 559 route_path('pullrequest_comment_create',
560 560 repo_name=pull_request.target_repo.scm_instance().name,
561 561 pull_request_id=pull_request.pull_request_id),
562 562 params={
563 563 'close_pull_request': 'true',
564 564 'csrf_token': csrf_token},
565 565 extra_environ=xhr_header)
566 566
567 567 assert response.json
568 568
569 569 pull_request = PullRequest.get(pull_request_id)
570 570 assert pull_request.is_closed()
571 571
572 572 # check only the latest status, not the review status
573 573 status = ChangesetStatusModel().get_status(
574 574 pull_request.source_repo, pull_request=pull_request)
575 575 assert status == ChangesetStatus.STATUS_REJECTED
576 576
577 577 def test_comment_and_close_pull_request_try_edit_comment(
578 578 self, pr_util, csrf_token, xhr_header
579 579 ):
580 580 pull_request = pr_util.create_pull_request()
581 581 pull_request_id = pull_request.pull_request_id
582 582 target_scm = pull_request.target_repo.scm_instance()
583 583 target_scm_name = target_scm.name
584 584
585 585 response = self.app.post(
586 586 route_path(
587 587 'pullrequest_comment_create',
588 588 repo_name=target_scm_name,
589 589 pull_request_id=pull_request_id,
590 590 ),
591 591 params={
592 592 'close_pull_request': 'true',
593 593 'csrf_token': csrf_token,
594 594 },
595 595 extra_environ=xhr_header)
596 596
597 597 assert response.json
598 598
599 599 pull_request = PullRequest.get(pull_request_id)
600 600 target_scm = pull_request.target_repo.scm_instance()
601 601 target_scm_name = target_scm.name
602 602 assert pull_request.is_closed()
603 603
604 604 # check only the latest status, not the review status
605 605 status = ChangesetStatusModel().get_status(
606 606 pull_request.source_repo, pull_request=pull_request)
607 607 assert status == ChangesetStatus.STATUS_REJECTED
608 608
609 609 comment_id = response.json.get('comment_id', None)
610 610 test_text = 'test'
611 611 response = self.app.post(
612 612 route_path(
613 613 'pullrequest_comment_edit',
614 614 repo_name=target_scm_name,
615 615 pull_request_id=pull_request_id,
616 616 comment_id=comment_id,
617 617 ),
618 618 extra_environ=xhr_header,
619 619 params={
620 620 'csrf_token': csrf_token,
621 621 'text': test_text,
622 622 },
623 623 status=403,
624 624 )
625 625 assert response.status_int == 403
626 626
627 627 def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header):
628 628 pull_request = pr_util.create_pull_request()
629 629 target_scm = pull_request.target_repo.scm_instance()
630 630 target_scm_name = target_scm.name
631 631
632 632 response = self.app.post(
633 633 route_path(
634 634 'pullrequest_comment_create',
635 635 repo_name=target_scm_name,
636 636 pull_request_id=pull_request.pull_request_id),
637 637 params={
638 638 'csrf_token': csrf_token,
639 639 'text': 'init',
640 640 },
641 641 extra_environ=xhr_header,
642 642 )
643 643 assert response.json
644 644
645 645 comment_id = response.json.get('comment_id', None)
646 646 assert comment_id
647 647 test_text = 'test'
648 648 self.app.post(
649 649 route_path(
650 650 'pullrequest_comment_edit',
651 651 repo_name=target_scm_name,
652 652 pull_request_id=pull_request.pull_request_id,
653 653 comment_id=comment_id,
654 654 ),
655 655 extra_environ=xhr_header,
656 656 params={
657 657 'csrf_token': csrf_token,
658 658 'text': test_text,
659 659 'version': '0',
660 660 },
661 661
662 662 )
663 663 text_form_db = ChangesetComment.query().filter(
664 664 ChangesetComment.comment_id == comment_id).first().text
665 665 assert test_text == text_form_db
666 666
667 667 def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header):
668 668 pull_request = pr_util.create_pull_request()
669 669 target_scm = pull_request.target_repo.scm_instance()
670 670 target_scm_name = target_scm.name
671 671
672 672 response = self.app.post(
673 673 route_path(
674 674 'pullrequest_comment_create',
675 675 repo_name=target_scm_name,
676 676 pull_request_id=pull_request.pull_request_id),
677 677 params={
678 678 'csrf_token': csrf_token,
679 679 'text': 'init',
680 680 },
681 681 extra_environ=xhr_header,
682 682 )
683 683 assert response.json
684 684
685 685 comment_id = response.json.get('comment_id', None)
686 686 assert comment_id
687 687 test_text = 'init'
688 688 response = self.app.post(
689 689 route_path(
690 690 'pullrequest_comment_edit',
691 691 repo_name=target_scm_name,
692 692 pull_request_id=pull_request.pull_request_id,
693 693 comment_id=comment_id,
694 694 ),
695 695 extra_environ=xhr_header,
696 696 params={
697 697 'csrf_token': csrf_token,
698 698 'text': test_text,
699 699 'version': '0',
700 700 },
701 701 status=404,
702 702
703 703 )
704 704 assert response.status_int == 404
705 705
706 706 def test_comment_and_try_edit_already_edited(self, pr_util, csrf_token, xhr_header):
707 707 pull_request = pr_util.create_pull_request()
708 708 target_scm = pull_request.target_repo.scm_instance()
709 709 target_scm_name = target_scm.name
710 710
711 711 response = self.app.post(
712 712 route_path(
713 713 'pullrequest_comment_create',
714 714 repo_name=target_scm_name,
715 715 pull_request_id=pull_request.pull_request_id),
716 716 params={
717 717 'csrf_token': csrf_token,
718 718 'text': 'init',
719 719 },
720 720 extra_environ=xhr_header,
721 721 )
722 722 assert response.json
723 723 comment_id = response.json.get('comment_id', None)
724 724 assert comment_id
725 725
726 726 test_text = 'test'
727 727 self.app.post(
728 728 route_path(
729 729 'pullrequest_comment_edit',
730 730 repo_name=target_scm_name,
731 731 pull_request_id=pull_request.pull_request_id,
732 732 comment_id=comment_id,
733 733 ),
734 734 extra_environ=xhr_header,
735 735 params={
736 736 'csrf_token': csrf_token,
737 737 'text': test_text,
738 738 'version': '0',
739 739 },
740 740
741 741 )
742 742 test_text_v2 = 'test_v2'
743 743 response = self.app.post(
744 744 route_path(
745 745 'pullrequest_comment_edit',
746 746 repo_name=target_scm_name,
747 747 pull_request_id=pull_request.pull_request_id,
748 748 comment_id=comment_id,
749 749 ),
750 750 extra_environ=xhr_header,
751 751 params={
752 752 'csrf_token': csrf_token,
753 753 'text': test_text_v2,
754 754 'version': '0',
755 755 },
756 756 status=409,
757 757 )
758 758 assert response.status_int == 409
759 759
760 760 text_form_db = ChangesetComment.query().filter(
761 761 ChangesetComment.comment_id == comment_id).first().text
762 762
763 763 assert test_text == text_form_db
764 764 assert test_text_v2 != text_form_db
765 765
766 766 def test_comment_and_comment_edit_permissions_forbidden(
767 767 self, autologin_regular_user, user_regular, user_admin, pr_util,
768 768 csrf_token, xhr_header):
769 769 pull_request = pr_util.create_pull_request(
770 770 author=user_admin.username, enable_notifications=False)
771 771 comment = CommentsModel().create(
772 772 text='test',
773 773 repo=pull_request.target_repo.scm_instance().name,
774 774 user=user_admin,
775 775 pull_request=pull_request,
776 776 )
777 777 response = self.app.post(
778 778 route_path(
779 779 'pullrequest_comment_edit',
780 780 repo_name=pull_request.target_repo.scm_instance().name,
781 781 pull_request_id=pull_request.pull_request_id,
782 782 comment_id=comment.comment_id,
783 783 ),
784 784 extra_environ=xhr_header,
785 785 params={
786 786 'csrf_token': csrf_token,
787 787 'text': 'test_text',
788 788 },
789 789 status=403,
790 790 )
791 791 assert response.status_int == 403
792 792
793 793 def test_create_pull_request(self, backend, csrf_token):
794 794 commits = [
795 795 {'message': 'ancestor'},
796 796 {'message': 'change'},
797 797 {'message': 'change2'},
798 798 ]
799 799 commit_ids = backend.create_master_repo(commits)
800 800 target = backend.create_repo(heads=['ancestor'])
801 801 source = backend.create_repo(heads=['change2'])
802 802
803 803 response = self.app.post(
804 804 route_path('pullrequest_create', repo_name=source.repo_name),
805 805 [
806 806 ('source_repo', source.repo_name),
807 807 ('source_ref', 'branch:default:' + commit_ids['change2']),
808 808 ('target_repo', target.repo_name),
809 809 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
810 810 ('common_ancestor', commit_ids['ancestor']),
811 811 ('pullrequest_title', 'Title'),
812 812 ('pullrequest_desc', 'Description'),
813 813 ('description_renderer', 'markdown'),
814 814 ('__start__', 'review_members:sequence'),
815 815 ('__start__', 'reviewer:mapping'),
816 816 ('user_id', '1'),
817 817 ('__start__', 'reasons:sequence'),
818 818 ('reason', 'Some reason'),
819 819 ('__end__', 'reasons:sequence'),
820 820 ('__start__', 'rules:sequence'),
821 821 ('__end__', 'rules:sequence'),
822 822 ('mandatory', 'False'),
823 823 ('__end__', 'reviewer:mapping'),
824 824 ('__end__', 'review_members:sequence'),
825 825 ('__start__', 'revisions:sequence'),
826 826 ('revisions', commit_ids['change']),
827 827 ('revisions', commit_ids['change2']),
828 828 ('__end__', 'revisions:sequence'),
829 829 ('user', ''),
830 830 ('csrf_token', csrf_token),
831 831 ],
832 832 status=302)
833 833
834 834 location = response.headers['Location']
835 835 pull_request_id = location.rsplit('/', 1)[1]
836 836 assert pull_request_id != 'new'
837 837 pull_request = PullRequest.get(int(pull_request_id))
838 838
839 839 # check that we have now both revisions
840 840 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
841 841 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
842 842 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
843 843 assert pull_request.target_ref == expected_target_ref
844 844
845 845 def test_reviewer_notifications(self, backend, csrf_token):
846 846 # We have to use the app.post for this test so it will create the
847 847 # notifications properly with the new PR
848 848 commits = [
849 849 {'message': 'ancestor',
850 850 'added': [FileNode('file_A', content='content_of_ancestor')]},
851 851 {'message': 'change',
852 852 'added': [FileNode('file_a', content='content_of_change')]},
853 853 {'message': 'change-child'},
854 854 {'message': 'ancestor-child', 'parents': ['ancestor'],
855 855 'added': [
856 856 FileNode('file_B', content='content_of_ancestor_child')]},
857 857 {'message': 'ancestor-child-2'},
858 858 ]
859 859 commit_ids = backend.create_master_repo(commits)
860 860 target = backend.create_repo(heads=['ancestor-child'])
861 861 source = backend.create_repo(heads=['change'])
862 862
863 863 response = self.app.post(
864 864 route_path('pullrequest_create', repo_name=source.repo_name),
865 865 [
866 866 ('source_repo', source.repo_name),
867 867 ('source_ref', 'branch:default:' + commit_ids['change']),
868 868 ('target_repo', target.repo_name),
869 869 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
870 870 ('common_ancestor', commit_ids['ancestor']),
871 871 ('pullrequest_title', 'Title'),
872 872 ('pullrequest_desc', 'Description'),
873 873 ('description_renderer', 'markdown'),
874 874 ('__start__', 'review_members:sequence'),
875 875 ('__start__', 'reviewer:mapping'),
876 876 ('user_id', '2'),
877 877 ('__start__', 'reasons:sequence'),
878 878 ('reason', 'Some reason'),
879 879 ('__end__', 'reasons:sequence'),
880 880 ('__start__', 'rules:sequence'),
881 881 ('__end__', 'rules:sequence'),
882 882 ('mandatory', 'False'),
883 883 ('__end__', 'reviewer:mapping'),
884 884 ('__end__', 'review_members:sequence'),
885 885 ('__start__', 'revisions:sequence'),
886 886 ('revisions', commit_ids['change']),
887 887 ('__end__', 'revisions:sequence'),
888 888 ('user', ''),
889 889 ('csrf_token', csrf_token),
890 890 ],
891 891 status=302)
892 892
893 893 location = response.headers['Location']
894 894
895 895 pull_request_id = location.rsplit('/', 1)[1]
896 896 assert pull_request_id != 'new'
897 897 pull_request = PullRequest.get(int(pull_request_id))
898 898
899 899 # Check that a notification was made
900 900 notifications = Notification.query()\
901 901 .filter(Notification.created_by == pull_request.author.user_id,
902 902 Notification.type_ == Notification.TYPE_PULL_REQUEST,
903 903 Notification.subject.contains(
904 904 "requested a pull request review. !%s" % pull_request_id))
905 905 assert len(notifications.all()) == 1
906 906
907 907 # Change reviewers and check that a notification was made
908 908 PullRequestModel().update_reviewers(
909 909 pull_request.pull_request_id, [(1, [], False, [])],
910 910 pull_request.author)
911 911 assert len(notifications.all()) == 2
912 912
913 913 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
914 914 csrf_token):
915 915 commits = [
916 916 {'message': 'ancestor',
917 917 'added': [FileNode('file_A', content='content_of_ancestor')]},
918 918 {'message': 'change',
919 919 'added': [FileNode('file_a', content='content_of_change')]},
920 920 {'message': 'change-child'},
921 921 {'message': 'ancestor-child', 'parents': ['ancestor'],
922 922 'added': [
923 923 FileNode('file_B', content='content_of_ancestor_child')]},
924 924 {'message': 'ancestor-child-2'},
925 925 ]
926 926 commit_ids = backend.create_master_repo(commits)
927 927 target = backend.create_repo(heads=['ancestor-child'])
928 928 source = backend.create_repo(heads=['change'])
929 929
930 930 response = self.app.post(
931 931 route_path('pullrequest_create', repo_name=source.repo_name),
932 932 [
933 933 ('source_repo', source.repo_name),
934 934 ('source_ref', 'branch:default:' + commit_ids['change']),
935 935 ('target_repo', target.repo_name),
936 936 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
937 937 ('common_ancestor', commit_ids['ancestor']),
938 938 ('pullrequest_title', 'Title'),
939 939 ('pullrequest_desc', 'Description'),
940 940 ('description_renderer', 'markdown'),
941 941 ('__start__', 'review_members:sequence'),
942 942 ('__start__', 'reviewer:mapping'),
943 943 ('user_id', '1'),
944 944 ('__start__', 'reasons:sequence'),
945 945 ('reason', 'Some reason'),
946 946 ('__end__', 'reasons:sequence'),
947 947 ('__start__', 'rules:sequence'),
948 948 ('__end__', 'rules:sequence'),
949 949 ('mandatory', 'False'),
950 950 ('__end__', 'reviewer:mapping'),
951 951 ('__end__', 'review_members:sequence'),
952 952 ('__start__', 'revisions:sequence'),
953 953 ('revisions', commit_ids['change']),
954 954 ('__end__', 'revisions:sequence'),
955 955 ('user', ''),
956 956 ('csrf_token', csrf_token),
957 957 ],
958 958 status=302)
959 959
960 960 location = response.headers['Location']
961 961
962 962 pull_request_id = location.rsplit('/', 1)[1]
963 963 assert pull_request_id != 'new'
964 964 pull_request = PullRequest.get(int(pull_request_id))
965 965
966 966 # target_ref has to point to the ancestor's commit_id in order to
967 967 # show the correct diff
968 968 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
969 969 assert pull_request.target_ref == expected_target_ref
970 970
971 971 # Check generated diff contents
972 972 response = response.follow()
973 973 response.mustcontain(no=['content_of_ancestor'])
974 974 response.mustcontain(no=['content_of_ancestor-child'])
975 975 response.mustcontain('content_of_change')
976 976
977 977 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
978 978 # Clear any previous calls to rcextensions
979 979 rhodecode.EXTENSIONS.calls.clear()
980 980
981 981 pull_request = pr_util.create_pull_request(
982 982 approved=True, mergeable=True)
983 983 pull_request_id = pull_request.pull_request_id
984 984 repo_name = pull_request.target_repo.scm_instance().name,
985 985
986 986 url = route_path('pullrequest_merge',
987 987 repo_name=str(repo_name[0]),
988 988 pull_request_id=pull_request_id)
989 989 response = self.app.post(url, params={'csrf_token': csrf_token}).follow()
990 990
991 991 pull_request = PullRequest.get(pull_request_id)
992 992
993 993 assert response.status_int == 200
994 994 assert pull_request.is_closed()
995 995 assert_pull_request_status(
996 996 pull_request, ChangesetStatus.STATUS_APPROVED)
997 997
998 998 # Check the relevant log entries were added
999 999 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(3)
1000 1000 actions = [log.action for log in user_logs]
1001 1001 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
1002 1002 expected_actions = [
1003 1003 u'repo.pull_request.close',
1004 1004 u'repo.pull_request.merge',
1005 1005 u'repo.pull_request.comment.create'
1006 1006 ]
1007 1007 assert actions == expected_actions
1008 1008
1009 1009 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(4)
1010 1010 actions = [log for log in user_logs]
1011 1011 assert actions[-1].action == 'user.push'
1012 1012 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
1013 1013
1014 1014 # Check post_push rcextension was really executed
1015 1015 push_calls = rhodecode.EXTENSIONS.calls['_push_hook']
1016 1016 assert len(push_calls) == 1
1017 1017 unused_last_call_args, last_call_kwargs = push_calls[0]
1018 1018 assert last_call_kwargs['action'] == 'push'
1019 1019 assert last_call_kwargs['commit_ids'] == pr_commit_ids
1020 1020
1021 1021 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
1022 1022 pull_request = pr_util.create_pull_request(mergeable=False)
1023 1023 pull_request_id = pull_request.pull_request_id
1024 1024 pull_request = PullRequest.get(pull_request_id)
1025 1025
1026 1026 response = self.app.post(
1027 1027 route_path('pullrequest_merge',
1028 1028 repo_name=pull_request.target_repo.scm_instance().name,
1029 1029 pull_request_id=pull_request.pull_request_id),
1030 1030 params={'csrf_token': csrf_token}).follow()
1031 1031
1032 1032 assert response.status_int == 200
1033 1033 response.mustcontain(
1034 1034 'Merge is not currently possible because of below failed checks.')
1035 1035 response.mustcontain('Server-side pull request merging is disabled.')
1036 1036
1037 1037 @pytest.mark.skip_backends('svn')
1038 1038 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
1039 1039 pull_request = pr_util.create_pull_request(mergeable=True)
1040 1040 pull_request_id = pull_request.pull_request_id
1041 1041 repo_name = pull_request.target_repo.scm_instance().name
1042 1042
1043 1043 response = self.app.post(
1044 1044 route_path('pullrequest_merge',
1045 1045 repo_name=repo_name, pull_request_id=pull_request_id),
1046 1046 params={'csrf_token': csrf_token}).follow()
1047 1047
1048 1048 assert response.status_int == 200
1049 1049
1050 1050 response.mustcontain(
1051 1051 'Merge is not currently possible because of below failed checks.')
1052 1052 response.mustcontain('Pull request reviewer approval is pending.')
1053 1053
1054 1054 def test_merge_pull_request_renders_failure_reason(
1055 1055 self, user_regular, csrf_token, pr_util):
1056 1056 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
1057 1057 pull_request_id = pull_request.pull_request_id
1058 1058 repo_name = pull_request.target_repo.scm_instance().name
1059 1059
1060 1060 merge_resp = MergeResponse(True, False, 'STUB_COMMIT_ID',
1061 1061 MergeFailureReason.PUSH_FAILED,
1062 1062 metadata={'target': 'shadow repo',
1063 1063 'merge_commit': 'xxx'})
1064 1064 model_patcher = mock.patch.multiple(
1065 1065 PullRequestModel,
1066 1066 merge_repo=mock.Mock(return_value=merge_resp),
1067 1067 merge_status=mock.Mock(return_value=(None, True, 'WRONG_MESSAGE')))
1068 1068
1069 1069 with model_patcher:
1070 1070 response = self.app.post(
1071 1071 route_path('pullrequest_merge',
1072 1072 repo_name=repo_name,
1073 1073 pull_request_id=pull_request_id),
1074 1074 params={'csrf_token': csrf_token}, status=302)
1075 1075
1076 1076 merge_resp = MergeResponse(True, True, '', MergeFailureReason.PUSH_FAILED,
1077 1077 metadata={'target': 'shadow repo',
1078 1078 'merge_commit': 'xxx'})
1079 1079 assert_session_flash(response, merge_resp.merge_status_message)
1080 1080
1081 1081 def test_update_source_revision(self, backend, csrf_token):
1082 1082 commits = [
1083 1083 {'message': 'ancestor'},
1084 1084 {'message': 'change'},
1085 1085 {'message': 'change-2'},
1086 1086 ]
1087 1087 commit_ids = backend.create_master_repo(commits)
1088 1088 target = backend.create_repo(heads=['ancestor'])
1089 1089 source = backend.create_repo(heads=['change'])
1090 1090
1091 1091 # create pr from a in source to A in target
1092 1092 pull_request = PullRequest()
1093 1093
1094 1094 pull_request.source_repo = source
1095 1095 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1096 1096 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1097 1097
1098 1098 pull_request.target_repo = target
1099 1099 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1100 1100 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1101 1101
1102 1102 pull_request.revisions = [commit_ids['change']]
1103 1103 pull_request.title = u"Test"
1104 1104 pull_request.description = u"Description"
1105 1105 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1106 1106 pull_request.pull_request_state = PullRequest.STATE_CREATED
1107 1107 Session().add(pull_request)
1108 1108 Session().commit()
1109 1109 pull_request_id = pull_request.pull_request_id
1110 1110
1111 1111 # source has ancestor - change - change-2
1112 1112 backend.pull_heads(source, heads=['change-2'])
1113 target_repo_name = target.repo_name
1113 1114
1114 1115 # update PR
1115 1116 self.app.post(
1116 1117 route_path('pullrequest_update',
1117 repo_name=target.repo_name, pull_request_id=pull_request_id),
1118 repo_name=target_repo_name, pull_request_id=pull_request_id),
1118 1119 params={'update_commits': 'true', 'csrf_token': csrf_token})
1119 1120
1120 1121 response = self.app.get(
1121 1122 route_path('pullrequest_show',
1122 repo_name=target.repo_name,
1123 repo_name=target_repo_name,
1123 1124 pull_request_id=pull_request.pull_request_id))
1124 1125
1125 1126 assert response.status_int == 200
1126 1127 response.mustcontain('Pull request updated to')
1127 1128 response.mustcontain('with 1 added, 0 removed commits.')
1128 1129
1129 1130 # check that we have now both revisions
1130 1131 pull_request = PullRequest.get(pull_request_id)
1131 1132 assert pull_request.revisions == [commit_ids['change-2'], commit_ids['change']]
1132 1133
1133 1134 def test_update_target_revision(self, backend, csrf_token):
1134 1135 commits = [
1135 1136 {'message': 'ancestor'},
1136 1137 {'message': 'change'},
1137 1138 {'message': 'ancestor-new', 'parents': ['ancestor']},
1138 1139 {'message': 'change-rebased'},
1139 1140 ]
1140 1141 commit_ids = backend.create_master_repo(commits)
1141 1142 target = backend.create_repo(heads=['ancestor'])
1142 1143 source = backend.create_repo(heads=['change'])
1143 1144
1144 1145 # create pr from a in source to A in target
1145 1146 pull_request = PullRequest()
1146 1147
1147 1148 pull_request.source_repo = source
1148 1149 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1149 1150 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1150 1151
1151 1152 pull_request.target_repo = target
1152 1153 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1153 1154 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1154 1155
1155 1156 pull_request.revisions = [commit_ids['change']]
1156 1157 pull_request.title = u"Test"
1157 1158 pull_request.description = u"Description"
1158 1159 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1159 1160 pull_request.pull_request_state = PullRequest.STATE_CREATED
1160 1161
1161 1162 Session().add(pull_request)
1162 1163 Session().commit()
1163 1164 pull_request_id = pull_request.pull_request_id
1164 1165
1165 1166 # target has ancestor - ancestor-new
1166 1167 # source has ancestor - ancestor-new - change-rebased
1167 1168 backend.pull_heads(target, heads=['ancestor-new'])
1168 1169 backend.pull_heads(source, heads=['change-rebased'])
1170 target_repo_name = target.repo_name
1169 1171
1170 1172 # update PR
1171 1173 url = route_path('pullrequest_update',
1172 repo_name=target.repo_name,
1174 repo_name=target_repo_name,
1173 1175 pull_request_id=pull_request_id)
1174 1176 self.app.post(url,
1175 1177 params={'update_commits': 'true', 'csrf_token': csrf_token},
1176 1178 status=200)
1177 1179
1178 1180 # check that we have now both revisions
1179 1181 pull_request = PullRequest.get(pull_request_id)
1180 1182 assert pull_request.revisions == [commit_ids['change-rebased']]
1181 1183 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
1182 1184 branch=backend.default_branch_name, commit_id=commit_ids['ancestor-new'])
1183 1185
1184 1186 response = self.app.get(
1185 1187 route_path('pullrequest_show',
1186 repo_name=target.repo_name,
1188 repo_name=target_repo_name,
1187 1189 pull_request_id=pull_request.pull_request_id))
1188 1190 assert response.status_int == 200
1189 1191 response.mustcontain('Pull request updated to')
1190 1192 response.mustcontain('with 1 added, 1 removed commits.')
1191 1193
1192 1194 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
1193 1195 backend = backend_git
1194 1196 commits = [
1195 1197 {'message': 'master-commit-1'},
1196 1198 {'message': 'master-commit-2-change-1'},
1197 1199 {'message': 'master-commit-3-change-2'},
1198 1200
1199 1201 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
1200 1202 {'message': 'feat-commit-2'},
1201 1203 ]
1202 1204 commit_ids = backend.create_master_repo(commits)
1203 1205 target = backend.create_repo(heads=['master-commit-3-change-2'])
1204 1206 source = backend.create_repo(heads=['feat-commit-2'])
1205 1207
1206 1208 # create pr from a in source to A in target
1207 1209 pull_request = PullRequest()
1208 1210 pull_request.source_repo = source
1209 1211
1210 1212 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1211 1213 branch=backend.default_branch_name,
1212 1214 commit_id=commit_ids['master-commit-3-change-2'])
1213 1215
1214 1216 pull_request.target_repo = target
1215 1217 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1216 1218 branch=backend.default_branch_name, commit_id=commit_ids['feat-commit-2'])
1217 1219
1218 1220 pull_request.revisions = [
1219 1221 commit_ids['feat-commit-1'],
1220 1222 commit_ids['feat-commit-2']
1221 1223 ]
1222 1224 pull_request.title = u"Test"
1223 1225 pull_request.description = u"Description"
1224 1226 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1225 1227 pull_request.pull_request_state = PullRequest.STATE_CREATED
1226 1228 Session().add(pull_request)
1227 1229 Session().commit()
1228 1230 pull_request_id = pull_request.pull_request_id
1229 1231
1230 1232 # PR is created, now we simulate a force-push into target,
1231 1233 # that drops a 2 last commits
1232 1234 vcsrepo = target.scm_instance()
1233 1235 vcsrepo.config.clear_section('hooks')
1234 1236 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
1237 target_repo_name = target.repo_name
1235 1238
1236 1239 # update PR
1237 1240 url = route_path('pullrequest_update',
1238 repo_name=target.repo_name,
1241 repo_name=target_repo_name,
1239 1242 pull_request_id=pull_request_id)
1240 1243 self.app.post(url,
1241 1244 params={'update_commits': 'true', 'csrf_token': csrf_token},
1242 1245 status=200)
1243 1246
1244 response = self.app.get(route_path('pullrequest_new', repo_name=target.repo_name))
1247 response = self.app.get(route_path('pullrequest_new', repo_name=target_repo_name))
1245 1248 assert response.status_int == 200
1246 1249 response.mustcontain('Pull request updated to')
1247 1250 response.mustcontain('with 0 added, 0 removed commits.')
1248 1251
1249 1252 def test_update_of_ancestor_reference(self, backend, csrf_token):
1250 1253 commits = [
1251 1254 {'message': 'ancestor'},
1252 1255 {'message': 'change'},
1253 1256 {'message': 'change-2'},
1254 1257 {'message': 'ancestor-new', 'parents': ['ancestor']},
1255 1258 {'message': 'change-rebased'},
1256 1259 ]
1257 1260 commit_ids = backend.create_master_repo(commits)
1258 1261 target = backend.create_repo(heads=['ancestor'])
1259 1262 source = backend.create_repo(heads=['change'])
1260 1263
1261 1264 # create pr from a in source to A in target
1262 1265 pull_request = PullRequest()
1263 1266 pull_request.source_repo = source
1264 1267
1265 1268 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1266 1269 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1267 1270 pull_request.target_repo = target
1268 1271 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1269 1272 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1270 1273 pull_request.revisions = [commit_ids['change']]
1271 1274 pull_request.title = u"Test"
1272 1275 pull_request.description = u"Description"
1273 1276 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1274 1277 pull_request.pull_request_state = PullRequest.STATE_CREATED
1275 1278 Session().add(pull_request)
1276 1279 Session().commit()
1277 1280 pull_request_id = pull_request.pull_request_id
1278 1281
1279 1282 # target has ancestor - ancestor-new
1280 1283 # source has ancestor - ancestor-new - change-rebased
1281 1284 backend.pull_heads(target, heads=['ancestor-new'])
1282 1285 backend.pull_heads(source, heads=['change-rebased'])
1286 target_repo_name = target.repo_name
1283 1287
1284 1288 # update PR
1285 1289 self.app.post(
1286 1290 route_path('pullrequest_update',
1287 repo_name=target.repo_name, pull_request_id=pull_request_id),
1291 repo_name=target_repo_name, pull_request_id=pull_request_id),
1288 1292 params={'update_commits': 'true', 'csrf_token': csrf_token},
1289 1293 status=200)
1290 1294
1291 1295 # Expect the target reference to be updated correctly
1292 1296 pull_request = PullRequest.get(pull_request_id)
1293 1297 assert pull_request.revisions == [commit_ids['change-rebased']]
1294 1298 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
1295 1299 branch=backend.default_branch_name,
1296 1300 commit_id=commit_ids['ancestor-new'])
1297 1301 assert pull_request.target_ref == expected_target_ref
1298 1302
1299 1303 def test_remove_pull_request_branch(self, backend_git, csrf_token):
1300 1304 branch_name = 'development'
1301 1305 commits = [
1302 1306 {'message': 'initial-commit'},
1303 1307 {'message': 'old-feature'},
1304 1308 {'message': 'new-feature', 'branch': branch_name},
1305 1309 ]
1306 1310 repo = backend_git.create_repo(commits)
1307 1311 repo_name = repo.repo_name
1308 1312 commit_ids = backend_git.commit_ids
1309 1313
1310 1314 pull_request = PullRequest()
1311 1315 pull_request.source_repo = repo
1312 1316 pull_request.target_repo = repo
1313 1317 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1314 1318 branch=branch_name, commit_id=commit_ids['new-feature'])
1315 1319 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1316 1320 branch=backend_git.default_branch_name, commit_id=commit_ids['old-feature'])
1317 1321 pull_request.revisions = [commit_ids['new-feature']]
1318 1322 pull_request.title = u"Test"
1319 1323 pull_request.description = u"Description"
1320 1324 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1321 1325 pull_request.pull_request_state = PullRequest.STATE_CREATED
1322 1326 Session().add(pull_request)
1323 1327 Session().commit()
1324 1328
1325 1329 pull_request_id = pull_request.pull_request_id
1326 1330
1327 1331 vcs = repo.scm_instance()
1328 1332 vcs.remove_ref('refs/heads/{}'.format(branch_name))
1329 1333 # NOTE(marcink): run GC to ensure the commits are gone
1330 1334 vcs.run_gc()
1331 1335
1332 1336 response = self.app.get(route_path(
1333 1337 'pullrequest_show',
1334 1338 repo_name=repo_name,
1335 1339 pull_request_id=pull_request_id))
1336 1340
1337 1341 assert response.status_int == 200
1338 1342
1339 1343 response.assert_response().element_contains(
1340 1344 '#changeset_compare_view_content .alert strong',
1341 1345 'Missing commits')
1342 1346 response.assert_response().element_contains(
1343 1347 '#changeset_compare_view_content .alert',
1344 1348 'This pull request cannot be displayed, because one or more'
1345 1349 ' commits no longer exist in the source repository.')
1346 1350
1347 1351 def test_strip_commits_from_pull_request(
1348 1352 self, backend, pr_util, csrf_token):
1349 1353 commits = [
1350 1354 {'message': 'initial-commit'},
1351 1355 {'message': 'old-feature'},
1352 1356 {'message': 'new-feature', 'parents': ['initial-commit']},
1353 1357 ]
1354 1358 pull_request = pr_util.create_pull_request(
1355 1359 commits, target_head='initial-commit', source_head='new-feature',
1356 1360 revisions=['new-feature'])
1357 1361
1358 1362 vcs = pr_util.source_repository.scm_instance()
1359 1363 if backend.alias == 'git':
1360 1364 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
1361 1365 else:
1362 1366 vcs.strip(pr_util.commit_ids['new-feature'])
1363 1367
1364 1368 response = self.app.get(route_path(
1365 1369 'pullrequest_show',
1366 1370 repo_name=pr_util.target_repository.repo_name,
1367 1371 pull_request_id=pull_request.pull_request_id))
1368 1372
1369 1373 assert response.status_int == 200
1370 1374
1371 1375 response.assert_response().element_contains(
1372 1376 '#changeset_compare_view_content .alert strong',
1373 1377 'Missing commits')
1374 1378 response.assert_response().element_contains(
1375 1379 '#changeset_compare_view_content .alert',
1376 1380 'This pull request cannot be displayed, because one or more'
1377 1381 ' commits no longer exist in the source repository.')
1378 1382 response.assert_response().element_contains(
1379 1383 '#update_commits',
1380 1384 'Update commits')
1381 1385
1382 1386 def test_strip_commits_and_update(
1383 1387 self, backend, pr_util, csrf_token):
1384 1388 commits = [
1385 1389 {'message': 'initial-commit'},
1386 1390 {'message': 'old-feature'},
1387 1391 {'message': 'new-feature', 'parents': ['old-feature']},
1388 1392 ]
1389 1393 pull_request = pr_util.create_pull_request(
1390 1394 commits, target_head='old-feature', source_head='new-feature',
1391 1395 revisions=['new-feature'], mergeable=True)
1396 pr_id = pull_request.pull_request_id
1397 target_repo_name = pull_request.target_repo.repo_name
1392 1398
1393 1399 vcs = pr_util.source_repository.scm_instance()
1394 1400 if backend.alias == 'git':
1395 1401 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
1396 1402 else:
1397 1403 vcs.strip(pr_util.commit_ids['new-feature'])
1398 1404
1399 1405 url = route_path('pullrequest_update',
1400 repo_name=pull_request.target_repo.repo_name,
1401 pull_request_id=pull_request.pull_request_id)
1406 repo_name=target_repo_name,
1407 pull_request_id=pr_id)
1402 1408 response = self.app.post(url,
1403 1409 params={'update_commits': 'true',
1404 1410 'csrf_token': csrf_token})
1405 1411
1406 1412 assert response.status_int == 200
1407 1413 assert response.body == '{"response": true, "redirect_url": null}'
1408 1414
1409 1415 # Make sure that after update, it won't raise 500 errors
1410 1416 response = self.app.get(route_path(
1411 1417 'pullrequest_show',
1412 repo_name=pr_util.target_repository.repo_name,
1413 pull_request_id=pull_request.pull_request_id))
1418 repo_name=target_repo_name,
1419 pull_request_id=pr_id))
1414 1420
1415 1421 assert response.status_int == 200
1416 1422 response.assert_response().element_contains(
1417 1423 '#changeset_compare_view_content .alert strong',
1418 1424 'Missing commits')
1419 1425
1420 1426 def test_branch_is_a_link(self, pr_util):
1421 1427 pull_request = pr_util.create_pull_request()
1422 1428 pull_request.source_ref = 'branch:origin:1234567890abcdef'
1423 1429 pull_request.target_ref = 'branch:target:abcdef1234567890'
1424 1430 Session().add(pull_request)
1425 1431 Session().commit()
1426 1432
1427 1433 response = self.app.get(route_path(
1428 1434 'pullrequest_show',
1429 1435 repo_name=pull_request.target_repo.scm_instance().name,
1430 1436 pull_request_id=pull_request.pull_request_id))
1431 1437 assert response.status_int == 200
1432 1438
1433 1439 source = response.assert_response().get_element('.pr-source-info')
1434 1440 source_parent = source.getparent()
1435 1441 assert len(source_parent) == 1
1436 1442
1437 1443 target = response.assert_response().get_element('.pr-target-info')
1438 1444 target_parent = target.getparent()
1439 1445 assert len(target_parent) == 1
1440 1446
1441 1447 expected_origin_link = route_path(
1442 1448 'repo_commits',
1443 1449 repo_name=pull_request.source_repo.scm_instance().name,
1444 1450 params=dict(branch='origin'))
1445 1451 expected_target_link = route_path(
1446 1452 'repo_commits',
1447 1453 repo_name=pull_request.target_repo.scm_instance().name,
1448 1454 params=dict(branch='target'))
1449 1455 assert source_parent.attrib['href'] == expected_origin_link
1450 1456 assert target_parent.attrib['href'] == expected_target_link
1451 1457
1452 1458 def test_bookmark_is_not_a_link(self, pr_util):
1453 1459 pull_request = pr_util.create_pull_request()
1454 1460 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1455 1461 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1456 1462 Session().add(pull_request)
1457 1463 Session().commit()
1458 1464
1459 1465 response = self.app.get(route_path(
1460 1466 'pullrequest_show',
1461 1467 repo_name=pull_request.target_repo.scm_instance().name,
1462 1468 pull_request_id=pull_request.pull_request_id))
1463 1469 assert response.status_int == 200
1464 1470
1465 1471 source = response.assert_response().get_element('.pr-source-info')
1466 1472 assert source.text.strip() == 'bookmark:origin'
1467 1473 assert source.getparent().attrib.get('href') is None
1468 1474
1469 1475 target = response.assert_response().get_element('.pr-target-info')
1470 1476 assert target.text.strip() == 'bookmark:target'
1471 1477 assert target.getparent().attrib.get('href') is None
1472 1478
1473 1479 def test_tag_is_not_a_link(self, pr_util):
1474 1480 pull_request = pr_util.create_pull_request()
1475 1481 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1476 1482 pull_request.target_ref = 'tag:target:abcdef1234567890'
1477 1483 Session().add(pull_request)
1478 1484 Session().commit()
1479 1485
1480 1486 response = self.app.get(route_path(
1481 1487 'pullrequest_show',
1482 1488 repo_name=pull_request.target_repo.scm_instance().name,
1483 1489 pull_request_id=pull_request.pull_request_id))
1484 1490 assert response.status_int == 200
1485 1491
1486 1492 source = response.assert_response().get_element('.pr-source-info')
1487 1493 assert source.text.strip() == 'tag:origin'
1488 1494 assert source.getparent().attrib.get('href') is None
1489 1495
1490 1496 target = response.assert_response().get_element('.pr-target-info')
1491 1497 assert target.text.strip() == 'tag:target'
1492 1498 assert target.getparent().attrib.get('href') is None
1493 1499
1494 1500 @pytest.mark.parametrize('mergeable', [True, False])
1495 1501 def test_shadow_repository_link(
1496 1502 self, mergeable, pr_util, http_host_only_stub):
1497 1503 """
1498 1504 Check that the pull request summary page displays a link to the shadow
1499 1505 repository if the pull request is mergeable. If it is not mergeable
1500 1506 the link should not be displayed.
1501 1507 """
1502 1508 pull_request = pr_util.create_pull_request(
1503 1509 mergeable=mergeable, enable_notifications=False)
1504 1510 target_repo = pull_request.target_repo.scm_instance()
1505 1511 pr_id = pull_request.pull_request_id
1506 1512 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1507 1513 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1508 1514
1509 1515 response = self.app.get(route_path(
1510 1516 'pullrequest_show',
1511 1517 repo_name=target_repo.name,
1512 1518 pull_request_id=pr_id))
1513 1519
1514 1520 if mergeable:
1515 1521 response.assert_response().element_value_contains(
1516 1522 'input.pr-mergeinfo', shadow_url)
1517 1523 response.assert_response().element_value_contains(
1518 1524 'input.pr-mergeinfo ', 'pr-merge')
1519 1525 else:
1520 1526 response.assert_response().no_element_exists('.pr-mergeinfo')
1521 1527
1522 1528
1523 1529 @pytest.mark.usefixtures('app')
1524 1530 @pytest.mark.backends("git", "hg")
1525 1531 class TestPullrequestsControllerDelete(object):
1526 1532 def test_pull_request_delete_button_permissions_admin(
1527 1533 self, autologin_user, user_admin, pr_util):
1528 1534 pull_request = pr_util.create_pull_request(
1529 1535 author=user_admin.username, enable_notifications=False)
1530 1536
1531 1537 response = self.app.get(route_path(
1532 1538 'pullrequest_show',
1533 1539 repo_name=pull_request.target_repo.scm_instance().name,
1534 1540 pull_request_id=pull_request.pull_request_id))
1535 1541
1536 1542 response.mustcontain('id="delete_pullrequest"')
1537 1543 response.mustcontain('Confirm to delete this pull request')
1538 1544
1539 1545 def test_pull_request_delete_button_permissions_owner(
1540 1546 self, autologin_regular_user, user_regular, pr_util):
1541 1547 pull_request = pr_util.create_pull_request(
1542 1548 author=user_regular.username, enable_notifications=False)
1543 1549
1544 1550 response = self.app.get(route_path(
1545 1551 'pullrequest_show',
1546 1552 repo_name=pull_request.target_repo.scm_instance().name,
1547 1553 pull_request_id=pull_request.pull_request_id))
1548 1554
1549 1555 response.mustcontain('id="delete_pullrequest"')
1550 1556 response.mustcontain('Confirm to delete this pull request')
1551 1557
1552 1558 def test_pull_request_delete_button_permissions_forbidden(
1553 1559 self, autologin_regular_user, user_regular, user_admin, pr_util):
1554 1560 pull_request = pr_util.create_pull_request(
1555 1561 author=user_admin.username, enable_notifications=False)
1556 1562
1557 1563 response = self.app.get(route_path(
1558 1564 'pullrequest_show',
1559 1565 repo_name=pull_request.target_repo.scm_instance().name,
1560 1566 pull_request_id=pull_request.pull_request_id))
1561 1567 response.mustcontain(no=['id="delete_pullrequest"'])
1562 1568 response.mustcontain(no=['Confirm to delete this pull request'])
1563 1569
1564 1570 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1565 1571 self, autologin_regular_user, user_regular, user_admin, pr_util,
1566 1572 user_util):
1567 1573
1568 1574 pull_request = pr_util.create_pull_request(
1569 1575 author=user_admin.username, enable_notifications=False)
1570 1576
1571 1577 user_util.grant_user_permission_to_repo(
1572 1578 pull_request.target_repo, user_regular,
1573 1579 'repository.write')
1574 1580
1575 1581 response = self.app.get(route_path(
1576 1582 'pullrequest_show',
1577 1583 repo_name=pull_request.target_repo.scm_instance().name,
1578 1584 pull_request_id=pull_request.pull_request_id))
1579 1585
1580 1586 response.mustcontain('id="open_edit_pullrequest"')
1581 1587 response.mustcontain('id="delete_pullrequest"')
1582 1588 response.mustcontain(no=['Confirm to delete this pull request'])
1583 1589
1584 1590 def test_delete_comment_returns_404_if_comment_does_not_exist(
1585 1591 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1586 1592
1587 1593 pull_request = pr_util.create_pull_request(
1588 1594 author=user_admin.username, enable_notifications=False)
1589 1595
1590 1596 self.app.post(
1591 1597 route_path(
1592 1598 'pullrequest_comment_delete',
1593 1599 repo_name=pull_request.target_repo.scm_instance().name,
1594 1600 pull_request_id=pull_request.pull_request_id,
1595 1601 comment_id=1024404),
1596 1602 extra_environ=xhr_header,
1597 1603 params={'csrf_token': csrf_token},
1598 1604 status=404
1599 1605 )
1600 1606
1601 1607 def test_delete_comment(
1602 1608 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1603 1609
1604 1610 pull_request = pr_util.create_pull_request(
1605 1611 author=user_admin.username, enable_notifications=False)
1606 1612 comment = pr_util.create_comment()
1607 1613 comment_id = comment.comment_id
1608 1614
1609 1615 response = self.app.post(
1610 1616 route_path(
1611 1617 'pullrequest_comment_delete',
1612 1618 repo_name=pull_request.target_repo.scm_instance().name,
1613 1619 pull_request_id=pull_request.pull_request_id,
1614 1620 comment_id=comment_id),
1615 1621 extra_environ=xhr_header,
1616 1622 params={'csrf_token': csrf_token},
1617 1623 status=200
1618 1624 )
1619 1625 assert response.body == 'true'
1620 1626
1621 1627 @pytest.mark.parametrize('url_type', [
1622 1628 'pullrequest_new',
1623 1629 'pullrequest_create',
1624 1630 'pullrequest_update',
1625 1631 'pullrequest_merge',
1626 1632 ])
1627 1633 def test_pull_request_is_forbidden_on_archived_repo(
1628 1634 self, autologin_user, backend, xhr_header, user_util, url_type):
1629 1635
1630 1636 # create a temporary repo
1631 1637 source = user_util.create_repo(repo_type=backend.alias)
1632 1638 repo_name = source.repo_name
1633 1639 repo = Repository.get_by_repo_name(repo_name)
1634 1640 repo.archived = True
1635 1641 Session().commit()
1636 1642
1637 1643 response = self.app.get(
1638 1644 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302)
1639 1645
1640 1646 msg = 'Action not supported for archived repository.'
1641 1647 assert_session_flash(response, msg)
1642 1648
1643 1649
1644 1650 def assert_pull_request_status(pull_request, expected_status):
1645 1651 status = ChangesetStatusModel().calculated_review_status(pull_request=pull_request)
1646 1652 assert status == expected_status
1647 1653
1648 1654
1649 1655 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1650 1656 @pytest.mark.usefixtures("autologin_user")
1651 1657 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1652 1658 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