##// END OF EJS Templates
tests: Use merge reference object instead of raw commit id.
Martin Bornhold -
r1058:c3c86c38 default
parent child Browse files
Show More
@@ -1,840 +1,841 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 textwrap
24 24
25 25 import rhodecode
26 26 from rhodecode.lib.utils2 import safe_unicode
27 27 from rhodecode.lib.vcs.backends import get_backend
28 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
28 from rhodecode.lib.vcs.backends.base import (
29 MergeResponse, MergeFailureReason, Reference)
29 30 from rhodecode.lib.vcs.exceptions import RepositoryError
30 31 from rhodecode.lib.vcs.nodes import FileNode
31 32 from rhodecode.model.comment import ChangesetCommentsModel
32 33 from rhodecode.model.db import PullRequest, Session
33 34 from rhodecode.model.pull_request import PullRequestModel
34 35 from rhodecode.model.user import UserModel
35 36 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
36 37
37 38
38 39 pytestmark = [
39 40 pytest.mark.backends("git", "hg"),
40 41 ]
41 42
42 43
43 44 class TestPullRequestModel:
44 45
45 46 @pytest.fixture
46 47 def pull_request(self, request, backend, pr_util):
47 48 """
48 49 A pull request combined with multiples patches.
49 50 """
50 51 BackendClass = get_backend(backend.alias)
51 52 self.merge_patcher = mock.patch.object(BackendClass, 'merge')
52 53 self.workspace_remove_patcher = mock.patch.object(
53 54 BackendClass, 'cleanup_merge_workspace')
54 55
55 56 self.workspace_remove_mock = self.workspace_remove_patcher.start()
56 57 self.merge_mock = self.merge_patcher.start()
57 58 self.comment_patcher = mock.patch(
58 59 'rhodecode.model.changeset_status.ChangesetStatusModel.set_status')
59 60 self.comment_patcher.start()
60 61 self.notification_patcher = mock.patch(
61 62 'rhodecode.model.notification.NotificationModel.create')
62 63 self.notification_patcher.start()
63 64 self.helper_patcher = mock.patch(
64 65 'rhodecode.lib.helpers.url')
65 66 self.helper_patcher.start()
66 67
67 68 self.hook_patcher = mock.patch.object(PullRequestModel,
68 69 '_trigger_pull_request_hook')
69 70 self.hook_mock = self.hook_patcher.start()
70 71
71 72 self.invalidation_patcher = mock.patch(
72 73 'rhodecode.model.pull_request.ScmModel.mark_for_invalidation')
73 74 self.invalidation_mock = self.invalidation_patcher.start()
74 75
75 76 self.pull_request = pr_util.create_pull_request(
76 77 mergeable=True, name_suffix=u'Δ…Δ‡')
77 78 self.source_commit = self.pull_request.source_ref_parts.commit_id
78 79 self.target_commit = self.pull_request.target_ref_parts.commit_id
79 80 self.workspace_id = 'pr-%s' % self.pull_request.pull_request_id
80 81
81 82 @request.addfinalizer
82 83 def cleanup_pull_request():
83 84 calls = [mock.call(
84 85 self.pull_request, self.pull_request.author, 'create')]
85 86 self.hook_mock.assert_has_calls(calls)
86 87
87 88 self.workspace_remove_patcher.stop()
88 89 self.merge_patcher.stop()
89 90 self.comment_patcher.stop()
90 91 self.notification_patcher.stop()
91 92 self.helper_patcher.stop()
92 93 self.hook_patcher.stop()
93 94 self.invalidation_patcher.stop()
94 95
95 96 return self.pull_request
96 97
97 98 def test_get_all(self, pull_request):
98 99 prs = PullRequestModel().get_all(pull_request.target_repo)
99 100 assert isinstance(prs, list)
100 101 assert len(prs) == 1
101 102
102 103 def test_count_all(self, pull_request):
103 104 pr_count = PullRequestModel().count_all(pull_request.target_repo)
104 105 assert pr_count == 1
105 106
106 107 def test_get_awaiting_review(self, pull_request):
107 108 prs = PullRequestModel().get_awaiting_review(pull_request.target_repo)
108 109 assert isinstance(prs, list)
109 110 assert len(prs) == 1
110 111
111 112 def test_count_awaiting_review(self, pull_request):
112 113 pr_count = PullRequestModel().count_awaiting_review(
113 114 pull_request.target_repo)
114 115 assert pr_count == 1
115 116
116 117 def test_get_awaiting_my_review(self, pull_request):
117 118 PullRequestModel().update_reviewers(
118 119 pull_request, [(pull_request.author, ['author'])])
119 120 prs = PullRequestModel().get_awaiting_my_review(
120 121 pull_request.target_repo, user_id=pull_request.author.user_id)
121 122 assert isinstance(prs, list)
122 123 assert len(prs) == 1
123 124
124 125 def test_count_awaiting_my_review(self, pull_request):
125 126 PullRequestModel().update_reviewers(
126 127 pull_request, [(pull_request.author, ['author'])])
127 128 pr_count = PullRequestModel().count_awaiting_my_review(
128 129 pull_request.target_repo, user_id=pull_request.author.user_id)
129 130 assert pr_count == 1
130 131
131 132 def test_delete_calls_cleanup_merge(self, pull_request):
132 133 PullRequestModel().delete(pull_request)
133 134
134 135 self.workspace_remove_mock.assert_called_once_with(
135 136 self.workspace_id)
136 137
137 138 def test_close_calls_cleanup_and_hook(self, pull_request):
138 139 PullRequestModel().close_pull_request(
139 140 pull_request, pull_request.author)
140 141
141 142 self.workspace_remove_mock.assert_called_once_with(
142 143 self.workspace_id)
143 144 self.hook_mock.assert_called_with(
144 145 self.pull_request, self.pull_request.author, 'close')
145 146
146 147 def test_merge_status(self, pull_request):
147 148 self.merge_mock.return_value = MergeResponse(
148 149 True, False, None, MergeFailureReason.NONE)
149 150
150 151 assert pull_request._last_merge_source_rev is None
151 152 assert pull_request._last_merge_target_rev is None
152 153 assert pull_request._last_merge_status is None
153 154
154 155 status, msg = PullRequestModel().merge_status(pull_request)
155 156 assert status is True
156 157 assert msg.eval() == 'This pull request can be automatically merged.'
157 158 self.merge_mock.assert_called_once_with(
158 159 pull_request.target_ref_parts,
159 160 pull_request.source_repo.scm_instance(),
160 161 pull_request.source_ref_parts, self.workspace_id, dry_run=True,
161 162 use_rebase=False)
162 163
163 164 assert pull_request._last_merge_source_rev == self.source_commit
164 165 assert pull_request._last_merge_target_rev == self.target_commit
165 166 assert pull_request._last_merge_status is MergeFailureReason.NONE
166 167
167 168 self.merge_mock.reset_mock()
168 169 status, msg = PullRequestModel().merge_status(pull_request)
169 170 assert status is True
170 171 assert msg.eval() == 'This pull request can be automatically merged.'
171 172 assert self.merge_mock.called is False
172 173
173 174 def test_merge_status_known_failure(self, pull_request):
174 175 self.merge_mock.return_value = MergeResponse(
175 176 False, False, None, MergeFailureReason.MERGE_FAILED)
176 177
177 178 assert pull_request._last_merge_source_rev is None
178 179 assert pull_request._last_merge_target_rev is None
179 180 assert pull_request._last_merge_status is None
180 181
181 182 status, msg = PullRequestModel().merge_status(pull_request)
182 183 assert status is False
183 184 assert (
184 185 msg.eval() ==
185 186 'This pull request cannot be merged because of conflicts.')
186 187 self.merge_mock.assert_called_once_with(
187 188 pull_request.target_ref_parts,
188 189 pull_request.source_repo.scm_instance(),
189 190 pull_request.source_ref_parts, self.workspace_id, dry_run=True,
190 191 use_rebase=False)
191 192
192 193 assert pull_request._last_merge_source_rev == self.source_commit
193 194 assert pull_request._last_merge_target_rev == self.target_commit
194 195 assert (
195 196 pull_request._last_merge_status is MergeFailureReason.MERGE_FAILED)
196 197
197 198 self.merge_mock.reset_mock()
198 199 status, msg = PullRequestModel().merge_status(pull_request)
199 200 assert status is False
200 201 assert (
201 202 msg.eval() ==
202 203 'This pull request cannot be merged because of conflicts.')
203 204 assert self.merge_mock.called is False
204 205
205 206 def test_merge_status_unknown_failure(self, pull_request):
206 207 self.merge_mock.return_value = MergeResponse(
207 208 False, False, None, MergeFailureReason.UNKNOWN)
208 209
209 210 assert pull_request._last_merge_source_rev is None
210 211 assert pull_request._last_merge_target_rev is None
211 212 assert pull_request._last_merge_status is None
212 213
213 214 status, msg = PullRequestModel().merge_status(pull_request)
214 215 assert status is False
215 216 assert msg.eval() == (
216 217 'This pull request cannot be merged because of an unhandled'
217 218 ' exception.')
218 219 self.merge_mock.assert_called_once_with(
219 220 pull_request.target_ref_parts,
220 221 pull_request.source_repo.scm_instance(),
221 222 pull_request.source_ref_parts, self.workspace_id, dry_run=True,
222 223 use_rebase=False)
223 224
224 225 assert pull_request._last_merge_source_rev is None
225 226 assert pull_request._last_merge_target_rev is None
226 227 assert pull_request._last_merge_status is None
227 228
228 229 self.merge_mock.reset_mock()
229 230 status, msg = PullRequestModel().merge_status(pull_request)
230 231 assert status is False
231 232 assert msg.eval() == (
232 233 'This pull request cannot be merged because of an unhandled'
233 234 ' exception.')
234 235 assert self.merge_mock.called is True
235 236
236 237 def test_merge_status_when_target_is_locked(self, pull_request):
237 238 pull_request.target_repo.locked = [1, u'12345.50', 'lock_web']
238 239 status, msg = PullRequestModel().merge_status(pull_request)
239 240 assert status is False
240 241 assert msg.eval() == (
241 242 'This pull request cannot be merged because the target repository'
242 243 ' is locked.')
243 244
244 245 def test_merge_status_requirements_check_target(self, pull_request):
245 246
246 247 def has_largefiles(self, repo):
247 248 return repo == pull_request.source_repo
248 249
249 250 patcher = mock.patch.object(
250 251 PullRequestModel, '_has_largefiles', has_largefiles)
251 252 with patcher:
252 253 status, msg = PullRequestModel().merge_status(pull_request)
253 254
254 255 assert status is False
255 256 assert msg == 'Target repository large files support is disabled.'
256 257
257 258 def test_merge_status_requirements_check_source(self, pull_request):
258 259
259 260 def has_largefiles(self, repo):
260 261 return repo == pull_request.target_repo
261 262
262 263 patcher = mock.patch.object(
263 264 PullRequestModel, '_has_largefiles', has_largefiles)
264 265 with patcher:
265 266 status, msg = PullRequestModel().merge_status(pull_request)
266 267
267 268 assert status is False
268 269 assert msg == 'Source repository large files support is disabled.'
269 270
270 271 def test_merge(self, pull_request, merge_extras):
271 272 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
273 merge_ref = Reference(
274 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
272 275 self.merge_mock.return_value = MergeResponse(
273 True, True,
274 '6126b7bfcc82ad2d3deaee22af926b082ce54cc6',
275 MergeFailureReason.NONE)
276 True, True, merge_ref, MergeFailureReason.NONE)
276 277
277 278 merge_extras['repository'] = pull_request.target_repo.repo_name
278 279 PullRequestModel().merge(
279 280 pull_request, pull_request.author, extras=merge_extras)
280 281
281 282 message = (
282 283 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
283 284 u'\n\n {pr_title}'.format(
284 285 pr_id=pull_request.pull_request_id,
285 286 source_repo=safe_unicode(
286 287 pull_request.source_repo.scm_instance().name),
287 288 source_ref_name=pull_request.source_ref_parts.name,
288 289 pr_title=safe_unicode(pull_request.title)
289 290 )
290 291 )
291 292 self.merge_mock.assert_called_once_with(
292 293 pull_request.target_ref_parts,
293 294 pull_request.source_repo.scm_instance(),
294 295 pull_request.source_ref_parts, self.workspace_id,
295 296 user_name=user.username, user_email=user.email, message=message,
296 297 use_rebase=False
297 298 )
298 299 self.invalidation_mock.assert_called_once_with(
299 300 pull_request.target_repo.repo_name)
300 301
301 302 self.hook_mock.assert_called_with(
302 303 self.pull_request, self.pull_request.author, 'merge')
303 304
304 305 pull_request = PullRequest.get(pull_request.pull_request_id)
305 306 assert (
306 307 pull_request.merge_rev ==
307 308 '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
308 309
309 310 def test_merge_failed(self, pull_request, merge_extras):
310 311 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
312 merge_ref = Reference(
313 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
311 314 self.merge_mock.return_value = MergeResponse(
312 False, False,
313 '6126b7bfcc82ad2d3deaee22af926b082ce54cc6',
314 MergeFailureReason.MERGE_FAILED)
315 False, False, merge_ref, MergeFailureReason.MERGE_FAILED)
315 316
316 317 merge_extras['repository'] = pull_request.target_repo.repo_name
317 318 PullRequestModel().merge(
318 319 pull_request, pull_request.author, extras=merge_extras)
319 320
320 321 message = (
321 322 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
322 323 u'\n\n {pr_title}'.format(
323 324 pr_id=pull_request.pull_request_id,
324 325 source_repo=safe_unicode(
325 326 pull_request.source_repo.scm_instance().name),
326 327 source_ref_name=pull_request.source_ref_parts.name,
327 328 pr_title=safe_unicode(pull_request.title)
328 329 )
329 330 )
330 331 self.merge_mock.assert_called_once_with(
331 332 pull_request.target_ref_parts,
332 333 pull_request.source_repo.scm_instance(),
333 334 pull_request.source_ref_parts, self.workspace_id,
334 335 user_name=user.username, user_email=user.email, message=message,
335 336 use_rebase=False
336 337 )
337 338
338 339 pull_request = PullRequest.get(pull_request.pull_request_id)
339 340 assert self.invalidation_mock.called is False
340 341 assert pull_request.merge_rev is None
341 342
342 343 def test_get_commit_ids(self, pull_request):
343 344 # The PR has been not merget yet, so expect an exception
344 345 with pytest.raises(ValueError):
345 346 PullRequestModel()._get_commit_ids(pull_request)
346 347
347 348 # Merge revision is in the revisions list
348 349 pull_request.merge_rev = pull_request.revisions[0]
349 350 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
350 351 assert commit_ids == pull_request.revisions
351 352
352 353 # Merge revision is not in the revisions list
353 354 pull_request.merge_rev = 'f000' * 10
354 355 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
355 356 assert commit_ids == pull_request.revisions + [pull_request.merge_rev]
356 357
357 358 def test_get_diff_from_pr_version(self, pull_request):
358 359 diff = PullRequestModel()._get_diff_from_pr_or_version(
359 360 pull_request, context=6)
360 361 assert 'file_1' in diff.raw
361 362
362 363 def test_generate_title_returns_unicode(self):
363 364 title = PullRequestModel().generate_pullrequest_title(
364 365 source='source-dummy',
365 366 source_ref='source-ref-dummy',
366 367 target='target-dummy',
367 368 )
368 369 assert type(title) == unicode
369 370
370 371
371 372 class TestIntegrationMerge(object):
372 373 @pytest.mark.parametrize('extra_config', (
373 374 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
374 375 {'vcs.hooks.protocol': 'Pyro4', 'vcs.hooks.direct_calls': False},
375 376 ))
376 377 def test_merge_triggers_push_hooks(
377 378 self, pr_util, user_admin, capture_rcextensions, merge_extras,
378 379 extra_config):
379 380 pull_request = pr_util.create_pull_request(
380 381 approved=True, mergeable=True)
381 382 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
382 383 merge_extras['repository'] = pull_request.target_repo.repo_name
383 384 Session().commit()
384 385
385 386 with mock.patch.dict(rhodecode.CONFIG, extra_config, clear=False):
386 387 merge_state = PullRequestModel().merge(
387 388 pull_request, user_admin, extras=merge_extras)
388 389
389 390 assert merge_state.executed
390 391 assert 'pre_push' in capture_rcextensions
391 392 assert 'post_push' in capture_rcextensions
392 393
393 394 def test_merge_can_be_rejected_by_pre_push_hook(
394 395 self, pr_util, user_admin, capture_rcextensions, merge_extras):
395 396 pull_request = pr_util.create_pull_request(
396 397 approved=True, mergeable=True)
397 398 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
398 399 merge_extras['repository'] = pull_request.target_repo.repo_name
399 400 Session().commit()
400 401
401 402 with mock.patch('rhodecode.EXTENSIONS.PRE_PUSH_HOOK') as pre_pull:
402 403 pre_pull.side_effect = RepositoryError("Disallow push!")
403 404 merge_status = PullRequestModel().merge(
404 405 pull_request, user_admin, extras=merge_extras)
405 406
406 407 assert not merge_status.executed
407 408 assert 'pre_push' not in capture_rcextensions
408 409 assert 'post_push' not in capture_rcextensions
409 410
410 411 def test_merge_fails_if_target_is_locked(
411 412 self, pr_util, user_regular, merge_extras):
412 413 pull_request = pr_util.create_pull_request(
413 414 approved=True, mergeable=True)
414 415 locked_by = [user_regular.user_id + 1, 12345.50, 'lock_web']
415 416 pull_request.target_repo.locked = locked_by
416 417 # TODO: johbo: Check if this can work based on the database, currently
417 418 # all data is pre-computed, that's why just updating the DB is not
418 419 # enough.
419 420 merge_extras['locked_by'] = locked_by
420 421 merge_extras['repository'] = pull_request.target_repo.repo_name
421 422 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
422 423 Session().commit()
423 424 merge_status = PullRequestModel().merge(
424 425 pull_request, user_regular, extras=merge_extras)
425 426 assert not merge_status.executed
426 427
427 428
428 429 @pytest.mark.parametrize('use_outdated, inlines_count, outdated_count', [
429 430 (False, 1, 0),
430 431 (True, 0, 1),
431 432 ])
432 433 def test_outdated_comments(
433 434 pr_util, use_outdated, inlines_count, outdated_count):
434 435 pull_request = pr_util.create_pull_request()
435 436 pr_util.create_inline_comment(file_path='not_in_updated_diff')
436 437
437 438 with outdated_comments_patcher(use_outdated) as outdated_comment_mock:
438 439 pr_util.add_one_commit()
439 440 assert_inline_comments(
440 441 pull_request, visible=inlines_count, outdated=outdated_count)
441 442 outdated_comment_mock.assert_called_with(pull_request)
442 443
443 444
444 445 @pytest.fixture
445 446 def merge_extras(user_regular):
446 447 """
447 448 Context for the vcs operation when running a merge.
448 449 """
449 450 extras = {
450 451 'ip': '127.0.0.1',
451 452 'username': user_regular.username,
452 453 'action': 'push',
453 454 'repository': 'fake_target_repo_name',
454 455 'scm': 'git',
455 456 'config': 'fake_config_ini_path',
456 457 'make_lock': None,
457 458 'locked_by': [None, None, None],
458 459 'server_url': 'http://test.example.com:5000',
459 460 'hooks': ['push', 'pull'],
460 461 'is_shadow_repo': False,
461 462 }
462 463 return extras
463 464
464 465
465 466 class TestUpdateCommentHandling(object):
466 467
467 468 @pytest.fixture(autouse=True, scope='class')
468 469 def enable_outdated_comments(self, request, pylonsapp):
469 470 config_patch = mock.patch.dict(
470 471 'rhodecode.CONFIG', {'rhodecode_use_outdated_comments': True})
471 472 config_patch.start()
472 473
473 474 @request.addfinalizer
474 475 def cleanup():
475 476 config_patch.stop()
476 477
477 478 def test_comment_stays_unflagged_on_unchanged_diff(self, pr_util):
478 479 commits = [
479 480 {'message': 'a'},
480 481 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
481 482 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
482 483 ]
483 484 pull_request = pr_util.create_pull_request(
484 485 commits=commits, target_head='a', source_head='b', revisions=['b'])
485 486 pr_util.create_inline_comment(file_path='file_b')
486 487 pr_util.add_one_commit(head='c')
487 488
488 489 assert_inline_comments(pull_request, visible=1, outdated=0)
489 490
490 491 def test_comment_stays_unflagged_on_change_above(self, pr_util):
491 492 original_content = ''.join(
492 493 ['line {}\n'.format(x) for x in range(1, 11)])
493 494 updated_content = 'new_line_at_top\n' + original_content
494 495 commits = [
495 496 {'message': 'a'},
496 497 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
497 498 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
498 499 ]
499 500 pull_request = pr_util.create_pull_request(
500 501 commits=commits, target_head='a', source_head='b', revisions=['b'])
501 502
502 503 with outdated_comments_patcher():
503 504 comment = pr_util.create_inline_comment(
504 505 line_no=u'n8', file_path='file_b')
505 506 pr_util.add_one_commit(head='c')
506 507
507 508 assert_inline_comments(pull_request, visible=1, outdated=0)
508 509 assert comment.line_no == u'n9'
509 510
510 511 def test_comment_stays_unflagged_on_change_below(self, pr_util):
511 512 original_content = ''.join(['line {}\n'.format(x) for x in range(10)])
512 513 updated_content = original_content + 'new_line_at_end\n'
513 514 commits = [
514 515 {'message': 'a'},
515 516 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
516 517 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
517 518 ]
518 519 pull_request = pr_util.create_pull_request(
519 520 commits=commits, target_head='a', source_head='b', revisions=['b'])
520 521 pr_util.create_inline_comment(file_path='file_b')
521 522 pr_util.add_one_commit(head='c')
522 523
523 524 assert_inline_comments(pull_request, visible=1, outdated=0)
524 525
525 526 @pytest.mark.parametrize('line_no', ['n4', 'o4', 'n10', 'o9'])
526 527 def test_comment_flagged_on_change_around_context(self, pr_util, line_no):
527 528 base_lines = ['line {}\n'.format(x) for x in range(1, 13)]
528 529 change_lines = list(base_lines)
529 530 change_lines.insert(6, 'line 6a added\n')
530 531
531 532 # Changes on the last line of sight
532 533 update_lines = list(change_lines)
533 534 update_lines[0] = 'line 1 changed\n'
534 535 update_lines[-1] = 'line 12 changed\n'
535 536
536 537 def file_b(lines):
537 538 return FileNode('file_b', ''.join(lines))
538 539
539 540 commits = [
540 541 {'message': 'a', 'added': [file_b(base_lines)]},
541 542 {'message': 'b', 'changed': [file_b(change_lines)]},
542 543 {'message': 'c', 'changed': [file_b(update_lines)]},
543 544 ]
544 545
545 546 pull_request = pr_util.create_pull_request(
546 547 commits=commits, target_head='a', source_head='b', revisions=['b'])
547 548 pr_util.create_inline_comment(line_no=line_no, file_path='file_b')
548 549
549 550 with outdated_comments_patcher():
550 551 pr_util.add_one_commit(head='c')
551 552 assert_inline_comments(pull_request, visible=0, outdated=1)
552 553
553 554 @pytest.mark.parametrize("change, content", [
554 555 ('changed', 'changed\n'),
555 556 ('removed', ''),
556 557 ], ids=['changed', 'removed'])
557 558 def test_comment_flagged_on_change(self, pr_util, change, content):
558 559 commits = [
559 560 {'message': 'a'},
560 561 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
561 562 {'message': 'c', change: [FileNode('file_b', content)]},
562 563 ]
563 564 pull_request = pr_util.create_pull_request(
564 565 commits=commits, target_head='a', source_head='b', revisions=['b'])
565 566 pr_util.create_inline_comment(file_path='file_b')
566 567
567 568 with outdated_comments_patcher():
568 569 pr_util.add_one_commit(head='c')
569 570 assert_inline_comments(pull_request, visible=0, outdated=1)
570 571
571 572
572 573 class TestUpdateChangedFiles(object):
573 574
574 575 def test_no_changes_on_unchanged_diff(self, pr_util):
575 576 commits = [
576 577 {'message': 'a'},
577 578 {'message': 'b',
578 579 'added': [FileNode('file_b', 'test_content b\n')]},
579 580 {'message': 'c',
580 581 'added': [FileNode('file_c', 'test_content c\n')]},
581 582 ]
582 583 # open a PR from a to b, adding file_b
583 584 pull_request = pr_util.create_pull_request(
584 585 commits=commits, target_head='a', source_head='b', revisions=['b'],
585 586 name_suffix='per-file-review')
586 587
587 588 # modify PR adding new file file_c
588 589 pr_util.add_one_commit(head='c')
589 590
590 591 assert_pr_file_changes(
591 592 pull_request,
592 593 added=['file_c'],
593 594 modified=[],
594 595 removed=[])
595 596
596 597 def test_modify_and_undo_modification_diff(self, pr_util):
597 598 commits = [
598 599 {'message': 'a'},
599 600 {'message': 'b',
600 601 'added': [FileNode('file_b', 'test_content b\n')]},
601 602 {'message': 'c',
602 603 'changed': [FileNode('file_b', 'test_content b modified\n')]},
603 604 {'message': 'd',
604 605 'changed': [FileNode('file_b', 'test_content b\n')]},
605 606 ]
606 607 # open a PR from a to b, adding file_b
607 608 pull_request = pr_util.create_pull_request(
608 609 commits=commits, target_head='a', source_head='b', revisions=['b'],
609 610 name_suffix='per-file-review')
610 611
611 612 # modify PR modifying file file_b
612 613 pr_util.add_one_commit(head='c')
613 614
614 615 assert_pr_file_changes(
615 616 pull_request,
616 617 added=[],
617 618 modified=['file_b'],
618 619 removed=[])
619 620
620 621 # move the head again to d, which rollbacks change,
621 622 # meaning we should indicate no changes
622 623 pr_util.add_one_commit(head='d')
623 624
624 625 assert_pr_file_changes(
625 626 pull_request,
626 627 added=[],
627 628 modified=[],
628 629 removed=[])
629 630
630 631 def test_updated_all_files_in_pr(self, pr_util):
631 632 commits = [
632 633 {'message': 'a'},
633 634 {'message': 'b', 'added': [
634 635 FileNode('file_a', 'test_content a\n'),
635 636 FileNode('file_b', 'test_content b\n'),
636 637 FileNode('file_c', 'test_content c\n')]},
637 638 {'message': 'c', 'changed': [
638 639 FileNode('file_a', 'test_content a changed\n'),
639 640 FileNode('file_b', 'test_content b changed\n'),
640 641 FileNode('file_c', 'test_content c changed\n')]},
641 642 ]
642 643 # open a PR from a to b, changing 3 files
643 644 pull_request = pr_util.create_pull_request(
644 645 commits=commits, target_head='a', source_head='b', revisions=['b'],
645 646 name_suffix='per-file-review')
646 647
647 648 pr_util.add_one_commit(head='c')
648 649
649 650 assert_pr_file_changes(
650 651 pull_request,
651 652 added=[],
652 653 modified=['file_a', 'file_b', 'file_c'],
653 654 removed=[])
654 655
655 656 def test_updated_and_removed_all_files_in_pr(self, pr_util):
656 657 commits = [
657 658 {'message': 'a'},
658 659 {'message': 'b', 'added': [
659 660 FileNode('file_a', 'test_content a\n'),
660 661 FileNode('file_b', 'test_content b\n'),
661 662 FileNode('file_c', 'test_content c\n')]},
662 663 {'message': 'c', 'removed': [
663 664 FileNode('file_a', 'test_content a changed\n'),
664 665 FileNode('file_b', 'test_content b changed\n'),
665 666 FileNode('file_c', 'test_content c changed\n')]},
666 667 ]
667 668 # open a PR from a to b, removing 3 files
668 669 pull_request = pr_util.create_pull_request(
669 670 commits=commits, target_head='a', source_head='b', revisions=['b'],
670 671 name_suffix='per-file-review')
671 672
672 673 pr_util.add_one_commit(head='c')
673 674
674 675 assert_pr_file_changes(
675 676 pull_request,
676 677 added=[],
677 678 modified=[],
678 679 removed=['file_a', 'file_b', 'file_c'])
679 680
680 681
681 682 def test_update_writes_snapshot_into_pull_request_version(pr_util):
682 683 model = PullRequestModel()
683 684 pull_request = pr_util.create_pull_request()
684 685 pr_util.update_source_repository()
685 686
686 687 model.update_commits(pull_request)
687 688
688 689 # Expect that it has a version entry now
689 690 assert len(model.get_versions(pull_request)) == 1
690 691
691 692
692 693 def test_update_skips_new_version_if_unchanged(pr_util):
693 694 pull_request = pr_util.create_pull_request()
694 695 model = PullRequestModel()
695 696 model.update_commits(pull_request)
696 697
697 698 # Expect that it still has no versions
698 699 assert len(model.get_versions(pull_request)) == 0
699 700
700 701
701 702 def test_update_assigns_comments_to_the_new_version(pr_util):
702 703 model = PullRequestModel()
703 704 pull_request = pr_util.create_pull_request()
704 705 comment = pr_util.create_comment()
705 706 pr_util.update_source_repository()
706 707
707 708 model.update_commits(pull_request)
708 709
709 710 # Expect that the comment is linked to the pr version now
710 711 assert comment.pull_request_version == model.get_versions(pull_request)[0]
711 712
712 713
713 714 def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util):
714 715 model = PullRequestModel()
715 716 pull_request = pr_util.create_pull_request()
716 717 pr_util.update_source_repository()
717 718 pr_util.update_source_repository()
718 719
719 720 model.update_commits(pull_request)
720 721
721 722 # Expect to find a new comment about the change
722 723 expected_message = textwrap.dedent(
723 724 """\
724 725 Auto status change to |under_review|
725 726
726 727 .. role:: added
727 728 .. role:: removed
728 729 .. parsed-literal::
729 730
730 731 Changed commits:
731 732 * :added:`1 added`
732 733 * :removed:`0 removed`
733 734
734 735 Changed files:
735 736 * `A file_2 <#a_c--92ed3b5f07b4>`_
736 737
737 738 .. |under_review| replace:: *"Under Review"*"""
738 739 )
739 740 pull_request_comments = sorted(
740 741 pull_request.comments, key=lambda c: c.modified_at)
741 742 update_comment = pull_request_comments[-1]
742 743 assert update_comment.text == expected_message
743 744
744 745
745 746 def test_create_version_from_snapshot_updates_attributes(pr_util):
746 747 pull_request = pr_util.create_pull_request()
747 748
748 749 # Avoiding default values
749 750 pull_request.status = PullRequest.STATUS_CLOSED
750 751 pull_request._last_merge_source_rev = "0" * 40
751 752 pull_request._last_merge_target_rev = "1" * 40
752 753 pull_request._last_merge_status = 1
753 754 pull_request.merge_rev = "2" * 40
754 755
755 756 # Remember automatic values
756 757 created_on = pull_request.created_on
757 758 updated_on = pull_request.updated_on
758 759
759 760 # Create a new version of the pull request
760 761 version = PullRequestModel()._create_version_from_snapshot(pull_request)
761 762
762 763 # Check attributes
763 764 assert version.title == pr_util.create_parameters['title']
764 765 assert version.description == pr_util.create_parameters['description']
765 766 assert version.status == PullRequest.STATUS_CLOSED
766 767 assert version.created_on == created_on
767 768 assert version.updated_on == updated_on
768 769 assert version.user_id == pull_request.user_id
769 770 assert version.revisions == pr_util.create_parameters['revisions']
770 771 assert version.source_repo == pr_util.source_repository
771 772 assert version.source_ref == pr_util.create_parameters['source_ref']
772 773 assert version.target_repo == pr_util.target_repository
773 774 assert version.target_ref == pr_util.create_parameters['target_ref']
774 775 assert version._last_merge_source_rev == pull_request._last_merge_source_rev
775 776 assert version._last_merge_target_rev == pull_request._last_merge_target_rev
776 777 assert version._last_merge_status == pull_request._last_merge_status
777 778 assert version.merge_rev == pull_request.merge_rev
778 779 assert version.pull_request == pull_request
779 780
780 781
781 782 def test_link_comments_to_version_only_updates_unlinked_comments(pr_util):
782 783 version1 = pr_util.create_version_of_pull_request()
783 784 comment_linked = pr_util.create_comment(linked_to=version1)
784 785 comment_unlinked = pr_util.create_comment()
785 786 version2 = pr_util.create_version_of_pull_request()
786 787
787 788 PullRequestModel()._link_comments_to_version(version2)
788 789
789 790 # Expect that only the new comment is linked to version2
790 791 assert (
791 792 comment_unlinked.pull_request_version_id ==
792 793 version2.pull_request_version_id)
793 794 assert (
794 795 comment_linked.pull_request_version_id ==
795 796 version1.pull_request_version_id)
796 797 assert (
797 798 comment_unlinked.pull_request_version_id !=
798 799 comment_linked.pull_request_version_id)
799 800
800 801
801 802 def test_calculate_commits():
802 803 change = PullRequestModel()._calculate_commit_id_changes(
803 804 set([1, 2, 3]), set([1, 3, 4, 5]))
804 805 assert (set([4, 5]), set([1, 3]), set([2])) == (
805 806 change.added, change.common, change.removed)
806 807
807 808
808 809 def assert_inline_comments(pull_request, visible=None, outdated=None):
809 810 if visible is not None:
810 811 inline_comments = ChangesetCommentsModel().get_inline_comments(
811 812 pull_request.target_repo.repo_id, pull_request=pull_request)
812 813 assert len(inline_comments) == visible
813 814 if outdated is not None:
814 815 outdated_comments = ChangesetCommentsModel().get_outdated_comments(
815 816 pull_request.target_repo.repo_id, pull_request)
816 817 assert len(outdated_comments) == outdated
817 818
818 819
819 820 def assert_pr_file_changes(
820 821 pull_request, added=None, modified=None, removed=None):
821 822 pr_versions = PullRequestModel().get_versions(pull_request)
822 823 # always use first version, ie original PR to calculate changes
823 824 pull_request_version = pr_versions[0]
824 825 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
825 826 pull_request, pull_request_version)
826 827 file_changes = PullRequestModel()._calculate_file_changes(
827 828 old_diff_data, new_diff_data)
828 829
829 830 assert added == file_changes.added, \
830 831 'expected added:%s vs value:%s' % (added, file_changes.added)
831 832 assert modified == file_changes.modified, \
832 833 'expected modified:%s vs value:%s' % (modified, file_changes.modified)
833 834 assert removed == file_changes.removed, \
834 835 'expected removed:%s vs value:%s' % (removed, file_changes.removed)
835 836
836 837
837 838 def outdated_comments_patcher(use_outdated=True):
838 839 return mock.patch.object(
839 840 ChangesetCommentsModel, 'use_outdated_comments',
840 841 return_value=use_outdated)
General Comments 0
You need to be logged in to leave comments. Login now