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