##// END OF EJS Templates
tests: Add a test that checks return type of pr title generate function. (has to be unicode)
Martin Bornhold -
r843:e7af289c default
parent child Browse files
Show More
@@ -1,831 +1,839 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])
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])
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 def test_generate_title_returns_unicode(self):
363 title = PullRequestModel().generate_pullrequest_title(
364 source='source-dummy',
365 source_ref='source-ref-dummy',
366 target='target-dummy',
367 )
368 assert type(title) == unicode
369
362 370
363 371 class TestIntegrationMerge(object):
364 372 @pytest.mark.parametrize('extra_config', (
365 373 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
366 374 {'vcs.hooks.protocol': 'Pyro4', 'vcs.hooks.direct_calls': False},
367 375 ))
368 376 def test_merge_triggers_push_hooks(
369 377 self, pr_util, user_admin, capture_rcextensions, merge_extras,
370 378 extra_config):
371 379 pull_request = pr_util.create_pull_request(
372 380 approved=True, mergeable=True)
373 381 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
374 382 merge_extras['repository'] = pull_request.target_repo.repo_name
375 383 Session().commit()
376 384
377 385 with mock.patch.dict(rhodecode.CONFIG, extra_config, clear=False):
378 386 merge_state = PullRequestModel().merge(
379 387 pull_request, user_admin, extras=merge_extras)
380 388
381 389 assert merge_state.executed
382 390 assert 'pre_push' in capture_rcextensions
383 391 assert 'post_push' in capture_rcextensions
384 392
385 393 def test_merge_can_be_rejected_by_pre_push_hook(
386 394 self, pr_util, user_admin, capture_rcextensions, merge_extras):
387 395 pull_request = pr_util.create_pull_request(
388 396 approved=True, mergeable=True)
389 397 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
390 398 merge_extras['repository'] = pull_request.target_repo.repo_name
391 399 Session().commit()
392 400
393 401 with mock.patch('rhodecode.EXTENSIONS.PRE_PUSH_HOOK') as pre_pull:
394 402 pre_pull.side_effect = RepositoryError("Disallow push!")
395 403 merge_status = PullRequestModel().merge(
396 404 pull_request, user_admin, extras=merge_extras)
397 405
398 406 assert not merge_status.executed
399 407 assert 'pre_push' not in capture_rcextensions
400 408 assert 'post_push' not in capture_rcextensions
401 409
402 410 def test_merge_fails_if_target_is_locked(
403 411 self, pr_util, user_regular, merge_extras):
404 412 pull_request = pr_util.create_pull_request(
405 413 approved=True, mergeable=True)
406 414 locked_by = [user_regular.user_id + 1, 12345.50, 'lock_web']
407 415 pull_request.target_repo.locked = locked_by
408 416 # TODO: johbo: Check if this can work based on the database, currently
409 417 # all data is pre-computed, that's why just updating the DB is not
410 418 # enough.
411 419 merge_extras['locked_by'] = locked_by
412 420 merge_extras['repository'] = pull_request.target_repo.repo_name
413 421 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
414 422 Session().commit()
415 423 merge_status = PullRequestModel().merge(
416 424 pull_request, user_regular, extras=merge_extras)
417 425 assert not merge_status.executed
418 426
419 427
420 428 @pytest.mark.parametrize('use_outdated, inlines_count, outdated_count', [
421 429 (False, 1, 0),
422 430 (True, 0, 1),
423 431 ])
424 432 def test_outdated_comments(
425 433 pr_util, use_outdated, inlines_count, outdated_count):
426 434 pull_request = pr_util.create_pull_request()
427 435 pr_util.create_inline_comment(file_path='not_in_updated_diff')
428 436
429 437 with outdated_comments_patcher(use_outdated) as outdated_comment_mock:
430 438 pr_util.add_one_commit()
431 439 assert_inline_comments(
432 440 pull_request, visible=inlines_count, outdated=outdated_count)
433 441 outdated_comment_mock.assert_called_with(pull_request)
434 442
435 443
436 444 @pytest.fixture
437 445 def merge_extras(user_regular):
438 446 """
439 447 Context for the vcs operation when running a merge.
440 448 """
441 449 extras = {
442 450 'ip': '127.0.0.1',
443 451 'username': user_regular.username,
444 452 'action': 'push',
445 453 'repository': 'fake_target_repo_name',
446 454 'scm': 'git',
447 455 'config': 'fake_config_ini_path',
448 456 'make_lock': None,
449 457 'locked_by': [None, None, None],
450 458 'server_url': 'http://test.example.com:5000',
451 459 'hooks': ['push', 'pull'],
452 460 }
453 461 return extras
454 462
455 463
456 464 class TestUpdateCommentHandling(object):
457 465
458 466 @pytest.fixture(autouse=True, scope='class')
459 467 def enable_outdated_comments(self, request, pylonsapp):
460 468 config_patch = mock.patch.dict(
461 469 'rhodecode.CONFIG', {'rhodecode_use_outdated_comments': True})
462 470 config_patch.start()
463 471
464 472 @request.addfinalizer
465 473 def cleanup():
466 474 config_patch.stop()
467 475
468 476 def test_comment_stays_unflagged_on_unchanged_diff(self, pr_util):
469 477 commits = [
470 478 {'message': 'a'},
471 479 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
472 480 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
473 481 ]
474 482 pull_request = pr_util.create_pull_request(
475 483 commits=commits, target_head='a', source_head='b', revisions=['b'])
476 484 pr_util.create_inline_comment(file_path='file_b')
477 485 pr_util.add_one_commit(head='c')
478 486
479 487 assert_inline_comments(pull_request, visible=1, outdated=0)
480 488
481 489 def test_comment_stays_unflagged_on_change_above(self, pr_util):
482 490 original_content = ''.join(
483 491 ['line {}\n'.format(x) for x in range(1, 11)])
484 492 updated_content = 'new_line_at_top\n' + original_content
485 493 commits = [
486 494 {'message': 'a'},
487 495 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
488 496 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
489 497 ]
490 498 pull_request = pr_util.create_pull_request(
491 499 commits=commits, target_head='a', source_head='b', revisions=['b'])
492 500
493 501 with outdated_comments_patcher():
494 502 comment = pr_util.create_inline_comment(
495 503 line_no=u'n8', file_path='file_b')
496 504 pr_util.add_one_commit(head='c')
497 505
498 506 assert_inline_comments(pull_request, visible=1, outdated=0)
499 507 assert comment.line_no == u'n9'
500 508
501 509 def test_comment_stays_unflagged_on_change_below(self, pr_util):
502 510 original_content = ''.join(['line {}\n'.format(x) for x in range(10)])
503 511 updated_content = original_content + 'new_line_at_end\n'
504 512 commits = [
505 513 {'message': 'a'},
506 514 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
507 515 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
508 516 ]
509 517 pull_request = pr_util.create_pull_request(
510 518 commits=commits, target_head='a', source_head='b', revisions=['b'])
511 519 pr_util.create_inline_comment(file_path='file_b')
512 520 pr_util.add_one_commit(head='c')
513 521
514 522 assert_inline_comments(pull_request, visible=1, outdated=0)
515 523
516 524 @pytest.mark.parametrize('line_no', ['n4', 'o4', 'n10', 'o9'])
517 525 def test_comment_flagged_on_change_around_context(self, pr_util, line_no):
518 526 base_lines = ['line {}\n'.format(x) for x in range(1, 13)]
519 527 change_lines = list(base_lines)
520 528 change_lines.insert(6, 'line 6a added\n')
521 529
522 530 # Changes on the last line of sight
523 531 update_lines = list(change_lines)
524 532 update_lines[0] = 'line 1 changed\n'
525 533 update_lines[-1] = 'line 12 changed\n'
526 534
527 535 def file_b(lines):
528 536 return FileNode('file_b', ''.join(lines))
529 537
530 538 commits = [
531 539 {'message': 'a', 'added': [file_b(base_lines)]},
532 540 {'message': 'b', 'changed': [file_b(change_lines)]},
533 541 {'message': 'c', 'changed': [file_b(update_lines)]},
534 542 ]
535 543
536 544 pull_request = pr_util.create_pull_request(
537 545 commits=commits, target_head='a', source_head='b', revisions=['b'])
538 546 pr_util.create_inline_comment(line_no=line_no, file_path='file_b')
539 547
540 548 with outdated_comments_patcher():
541 549 pr_util.add_one_commit(head='c')
542 550 assert_inline_comments(pull_request, visible=0, outdated=1)
543 551
544 552 @pytest.mark.parametrize("change, content", [
545 553 ('changed', 'changed\n'),
546 554 ('removed', ''),
547 555 ], ids=['changed', 'removed'])
548 556 def test_comment_flagged_on_change(self, pr_util, change, content):
549 557 commits = [
550 558 {'message': 'a'},
551 559 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
552 560 {'message': 'c', change: [FileNode('file_b', content)]},
553 561 ]
554 562 pull_request = pr_util.create_pull_request(
555 563 commits=commits, target_head='a', source_head='b', revisions=['b'])
556 564 pr_util.create_inline_comment(file_path='file_b')
557 565
558 566 with outdated_comments_patcher():
559 567 pr_util.add_one_commit(head='c')
560 568 assert_inline_comments(pull_request, visible=0, outdated=1)
561 569
562 570
563 571 class TestUpdateChangedFiles(object):
564 572
565 573 def test_no_changes_on_unchanged_diff(self, pr_util):
566 574 commits = [
567 575 {'message': 'a'},
568 576 {'message': 'b',
569 577 'added': [FileNode('file_b', 'test_content b\n')]},
570 578 {'message': 'c',
571 579 'added': [FileNode('file_c', 'test_content c\n')]},
572 580 ]
573 581 # open a PR from a to b, adding file_b
574 582 pull_request = pr_util.create_pull_request(
575 583 commits=commits, target_head='a', source_head='b', revisions=['b'],
576 584 name_suffix='per-file-review')
577 585
578 586 # modify PR adding new file file_c
579 587 pr_util.add_one_commit(head='c')
580 588
581 589 assert_pr_file_changes(
582 590 pull_request,
583 591 added=['file_c'],
584 592 modified=[],
585 593 removed=[])
586 594
587 595 def test_modify_and_undo_modification_diff(self, pr_util):
588 596 commits = [
589 597 {'message': 'a'},
590 598 {'message': 'b',
591 599 'added': [FileNode('file_b', 'test_content b\n')]},
592 600 {'message': 'c',
593 601 'changed': [FileNode('file_b', 'test_content b modified\n')]},
594 602 {'message': 'd',
595 603 'changed': [FileNode('file_b', 'test_content b\n')]},
596 604 ]
597 605 # open a PR from a to b, adding file_b
598 606 pull_request = pr_util.create_pull_request(
599 607 commits=commits, target_head='a', source_head='b', revisions=['b'],
600 608 name_suffix='per-file-review')
601 609
602 610 # modify PR modifying file file_b
603 611 pr_util.add_one_commit(head='c')
604 612
605 613 assert_pr_file_changes(
606 614 pull_request,
607 615 added=[],
608 616 modified=['file_b'],
609 617 removed=[])
610 618
611 619 # move the head again to d, which rollbacks change,
612 620 # meaning we should indicate no changes
613 621 pr_util.add_one_commit(head='d')
614 622
615 623 assert_pr_file_changes(
616 624 pull_request,
617 625 added=[],
618 626 modified=[],
619 627 removed=[])
620 628
621 629 def test_updated_all_files_in_pr(self, pr_util):
622 630 commits = [
623 631 {'message': 'a'},
624 632 {'message': 'b', 'added': [
625 633 FileNode('file_a', 'test_content a\n'),
626 634 FileNode('file_b', 'test_content b\n'),
627 635 FileNode('file_c', 'test_content c\n')]},
628 636 {'message': 'c', 'changed': [
629 637 FileNode('file_a', 'test_content a changed\n'),
630 638 FileNode('file_b', 'test_content b changed\n'),
631 639 FileNode('file_c', 'test_content c changed\n')]},
632 640 ]
633 641 # open a PR from a to b, changing 3 files
634 642 pull_request = pr_util.create_pull_request(
635 643 commits=commits, target_head='a', source_head='b', revisions=['b'],
636 644 name_suffix='per-file-review')
637 645
638 646 pr_util.add_one_commit(head='c')
639 647
640 648 assert_pr_file_changes(
641 649 pull_request,
642 650 added=[],
643 651 modified=['file_a', 'file_b', 'file_c'],
644 652 removed=[])
645 653
646 654 def test_updated_and_removed_all_files_in_pr(self, pr_util):
647 655 commits = [
648 656 {'message': 'a'},
649 657 {'message': 'b', 'added': [
650 658 FileNode('file_a', 'test_content a\n'),
651 659 FileNode('file_b', 'test_content b\n'),
652 660 FileNode('file_c', 'test_content c\n')]},
653 661 {'message': 'c', 'removed': [
654 662 FileNode('file_a', 'test_content a changed\n'),
655 663 FileNode('file_b', 'test_content b changed\n'),
656 664 FileNode('file_c', 'test_content c changed\n')]},
657 665 ]
658 666 # open a PR from a to b, removing 3 files
659 667 pull_request = pr_util.create_pull_request(
660 668 commits=commits, target_head='a', source_head='b', revisions=['b'],
661 669 name_suffix='per-file-review')
662 670
663 671 pr_util.add_one_commit(head='c')
664 672
665 673 assert_pr_file_changes(
666 674 pull_request,
667 675 added=[],
668 676 modified=[],
669 677 removed=['file_a', 'file_b', 'file_c'])
670 678
671 679
672 680 def test_update_writes_snapshot_into_pull_request_version(pr_util):
673 681 model = PullRequestModel()
674 682 pull_request = pr_util.create_pull_request()
675 683 pr_util.update_source_repository()
676 684
677 685 model.update_commits(pull_request)
678 686
679 687 # Expect that it has a version entry now
680 688 assert len(model.get_versions(pull_request)) == 1
681 689
682 690
683 691 def test_update_skips_new_version_if_unchanged(pr_util):
684 692 pull_request = pr_util.create_pull_request()
685 693 model = PullRequestModel()
686 694 model.update_commits(pull_request)
687 695
688 696 # Expect that it still has no versions
689 697 assert len(model.get_versions(pull_request)) == 0
690 698
691 699
692 700 def test_update_assigns_comments_to_the_new_version(pr_util):
693 701 model = PullRequestModel()
694 702 pull_request = pr_util.create_pull_request()
695 703 comment = pr_util.create_comment()
696 704 pr_util.update_source_repository()
697 705
698 706 model.update_commits(pull_request)
699 707
700 708 # Expect that the comment is linked to the pr version now
701 709 assert comment.pull_request_version == model.get_versions(pull_request)[0]
702 710
703 711
704 712 def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util):
705 713 model = PullRequestModel()
706 714 pull_request = pr_util.create_pull_request()
707 715 pr_util.update_source_repository()
708 716 pr_util.update_source_repository()
709 717
710 718 model.update_commits(pull_request)
711 719
712 720 # Expect to find a new comment about the change
713 721 expected_message = textwrap.dedent(
714 722 """\
715 723 Auto status change to |under_review|
716 724
717 725 .. role:: added
718 726 .. role:: removed
719 727 .. parsed-literal::
720 728
721 729 Changed commits:
722 730 * :added:`1 added`
723 731 * :removed:`0 removed`
724 732
725 733 Changed files:
726 734 * `A file_2 <#a_c--92ed3b5f07b4>`_
727 735
728 736 .. |under_review| replace:: *"Under Review"*"""
729 737 )
730 738 pull_request_comments = sorted(
731 739 pull_request.comments, key=lambda c: c.modified_at)
732 740 update_comment = pull_request_comments[-1]
733 741 assert update_comment.text == expected_message
734 742
735 743
736 744 def test_create_version_from_snapshot_updates_attributes(pr_util):
737 745 pull_request = pr_util.create_pull_request()
738 746
739 747 # Avoiding default values
740 748 pull_request.status = PullRequest.STATUS_CLOSED
741 749 pull_request._last_merge_source_rev = "0" * 40
742 750 pull_request._last_merge_target_rev = "1" * 40
743 751 pull_request._last_merge_status = 1
744 752 pull_request.merge_rev = "2" * 40
745 753
746 754 # Remember automatic values
747 755 created_on = pull_request.created_on
748 756 updated_on = pull_request.updated_on
749 757
750 758 # Create a new version of the pull request
751 759 version = PullRequestModel()._create_version_from_snapshot(pull_request)
752 760
753 761 # Check attributes
754 762 assert version.title == pr_util.create_parameters['title']
755 763 assert version.description == pr_util.create_parameters['description']
756 764 assert version.status == PullRequest.STATUS_CLOSED
757 765 assert version.created_on == created_on
758 766 assert version.updated_on == updated_on
759 767 assert version.user_id == pull_request.user_id
760 768 assert version.revisions == pr_util.create_parameters['revisions']
761 769 assert version.source_repo == pr_util.source_repository
762 770 assert version.source_ref == pr_util.create_parameters['source_ref']
763 771 assert version.target_repo == pr_util.target_repository
764 772 assert version.target_ref == pr_util.create_parameters['target_ref']
765 773 assert version._last_merge_source_rev == pull_request._last_merge_source_rev
766 774 assert version._last_merge_target_rev == pull_request._last_merge_target_rev
767 775 assert version._last_merge_status == pull_request._last_merge_status
768 776 assert version.merge_rev == pull_request.merge_rev
769 777 assert version.pull_request == pull_request
770 778
771 779
772 780 def test_link_comments_to_version_only_updates_unlinked_comments(pr_util):
773 781 version1 = pr_util.create_version_of_pull_request()
774 782 comment_linked = pr_util.create_comment(linked_to=version1)
775 783 comment_unlinked = pr_util.create_comment()
776 784 version2 = pr_util.create_version_of_pull_request()
777 785
778 786 PullRequestModel()._link_comments_to_version(version2)
779 787
780 788 # Expect that only the new comment is linked to version2
781 789 assert (
782 790 comment_unlinked.pull_request_version_id ==
783 791 version2.pull_request_version_id)
784 792 assert (
785 793 comment_linked.pull_request_version_id ==
786 794 version1.pull_request_version_id)
787 795 assert (
788 796 comment_unlinked.pull_request_version_id !=
789 797 comment_linked.pull_request_version_id)
790 798
791 799
792 800 def test_calculate_commits():
793 801 change = PullRequestModel()._calculate_commit_id_changes(
794 802 set([1, 2, 3]), set([1, 3, 4, 5]))
795 803 assert (set([4, 5]), set([1, 3]), set([2])) == (
796 804 change.added, change.common, change.removed)
797 805
798 806
799 807 def assert_inline_comments(pull_request, visible=None, outdated=None):
800 808 if visible is not None:
801 809 inline_comments = ChangesetCommentsModel().get_inline_comments(
802 810 pull_request.target_repo.repo_id, pull_request=pull_request)
803 811 assert len(inline_comments) == visible
804 812 if outdated is not None:
805 813 outdated_comments = ChangesetCommentsModel().get_outdated_comments(
806 814 pull_request.target_repo.repo_id, pull_request)
807 815 assert len(outdated_comments) == outdated
808 816
809 817
810 818 def assert_pr_file_changes(
811 819 pull_request, added=None, modified=None, removed=None):
812 820 pr_versions = PullRequestModel().get_versions(pull_request)
813 821 # always use first version, ie original PR to calculate changes
814 822 pull_request_version = pr_versions[0]
815 823 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
816 824 pull_request, pull_request_version)
817 825 file_changes = PullRequestModel()._calculate_file_changes(
818 826 old_diff_data, new_diff_data)
819 827
820 828 assert added == file_changes.added, \
821 829 'expected added:%s vs value:%s' % (added, file_changes.added)
822 830 assert modified == file_changes.modified, \
823 831 'expected modified:%s vs value:%s' % (modified, file_changes.modified)
824 832 assert removed == file_changes.removed, \
825 833 'expected removed:%s vs value:%s' % (removed, file_changes.removed)
826 834
827 835
828 836 def outdated_comments_patcher(use_outdated=True):
829 837 return mock.patch.object(
830 838 ChangesetCommentsModel, 'use_outdated_comments',
831 839 return_value=use_outdated)
General Comments 0
You need to be logged in to leave comments. Login now