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