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