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