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