##// END OF EJS Templates
pull-requests: fixed tests.
ergo -
r1977:cd9c800f default
parent child Browse files
Show More
@@ -1,1112 +1,1110 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 import rhodecode
23 import rhodecode
24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
25 from rhodecode.lib.vcs.nodes import FileNode
25 from rhodecode.lib.vcs.nodes import FileNode
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.model.changeset_status import ChangesetStatusModel
27 from rhodecode.model.changeset_status import ChangesetStatusModel
28 from rhodecode.model.db import (
28 from rhodecode.model.db import (
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment)
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment)
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31 from rhodecode.model.pull_request import PullRequestModel
31 from rhodecode.model.pull_request import PullRequestModel
32 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
33 from rhodecode.tests import (
33 from rhodecode.tests import (
34 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
34 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
35 from rhodecode.tests.utils import AssertResponse
35 from rhodecode.tests.utils import AssertResponse
36
36
37
37
38 def route_path(name, params=None, **kwargs):
38 def route_path(name, params=None, **kwargs):
39 import urllib
39 import urllib
40
40
41 base_url = {
41 base_url = {
42 'repo_changelog': '/{repo_name}/changelog',
42 'repo_changelog': '/{repo_name}/changelog',
43 'repo_changelog_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
43 'repo_changelog_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
44 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
44 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
45 'pullrequest_show_all': '/{repo_name}/pull-request',
45 'pullrequest_show_all': '/{repo_name}/pull-request',
46 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
46 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
47 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
47 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
48 'pullrequest_repo_destinations': '/{repo_name}/pull-request/repo-destinations',
48 'pullrequest_repo_destinations': '/{repo_name}/pull-request/repo-destinations',
49 'pullrequest_new': '/{repo_name}/pull-request/new',
49 'pullrequest_new': '/{repo_name}/pull-request/new',
50 'pullrequest_create': '/{repo_name}/pull-request/create',
50 'pullrequest_create': '/{repo_name}/pull-request/create',
51 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
51 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
52 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
52 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
53 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
53 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
54 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
54 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
55 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
55 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
56 }[name].format(**kwargs)
56 }[name].format(**kwargs)
57
57
58 if params:
58 if params:
59 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
59 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
60 return base_url
60 return base_url
61
61
62
62
63 @pytest.mark.usefixtures('app', 'autologin_user')
63 @pytest.mark.usefixtures('app', 'autologin_user')
64 @pytest.mark.backends("git", "hg")
64 @pytest.mark.backends("git", "hg")
65 class TestPullrequestsView(object):
65 class TestPullrequestsView(object):
66
66
67 def test_index(self, backend):
67 def test_index(self, backend):
68 self.app.get(route_path(
68 self.app.get(route_path(
69 'pullrequest_new',
69 'pullrequest_new',
70 repo_name=backend.repo_name))
70 repo_name=backend.repo_name))
71
71
72 def test_option_menu_create_pull_request_exists(self, backend):
72 def test_option_menu_create_pull_request_exists(self, backend):
73 repo_name = backend.repo_name
73 repo_name = backend.repo_name
74 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
74 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
75
75
76 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
76 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
77 'pullrequest_new', repo_name=repo_name)
77 'pullrequest_new', repo_name=repo_name)
78 response.mustcontain(create_pr_link)
78 response.mustcontain(create_pr_link)
79
79
80 def test_create_pr_form_with_raw_commit_id(self, backend):
80 def test_create_pr_form_with_raw_commit_id(self, backend):
81 repo = backend.repo
81 repo = backend.repo
82
82
83 self.app.get(
83 self.app.get(
84 route_path('pullrequest_new',
84 route_path('pullrequest_new',
85 repo_name=repo.repo_name,
85 repo_name=repo.repo_name,
86 commit=repo.get_commit().raw_id),
86 commit=repo.get_commit().raw_id),
87 status=200)
87 status=200)
88
88
89 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
89 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
90 def test_show(self, pr_util, pr_merge_enabled):
90 def test_show(self, pr_util, pr_merge_enabled):
91 pull_request = pr_util.create_pull_request(
91 pull_request = pr_util.create_pull_request(
92 mergeable=pr_merge_enabled, enable_notifications=False)
92 mergeable=pr_merge_enabled, enable_notifications=False)
93
93
94 response = self.app.get(route_path(
94 response = self.app.get(route_path(
95 'pullrequest_show',
95 'pullrequest_show',
96 repo_name=pull_request.target_repo.scm_instance().name,
96 repo_name=pull_request.target_repo.scm_instance().name,
97 pull_request_id=pull_request.pull_request_id))
97 pull_request_id=pull_request.pull_request_id))
98
98
99 for commit_id in pull_request.revisions:
99 for commit_id in pull_request.revisions:
100 response.mustcontain(commit_id)
100 response.mustcontain(commit_id)
101
101
102 assert pull_request.target_ref_parts.type in response
102 assert pull_request.target_ref_parts.type in response
103 assert pull_request.target_ref_parts.name in response
103 assert pull_request.target_ref_parts.name in response
104 target_clone_url = pull_request.target_repo.clone_url()
104 target_clone_url = pull_request.target_repo.clone_url()
105 assert target_clone_url in response
105 assert target_clone_url in response
106
106
107 assert 'class="pull-request-merge"' in response
107 assert 'class="pull-request-merge"' in response
108 assert (
108 assert (
109 'Server-side pull request merging is disabled.'
109 'Server-side pull request merging is disabled.'
110 in response) != pr_merge_enabled
110 in response) != pr_merge_enabled
111
111
112 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
112 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
113 # Logout
113 # Logout
114 response = self.app.post(
114 response = self.app.post(
115 h.route_path('logout'),
115 h.route_path('logout'),
116 params={'csrf_token': csrf_token})
116 params={'csrf_token': csrf_token})
117 # Login as regular user
117 # Login as regular user
118 response = self.app.post(h.route_path('login'),
118 response = self.app.post(h.route_path('login'),
119 {'username': TEST_USER_REGULAR_LOGIN,
119 {'username': TEST_USER_REGULAR_LOGIN,
120 'password': 'test12'})
120 'password': 'test12'})
121
121
122 pull_request = pr_util.create_pull_request(
122 pull_request = pr_util.create_pull_request(
123 author=TEST_USER_REGULAR_LOGIN)
123 author=TEST_USER_REGULAR_LOGIN)
124
124
125 response = self.app.get(route_path(
125 response = self.app.get(route_path(
126 'pullrequest_show',
126 'pullrequest_show',
127 repo_name=pull_request.target_repo.scm_instance().name,
127 repo_name=pull_request.target_repo.scm_instance().name,
128 pull_request_id=pull_request.pull_request_id))
128 pull_request_id=pull_request.pull_request_id))
129
129
130 response.mustcontain('Server-side pull request merging is disabled.')
130 response.mustcontain('Server-side pull request merging is disabled.')
131
131
132 assert_response = response.assert_response()
132 assert_response = response.assert_response()
133 # for regular user without a merge permissions, we don't see it
133 # for regular user without a merge permissions, we don't see it
134 assert_response.no_element_exists('#close-pull-request-action')
134 assert_response.no_element_exists('#close-pull-request-action')
135
135
136 user_util.grant_user_permission_to_repo(
136 user_util.grant_user_permission_to_repo(
137 pull_request.target_repo,
137 pull_request.target_repo,
138 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
138 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
139 'repository.write')
139 'repository.write')
140 response = self.app.get(route_path(
140 response = self.app.get(route_path(
141 'pullrequest_show',
141 'pullrequest_show',
142 repo_name=pull_request.target_repo.scm_instance().name,
142 repo_name=pull_request.target_repo.scm_instance().name,
143 pull_request_id=pull_request.pull_request_id))
143 pull_request_id=pull_request.pull_request_id))
144
144
145 response.mustcontain('Server-side pull request merging is disabled.')
145 response.mustcontain('Server-side pull request merging is disabled.')
146
146
147 assert_response = response.assert_response()
147 assert_response = response.assert_response()
148 # now regular user has a merge permissions, we have CLOSE button
148 # now regular user has a merge permissions, we have CLOSE button
149 assert_response.one_element_exists('#close-pull-request-action')
149 assert_response.one_element_exists('#close-pull-request-action')
150
150
151 def test_show_invalid_commit_id(self, pr_util):
151 def test_show_invalid_commit_id(self, pr_util):
152 # Simulating invalid revisions which will cause a lookup error
152 # Simulating invalid revisions which will cause a lookup error
153 pull_request = pr_util.create_pull_request()
153 pull_request = pr_util.create_pull_request()
154 pull_request.revisions = ['invalid']
154 pull_request.revisions = ['invalid']
155 Session().add(pull_request)
155 Session().add(pull_request)
156 Session().commit()
156 Session().commit()
157
157
158 response = self.app.get(route_path(
158 response = self.app.get(route_path(
159 'pullrequest_show',
159 'pullrequest_show',
160 repo_name=pull_request.target_repo.scm_instance().name,
160 repo_name=pull_request.target_repo.scm_instance().name,
161 pull_request_id=pull_request.pull_request_id))
161 pull_request_id=pull_request.pull_request_id))
162
162
163 for commit_id in pull_request.revisions:
163 for commit_id in pull_request.revisions:
164 response.mustcontain(commit_id)
164 response.mustcontain(commit_id)
165
165
166 def test_show_invalid_source_reference(self, pr_util):
166 def test_show_invalid_source_reference(self, pr_util):
167 pull_request = pr_util.create_pull_request()
167 pull_request = pr_util.create_pull_request()
168 pull_request.source_ref = 'branch:b:invalid'
168 pull_request.source_ref = 'branch:b:invalid'
169 Session().add(pull_request)
169 Session().add(pull_request)
170 Session().commit()
170 Session().commit()
171
171
172 self.app.get(route_path(
172 self.app.get(route_path(
173 'pullrequest_show',
173 'pullrequest_show',
174 repo_name=pull_request.target_repo.scm_instance().name,
174 repo_name=pull_request.target_repo.scm_instance().name,
175 pull_request_id=pull_request.pull_request_id))
175 pull_request_id=pull_request.pull_request_id))
176
176
177 def test_edit_title_description(self, pr_util, csrf_token):
177 def test_edit_title_description(self, pr_util, csrf_token):
178 pull_request = pr_util.create_pull_request()
178 pull_request = pr_util.create_pull_request()
179 pull_request_id = pull_request.pull_request_id
179 pull_request_id = pull_request.pull_request_id
180
180
181 response = self.app.post(
181 response = self.app.post(
182 route_path('pullrequest_update',
182 route_path('pullrequest_update',
183 repo_name=pull_request.target_repo.repo_name,
183 repo_name=pull_request.target_repo.repo_name,
184 pull_request_id=pull_request_id),
184 pull_request_id=pull_request_id),
185 params={
185 params={
186 'edit_pull_request': 'true',
186 'edit_pull_request': 'true',
187 'title': 'New title',
187 'title': 'New title',
188 'description': 'New description',
188 'description': 'New description',
189 'csrf_token': csrf_token})
189 'csrf_token': csrf_token})
190
190
191 assert_session_flash(
191 assert_session_flash(
192 response, u'Pull request title & description updated.',
192 response, u'Pull request title & description updated.',
193 category='success')
193 category='success')
194
194
195 pull_request = PullRequest.get(pull_request_id)
195 pull_request = PullRequest.get(pull_request_id)
196 assert pull_request.title == 'New title'
196 assert pull_request.title == 'New title'
197 assert pull_request.description == 'New description'
197 assert pull_request.description == 'New description'
198
198
199 def test_edit_title_description_closed(self, pr_util, csrf_token):
199 def test_edit_title_description_closed(self, pr_util, csrf_token):
200 pull_request = pr_util.create_pull_request()
200 pull_request = pr_util.create_pull_request()
201 pull_request_id = pull_request.pull_request_id
201 pull_request_id = pull_request.pull_request_id
202 pr_util.close()
202 pr_util.close()
203
203
204 response = self.app.post(
204 response = self.app.post(
205 route_path('pullrequest_update',
205 route_path('pullrequest_update',
206 repo_name=pull_request.target_repo.repo_name,
206 repo_name=pull_request.target_repo.repo_name,
207 pull_request_id=pull_request_id),
207 pull_request_id=pull_request_id),
208 params={
208 params={
209 'edit_pull_request': 'true',
209 'edit_pull_request': 'true',
210 'title': 'New title',
210 'title': 'New title',
211 'description': 'New description',
211 'description': 'New description',
212 'csrf_token': csrf_token})
212 'csrf_token': csrf_token})
213
213
214 assert_session_flash(
214 assert_session_flash(
215 response, u'Cannot update closed pull requests.',
215 response, u'Cannot update closed pull requests.',
216 category='error')
216 category='error')
217
217
218 def test_update_invalid_source_reference(self, pr_util, csrf_token):
218 def test_update_invalid_source_reference(self, pr_util, csrf_token):
219 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
219 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
220
220
221 pull_request = pr_util.create_pull_request()
221 pull_request = pr_util.create_pull_request()
222 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
222 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
223 Session().add(pull_request)
223 Session().add(pull_request)
224 Session().commit()
224 Session().commit()
225
225
226 pull_request_id = pull_request.pull_request_id
226 pull_request_id = pull_request.pull_request_id
227
227
228 response = self.app.post(
228 response = self.app.post(
229 route_path('pullrequest_update',
229 route_path('pullrequest_update',
230 repo_name=pull_request.target_repo.repo_name,
230 repo_name=pull_request.target_repo.repo_name,
231 pull_request_id=pull_request_id),
231 pull_request_id=pull_request_id),
232 params={'update_commits': 'true',
232 params={'update_commits': 'true',
233 'csrf_token': csrf_token})
233 'csrf_token': csrf_token})
234
234
235 expected_msg = PullRequestModel.UPDATE_STATUS_MESSAGES[
235 expected_msg = PullRequestModel.UPDATE_STATUS_MESSAGES[
236 UpdateFailureReason.MISSING_SOURCE_REF]
236 UpdateFailureReason.MISSING_SOURCE_REF]
237 assert_session_flash(response, expected_msg, category='error')
237 assert_session_flash(response, expected_msg, category='error')
238
238
239 def test_missing_target_reference(self, pr_util, csrf_token):
239 def test_missing_target_reference(self, pr_util, csrf_token):
240 from rhodecode.lib.vcs.backends.base import MergeFailureReason
240 from rhodecode.lib.vcs.backends.base import MergeFailureReason
241 pull_request = pr_util.create_pull_request(
241 pull_request = pr_util.create_pull_request(
242 approved=True, mergeable=True)
242 approved=True, mergeable=True)
243 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
243 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
244 Session().add(pull_request)
244 Session().add(pull_request)
245 Session().commit()
245 Session().commit()
246
246
247 pull_request_id = pull_request.pull_request_id
247 pull_request_id = pull_request.pull_request_id
248 pull_request_url = route_path(
248 pull_request_url = route_path(
249 'pullrequest_show',
249 'pullrequest_show',
250 repo_name=pull_request.target_repo.repo_name,
250 repo_name=pull_request.target_repo.repo_name,
251 pull_request_id=pull_request_id)
251 pull_request_id=pull_request_id)
252
252
253 response = self.app.get(pull_request_url)
253 response = self.app.get(pull_request_url)
254
254
255 assertr = AssertResponse(response)
255 assertr = AssertResponse(response)
256 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
256 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
257 MergeFailureReason.MISSING_TARGET_REF]
257 MergeFailureReason.MISSING_TARGET_REF]
258 assertr.element_contains(
258 assertr.element_contains(
259 'span[data-role="merge-message"]', str(expected_msg))
259 'span[data-role="merge-message"]', str(expected_msg))
260
260
261 def test_comment_and_close_pull_request_custom_message_approved(
261 def test_comment_and_close_pull_request_custom_message_approved(
262 self, pr_util, csrf_token, xhr_header):
262 self, pr_util, csrf_token, xhr_header):
263
263
264 pull_request = pr_util.create_pull_request(approved=True)
264 pull_request = pr_util.create_pull_request(approved=True)
265 pull_request_id = pull_request.pull_request_id
265 pull_request_id = pull_request.pull_request_id
266 author = pull_request.user_id
266 author = pull_request.user_id
267 repo = pull_request.target_repo.repo_id
267 repo = pull_request.target_repo.repo_id
268
268
269 self.app.post(
269 self.app.post(
270 route_path('pullrequest_comment_create',
270 route_path('pullrequest_comment_create',
271 repo_name=pull_request.target_repo.scm_instance().name,
271 repo_name=pull_request.target_repo.scm_instance().name,
272 pull_request_id=pull_request_id),
272 pull_request_id=pull_request_id),
273 params={
273 params={
274 'close_pull_request': '1',
274 'close_pull_request': '1',
275 'text': 'Closing a PR',
275 'text': 'Closing a PR',
276 'csrf_token': csrf_token},
276 'csrf_token': csrf_token},
277 extra_environ=xhr_header,)
277 extra_environ=xhr_header,)
278
278
279 journal = UserLog.query()\
279 journal = UserLog.query()\
280 .filter(UserLog.user_id == author)\
280 .filter(UserLog.user_id == author)\
281 .filter(UserLog.repository_id == repo) \
281 .filter(UserLog.repository_id == repo) \
282 .order_by('user_log_id') \
282 .order_by('user_log_id') \
283 .all()
283 .all()
284 assert journal[-1].action == 'repo.pull_request.close'
284 assert journal[-1].action == 'repo.pull_request.close'
285
285
286 pull_request = PullRequest.get(pull_request_id)
286 pull_request = PullRequest.get(pull_request_id)
287 assert pull_request.is_closed()
287 assert pull_request.is_closed()
288
288
289 status = ChangesetStatusModel().get_status(
289 status = ChangesetStatusModel().get_status(
290 pull_request.source_repo, pull_request=pull_request)
290 pull_request.source_repo, pull_request=pull_request)
291 assert status == ChangesetStatus.STATUS_APPROVED
291 assert status == ChangesetStatus.STATUS_APPROVED
292 comments = ChangesetComment().query() \
292 comments = ChangesetComment().query() \
293 .filter(ChangesetComment.pull_request == pull_request) \
293 .filter(ChangesetComment.pull_request == pull_request) \
294 .order_by(ChangesetComment.comment_id.asc())\
294 .order_by(ChangesetComment.comment_id.asc())\
295 .all()
295 .all()
296 assert comments[-1].text == 'Closing a PR'
296 assert comments[-1].text == 'Closing a PR'
297
297
298 def test_comment_force_close_pull_request_rejected(
298 def test_comment_force_close_pull_request_rejected(
299 self, pr_util, csrf_token, xhr_header):
299 self, pr_util, csrf_token, xhr_header):
300 pull_request = pr_util.create_pull_request()
300 pull_request = pr_util.create_pull_request()
301 pull_request_id = pull_request.pull_request_id
301 pull_request_id = pull_request.pull_request_id
302 PullRequestModel().update_reviewers(
302 PullRequestModel().update_reviewers(
303 pull_request_id, [(1, ['reason'], False), (2, ['reason2'], False)],
303 pull_request_id, [(1, ['reason'], False), (2, ['reason2'], False)],
304 pull_request.author)
304 pull_request.author)
305 author = pull_request.user_id
305 author = pull_request.user_id
306 repo = pull_request.target_repo.repo_id
306 repo = pull_request.target_repo.repo_id
307
307
308 self.app.post(
308 self.app.post(
309 route_path('pullrequest_comment_create',
309 route_path('pullrequest_comment_create',
310 repo_name=pull_request.target_repo.scm_instance().name,
310 repo_name=pull_request.target_repo.scm_instance().name,
311 pull_request_id=pull_request_id),
311 pull_request_id=pull_request_id),
312 params={
312 params={
313 'close_pull_request': '1',
313 'close_pull_request': '1',
314 'csrf_token': csrf_token},
314 'csrf_token': csrf_token},
315 extra_environ=xhr_header)
315 extra_environ=xhr_header)
316
316
317 pull_request = PullRequest.get(pull_request_id)
317 pull_request = PullRequest.get(pull_request_id)
318
318
319 journal = UserLog.query()\
319 journal = UserLog.query()\
320 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
320 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
321 .order_by('user_log_id') \
321 .order_by('user_log_id') \
322 .all()
322 .all()
323 assert journal[-1].action == 'repo.pull_request.close'
323 assert journal[-1].action == 'repo.pull_request.close'
324
324
325 # check only the latest status, not the review status
325 # check only the latest status, not the review status
326 status = ChangesetStatusModel().get_status(
326 status = ChangesetStatusModel().get_status(
327 pull_request.source_repo, pull_request=pull_request)
327 pull_request.source_repo, pull_request=pull_request)
328 assert status == ChangesetStatus.STATUS_REJECTED
328 assert status == ChangesetStatus.STATUS_REJECTED
329
329
330 def test_comment_and_close_pull_request(
330 def test_comment_and_close_pull_request(
331 self, pr_util, csrf_token, xhr_header):
331 self, pr_util, csrf_token, xhr_header):
332 pull_request = pr_util.create_pull_request()
332 pull_request = pr_util.create_pull_request()
333 pull_request_id = pull_request.pull_request_id
333 pull_request_id = pull_request.pull_request_id
334
334
335 response = self.app.post(
335 response = self.app.post(
336 route_path('pullrequest_comment_create',
336 route_path('pullrequest_comment_create',
337 repo_name=pull_request.target_repo.scm_instance().name,
337 repo_name=pull_request.target_repo.scm_instance().name,
338 pull_request_id=pull_request.pull_request_id),
338 pull_request_id=pull_request.pull_request_id),
339 params={
339 params={
340 'close_pull_request': 'true',
340 'close_pull_request': 'true',
341 'csrf_token': csrf_token},
341 'csrf_token': csrf_token},
342 extra_environ=xhr_header)
342 extra_environ=xhr_header)
343
343
344 assert response.json
344 assert response.json
345
345
346 pull_request = PullRequest.get(pull_request_id)
346 pull_request = PullRequest.get(pull_request_id)
347 assert pull_request.is_closed()
347 assert pull_request.is_closed()
348
348
349 # check only the latest status, not the review status
349 # check only the latest status, not the review status
350 status = ChangesetStatusModel().get_status(
350 status = ChangesetStatusModel().get_status(
351 pull_request.source_repo, pull_request=pull_request)
351 pull_request.source_repo, pull_request=pull_request)
352 assert status == ChangesetStatus.STATUS_REJECTED
352 assert status == ChangesetStatus.STATUS_REJECTED
353
353
354 def test_create_pull_request(self, backend, csrf_token):
354 def test_create_pull_request(self, backend, csrf_token):
355 commits = [
355 commits = [
356 {'message': 'ancestor'},
356 {'message': 'ancestor'},
357 {'message': 'change'},
357 {'message': 'change'},
358 {'message': 'change2'},
358 {'message': 'change2'},
359 ]
359 ]
360 commit_ids = backend.create_master_repo(commits)
360 commit_ids = backend.create_master_repo(commits)
361 target = backend.create_repo(heads=['ancestor'])
361 target = backend.create_repo(heads=['ancestor'])
362 source = backend.create_repo(heads=['change2'])
362 source = backend.create_repo(heads=['change2'])
363
363
364 response = self.app.post(
364 response = self.app.post(
365 route_path('pullrequest_create', repo_name=source.repo_name),
365 route_path('pullrequest_create', repo_name=source.repo_name),
366 [
366 [
367 ('source_repo', source.repo_name),
367 ('source_repo', source.repo_name),
368 ('source_ref', 'branch:default:' + commit_ids['change2']),
368 ('source_ref', 'branch:default:' + commit_ids['change2']),
369 ('target_repo', target.repo_name),
369 ('target_repo', target.repo_name),
370 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
370 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
371 ('common_ancestor', commit_ids['ancestor']),
371 ('common_ancestor', commit_ids['ancestor']),
372 ('pullrequest_desc', 'Description'),
372 ('pullrequest_desc', 'Description'),
373 ('pullrequest_title', 'Title'),
373 ('pullrequest_title', 'Title'),
374 ('__start__', 'review_members:sequence'),
374 ('__start__', 'review_members:sequence'),
375 ('__start__', 'reviewer:mapping'),
375 ('__start__', 'reviewer:mapping'),
376 ('user_id', '1'),
376 ('user_id', '1'),
377 ('__start__', 'reasons:sequence'),
377 ('__start__', 'reasons:sequence'),
378 ('reason', 'Some reason'),
378 ('reason', 'Some reason'),
379 ('__end__', 'reasons:sequence'),
379 ('__end__', 'reasons:sequence'),
380 ('mandatory', 'False'),
380 ('mandatory', 'False'),
381 ('__end__', 'reviewer:mapping'),
381 ('__end__', 'reviewer:mapping'),
382 ('__end__', 'review_members:sequence'),
382 ('__end__', 'review_members:sequence'),
383 ('__start__', 'revisions:sequence'),
383 ('__start__', 'revisions:sequence'),
384 ('revisions', commit_ids['change']),
384 ('revisions', commit_ids['change']),
385 ('revisions', commit_ids['change2']),
385 ('revisions', commit_ids['change2']),
386 ('__end__', 'revisions:sequence'),
386 ('__end__', 'revisions:sequence'),
387 ('user', ''),
387 ('user', ''),
388 ('csrf_token', csrf_token),
388 ('csrf_token', csrf_token),
389 ],
389 ],
390 status=302)
390 status=302)
391
391
392 location = response.headers['Location']
392 location = response.headers['Location']
393 pull_request_id = location.rsplit('/', 1)[1]
393 pull_request_id = location.rsplit('/', 1)[1]
394 assert pull_request_id != 'new'
394 assert pull_request_id != 'new'
395 pull_request = PullRequest.get(int(pull_request_id))
395 pull_request = PullRequest.get(int(pull_request_id))
396
396
397 # check that we have now both revisions
397 # check that we have now both revisions
398 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
398 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
399 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
399 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
400 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
400 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
401 assert pull_request.target_ref == expected_target_ref
401 assert pull_request.target_ref == expected_target_ref
402
402
403 def test_reviewer_notifications(self, backend, csrf_token):
403 def test_reviewer_notifications(self, backend, csrf_token):
404 # We have to use the app.post for this test so it will create the
404 # We have to use the app.post for this test so it will create the
405 # notifications properly with the new PR
405 # notifications properly with the new PR
406 commits = [
406 commits = [
407 {'message': 'ancestor',
407 {'message': 'ancestor',
408 'added': [FileNode('file_A', content='content_of_ancestor')]},
408 'added': [FileNode('file_A', content='content_of_ancestor')]},
409 {'message': 'change',
409 {'message': 'change',
410 'added': [FileNode('file_a', content='content_of_change')]},
410 'added': [FileNode('file_a', content='content_of_change')]},
411 {'message': 'change-child'},
411 {'message': 'change-child'},
412 {'message': 'ancestor-child', 'parents': ['ancestor'],
412 {'message': 'ancestor-child', 'parents': ['ancestor'],
413 'added': [
413 'added': [
414 FileNode('file_B', content='content_of_ancestor_child')]},
414 FileNode('file_B', content='content_of_ancestor_child')]},
415 {'message': 'ancestor-child-2'},
415 {'message': 'ancestor-child-2'},
416 ]
416 ]
417 commit_ids = backend.create_master_repo(commits)
417 commit_ids = backend.create_master_repo(commits)
418 target = backend.create_repo(heads=['ancestor-child'])
418 target = backend.create_repo(heads=['ancestor-child'])
419 source = backend.create_repo(heads=['change'])
419 source = backend.create_repo(heads=['change'])
420
420
421 response = self.app.post(
421 response = self.app.post(
422 route_path('pullrequest_create', repo_name=source.repo_name),
422 route_path('pullrequest_create', repo_name=source.repo_name),
423 [
423 [
424 ('source_repo', source.repo_name),
424 ('source_repo', source.repo_name),
425 ('source_ref', 'branch:default:' + commit_ids['change']),
425 ('source_ref', 'branch:default:' + commit_ids['change']),
426 ('target_repo', target.repo_name),
426 ('target_repo', target.repo_name),
427 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
427 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
428 ('common_ancestor', commit_ids['ancestor']),
428 ('common_ancestor', commit_ids['ancestor']),
429 ('pullrequest_desc', 'Description'),
429 ('pullrequest_desc', 'Description'),
430 ('pullrequest_title', 'Title'),
430 ('pullrequest_title', 'Title'),
431 ('__start__', 'review_members:sequence'),
431 ('__start__', 'review_members:sequence'),
432 ('__start__', 'reviewer:mapping'),
432 ('__start__', 'reviewer:mapping'),
433 ('user_id', '2'),
433 ('user_id', '2'),
434 ('__start__', 'reasons:sequence'),
434 ('__start__', 'reasons:sequence'),
435 ('reason', 'Some reason'),
435 ('reason', 'Some reason'),
436 ('__end__', 'reasons:sequence'),
436 ('__end__', 'reasons:sequence'),
437 ('mandatory', 'False'),
437 ('mandatory', 'False'),
438 ('__end__', 'reviewer:mapping'),
438 ('__end__', 'reviewer:mapping'),
439 ('__end__', 'review_members:sequence'),
439 ('__end__', 'review_members:sequence'),
440 ('__start__', 'revisions:sequence'),
440 ('__start__', 'revisions:sequence'),
441 ('revisions', commit_ids['change']),
441 ('revisions', commit_ids['change']),
442 ('__end__', 'revisions:sequence'),
442 ('__end__', 'revisions:sequence'),
443 ('user', ''),
443 ('user', ''),
444 ('csrf_token', csrf_token),
444 ('csrf_token', csrf_token),
445 ],
445 ],
446 status=302)
446 status=302)
447
447
448 location = response.headers['Location']
448 location = response.headers['Location']
449
449
450 pull_request_id = location.rsplit('/', 1)[1]
450 pull_request_id = location.rsplit('/', 1)[1]
451 assert pull_request_id != 'new'
451 assert pull_request_id != 'new'
452 pull_request = PullRequest.get(int(pull_request_id))
452 pull_request = PullRequest.get(int(pull_request_id))
453
453
454 # Check that a notification was made
454 # Check that a notification was made
455 notifications = Notification.query()\
455 notifications = Notification.query()\
456 .filter(Notification.created_by == pull_request.author.user_id,
456 .filter(Notification.created_by == pull_request.author.user_id,
457 Notification.type_ == Notification.TYPE_PULL_REQUEST,
457 Notification.type_ == Notification.TYPE_PULL_REQUEST,
458 Notification.subject.contains(
458 Notification.subject.contains(
459 "wants you to review pull request #%s" % pull_request_id))
459 "wants you to review pull request #%s" % pull_request_id))
460 assert len(notifications.all()) == 1
460 assert len(notifications.all()) == 1
461
461
462 # Change reviewers and check that a notification was made
462 # Change reviewers and check that a notification was made
463 PullRequestModel().update_reviewers(
463 PullRequestModel().update_reviewers(
464 pull_request.pull_request_id, [(1, [], False)],
464 pull_request.pull_request_id, [(1, [], False)],
465 pull_request.author)
465 pull_request.author)
466 assert len(notifications.all()) == 2
466 assert len(notifications.all()) == 2
467
467
468 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
468 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
469 csrf_token):
469 csrf_token):
470 commits = [
470 commits = [
471 {'message': 'ancestor',
471 {'message': 'ancestor',
472 'added': [FileNode('file_A', content='content_of_ancestor')]},
472 'added': [FileNode('file_A', content='content_of_ancestor')]},
473 {'message': 'change',
473 {'message': 'change',
474 'added': [FileNode('file_a', content='content_of_change')]},
474 'added': [FileNode('file_a', content='content_of_change')]},
475 {'message': 'change-child'},
475 {'message': 'change-child'},
476 {'message': 'ancestor-child', 'parents': ['ancestor'],
476 {'message': 'ancestor-child', 'parents': ['ancestor'],
477 'added': [
477 'added': [
478 FileNode('file_B', content='content_of_ancestor_child')]},
478 FileNode('file_B', content='content_of_ancestor_child')]},
479 {'message': 'ancestor-child-2'},
479 {'message': 'ancestor-child-2'},
480 ]
480 ]
481 commit_ids = backend.create_master_repo(commits)
481 commit_ids = backend.create_master_repo(commits)
482 target = backend.create_repo(heads=['ancestor-child'])
482 target = backend.create_repo(heads=['ancestor-child'])
483 source = backend.create_repo(heads=['change'])
483 source = backend.create_repo(heads=['change'])
484
484
485 response = self.app.post(
485 response = self.app.post(
486 route_path('pullrequest_create', repo_name=source.repo_name),
486 route_path('pullrequest_create', repo_name=source.repo_name),
487 [
487 [
488 ('source_repo', source.repo_name),
488 ('source_repo', source.repo_name),
489 ('source_ref', 'branch:default:' + commit_ids['change']),
489 ('source_ref', 'branch:default:' + commit_ids['change']),
490 ('target_repo', target.repo_name),
490 ('target_repo', target.repo_name),
491 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
491 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
492 ('common_ancestor', commit_ids['ancestor']),
492 ('common_ancestor', commit_ids['ancestor']),
493 ('pullrequest_desc', 'Description'),
493 ('pullrequest_desc', 'Description'),
494 ('pullrequest_title', 'Title'),
494 ('pullrequest_title', 'Title'),
495 ('__start__', 'review_members:sequence'),
495 ('__start__', 'review_members:sequence'),
496 ('__start__', 'reviewer:mapping'),
496 ('__start__', 'reviewer:mapping'),
497 ('user_id', '1'),
497 ('user_id', '1'),
498 ('__start__', 'reasons:sequence'),
498 ('__start__', 'reasons:sequence'),
499 ('reason', 'Some reason'),
499 ('reason', 'Some reason'),
500 ('__end__', 'reasons:sequence'),
500 ('__end__', 'reasons:sequence'),
501 ('mandatory', 'False'),
501 ('mandatory', 'False'),
502 ('__end__', 'reviewer:mapping'),
502 ('__end__', 'reviewer:mapping'),
503 ('__end__', 'review_members:sequence'),
503 ('__end__', 'review_members:sequence'),
504 ('__start__', 'revisions:sequence'),
504 ('__start__', 'revisions:sequence'),
505 ('revisions', commit_ids['change']),
505 ('revisions', commit_ids['change']),
506 ('__end__', 'revisions:sequence'),
506 ('__end__', 'revisions:sequence'),
507 ('user', ''),
507 ('user', ''),
508 ('csrf_token', csrf_token),
508 ('csrf_token', csrf_token),
509 ],
509 ],
510 status=302)
510 status=302)
511
511
512 location = response.headers['Location']
512 location = response.headers['Location']
513
513
514 pull_request_id = location.rsplit('/', 1)[1]
514 pull_request_id = location.rsplit('/', 1)[1]
515 assert pull_request_id != 'new'
515 assert pull_request_id != 'new'
516 pull_request = PullRequest.get(int(pull_request_id))
516 pull_request = PullRequest.get(int(pull_request_id))
517
517
518 # target_ref has to point to the ancestor's commit_id in order to
518 # target_ref has to point to the ancestor's commit_id in order to
519 # show the correct diff
519 # show the correct diff
520 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
520 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
521 assert pull_request.target_ref == expected_target_ref
521 assert pull_request.target_ref == expected_target_ref
522
522
523 # Check generated diff contents
523 # Check generated diff contents
524 response = response.follow()
524 response = response.follow()
525 assert 'content_of_ancestor' not in response.body
525 assert 'content_of_ancestor' not in response.body
526 assert 'content_of_ancestor-child' not in response.body
526 assert 'content_of_ancestor-child' not in response.body
527 assert 'content_of_change' in response.body
527 assert 'content_of_change' in response.body
528
528
529 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
529 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
530 # Clear any previous calls to rcextensions
530 # Clear any previous calls to rcextensions
531 rhodecode.EXTENSIONS.calls.clear()
531 rhodecode.EXTENSIONS.calls.clear()
532
532
533 pull_request = pr_util.create_pull_request(
533 pull_request = pr_util.create_pull_request(
534 approved=True, mergeable=True)
534 approved=True, mergeable=True)
535 pull_request_id = pull_request.pull_request_id
535 pull_request_id = pull_request.pull_request_id
536 repo_name = pull_request.target_repo.scm_instance().name,
536 repo_name = pull_request.target_repo.scm_instance().name,
537
537
538 response = self.app.post(
538 response = self.app.post(
539 route_path('pullrequest_merge',
539 route_path('pullrequest_merge',
540 repo_name=str(repo_name[0]),
540 repo_name=str(repo_name[0]),
541 pull_request_id=pull_request_id),
541 pull_request_id=pull_request_id),
542 params={'csrf_token': csrf_token}).follow()
542 params={'csrf_token': csrf_token}).follow()
543
543
544 pull_request = PullRequest.get(pull_request_id)
544 pull_request = PullRequest.get(pull_request_id)
545
545
546 assert response.status_int == 200
546 assert response.status_int == 200
547 assert pull_request.is_closed()
547 assert pull_request.is_closed()
548 assert_pull_request_status(
548 assert_pull_request_status(
549 pull_request, ChangesetStatus.STATUS_APPROVED)
549 pull_request, ChangesetStatus.STATUS_APPROVED)
550
550
551 # Check the relevant log entries were added
551 # Check the relevant log entries were added
552 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
552 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
553 actions = [log.action for log in user_logs]
553 actions = [log.action for log in user_logs]
554 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
554 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
555 expected_actions = [
555 expected_actions = [
556 u'repo.pull_request.close',
556 u'repo.pull_request.close',
557 u'repo.pull_request.merge',
557 u'repo.pull_request.merge',
558 u'repo.pull_request.comment.create'
558 u'repo.pull_request.comment.create'
559 ]
559 ]
560 assert actions == expected_actions
560 assert actions == expected_actions
561
561
562 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
562 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
563 actions = [log for log in user_logs]
563 actions = [log for log in user_logs]
564 assert actions[-1].action == 'user.push'
564 assert actions[-1].action == 'user.push'
565 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
565 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
566
566
567 # Check post_push rcextension was really executed
567 # Check post_push rcextension was really executed
568 push_calls = rhodecode.EXTENSIONS.calls['post_push']
568 push_calls = rhodecode.EXTENSIONS.calls['post_push']
569 assert len(push_calls) == 1
569 assert len(push_calls) == 1
570 unused_last_call_args, last_call_kwargs = push_calls[0]
570 unused_last_call_args, last_call_kwargs = push_calls[0]
571 assert last_call_kwargs['action'] == 'push'
571 assert last_call_kwargs['action'] == 'push'
572 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
572 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
573
573
574 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
574 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
575 pull_request = pr_util.create_pull_request(mergeable=False)
575 pull_request = pr_util.create_pull_request(mergeable=False)
576 pull_request_id = pull_request.pull_request_id
576 pull_request_id = pull_request.pull_request_id
577 pull_request = PullRequest.get(pull_request_id)
577 pull_request = PullRequest.get(pull_request_id)
578
578
579 response = self.app.post(
579 response = self.app.post(
580 route_path('pullrequest_merge',
580 route_path('pullrequest_merge',
581 repo_name=pull_request.target_repo.scm_instance().name,
581 repo_name=pull_request.target_repo.scm_instance().name,
582 pull_request_id=pull_request.pull_request_id),
582 pull_request_id=pull_request.pull_request_id),
583 params={'csrf_token': csrf_token}).follow()
583 params={'csrf_token': csrf_token}).follow()
584
584
585 assert response.status_int == 200
585 assert response.status_int == 200
586 response.mustcontain(
586 response.mustcontain(
587 'Merge is not currently possible because of below failed checks.')
587 'Merge is not currently possible because of below failed checks.')
588 response.mustcontain('Server-side pull request merging is disabled.')
588 response.mustcontain('Server-side pull request merging is disabled.')
589
589
590 @pytest.mark.skip_backends('svn')
590 @pytest.mark.skip_backends('svn')
591 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
591 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
592 pull_request = pr_util.create_pull_request(mergeable=True)
592 pull_request = pr_util.create_pull_request(mergeable=True)
593 pull_request_id = pull_request.pull_request_id
593 pull_request_id = pull_request.pull_request_id
594 repo_name = pull_request.target_repo.scm_instance().name
594 repo_name = pull_request.target_repo.scm_instance().name
595
595
596 response = self.app.post(
596 response = self.app.post(
597 route_path('pullrequest_merge',
597 route_path('pullrequest_merge',
598 repo_name=repo_name,
598 repo_name=repo_name,
599 pull_request_id=pull_request_id),
599 pull_request_id=pull_request_id),
600 params={'csrf_token': csrf_token}).follow()
600 params={'csrf_token': csrf_token}).follow()
601
601
602 assert response.status_int == 200
602 assert response.status_int == 200
603
603
604 response.mustcontain(
604 response.mustcontain(
605 'Merge is not currently possible because of below failed checks.')
605 'Merge is not currently possible because of below failed checks.')
606 response.mustcontain('Pull request reviewer approval is pending.')
606 response.mustcontain('Pull request reviewer approval is pending.')
607
607
608 def test_merge_pull_request_renders_failure_reason(
608 def test_merge_pull_request_renders_failure_reason(
609 self, user_regular, csrf_token, pr_util):
609 self, user_regular, csrf_token, pr_util):
610 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
610 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
611 pull_request_id = pull_request.pull_request_id
611 pull_request_id = pull_request.pull_request_id
612 repo_name = pull_request.target_repo.scm_instance().name
612 repo_name = pull_request.target_repo.scm_instance().name
613
613
614 model_patcher = mock.patch.multiple(
614 model_patcher = mock.patch.multiple(
615 PullRequestModel,
615 PullRequestModel,
616 merge=mock.Mock(return_value=MergeResponse(
616 merge=mock.Mock(return_value=MergeResponse(
617 True, False, 'STUB_COMMIT_ID', MergeFailureReason.PUSH_FAILED)),
617 True, False, 'STUB_COMMIT_ID', MergeFailureReason.PUSH_FAILED)),
618 merge_status=mock.Mock(return_value=(True, 'WRONG_MESSAGE')))
618 merge_status=mock.Mock(return_value=(True, 'WRONG_MESSAGE')))
619
619
620 with model_patcher:
620 with model_patcher:
621 response = self.app.post(
621 response = self.app.post(
622 route_path('pullrequest_merge',
622 route_path('pullrequest_merge',
623 repo_name=repo_name,
623 repo_name=repo_name,
624 pull_request_id=pull_request_id),
624 pull_request_id=pull_request_id),
625 params={'csrf_token': csrf_token}, status=302)
625 params={'csrf_token': csrf_token}, status=302)
626
626
627 assert_session_flash(response, PullRequestModel.MERGE_STATUS_MESSAGES[
627 assert_session_flash(response, PullRequestModel.MERGE_STATUS_MESSAGES[
628 MergeFailureReason.PUSH_FAILED])
628 MergeFailureReason.PUSH_FAILED])
629
629
630 def test_update_source_revision(self, backend, csrf_token):
630 def test_update_source_revision(self, backend, csrf_token):
631 commits = [
631 commits = [
632 {'message': 'ancestor'},
632 {'message': 'ancestor'},
633 {'message': 'change'},
633 {'message': 'change'},
634 {'message': 'change-2'},
634 {'message': 'change-2'},
635 ]
635 ]
636 commit_ids = backend.create_master_repo(commits)
636 commit_ids = backend.create_master_repo(commits)
637 target = backend.create_repo(heads=['ancestor'])
637 target = backend.create_repo(heads=['ancestor'])
638 source = backend.create_repo(heads=['change'])
638 source = backend.create_repo(heads=['change'])
639
639
640 # create pr from a in source to A in target
640 # create pr from a in source to A in target
641 pull_request = PullRequest()
641 pull_request = PullRequest()
642 pull_request.source_repo = source
642 pull_request.source_repo = source
643 # TODO: johbo: Make sure that we write the source ref this way!
643 # TODO: johbo: Make sure that we write the source ref this way!
644 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
644 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
645 branch=backend.default_branch_name, commit_id=commit_ids['change'])
645 branch=backend.default_branch_name, commit_id=commit_ids['change'])
646 pull_request.target_repo = target
646 pull_request.target_repo = target
647
647
648 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
648 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
649 branch=backend.default_branch_name,
649 branch=backend.default_branch_name,
650 commit_id=commit_ids['ancestor'])
650 commit_id=commit_ids['ancestor'])
651 pull_request.revisions = [commit_ids['change']]
651 pull_request.revisions = [commit_ids['change']]
652 pull_request.title = u"Test"
652 pull_request.title = u"Test"
653 pull_request.description = u"Description"
653 pull_request.description = u"Description"
654 pull_request.author = UserModel().get_by_username(
654 pull_request.author = UserModel().get_by_username(
655 TEST_USER_ADMIN_LOGIN)
655 TEST_USER_ADMIN_LOGIN)
656 Session().add(pull_request)
656 Session().add(pull_request)
657 Session().commit()
657 Session().commit()
658 pull_request_id = pull_request.pull_request_id
658 pull_request_id = pull_request.pull_request_id
659
659
660 # source has ancestor - change - change-2
660 # source has ancestor - change - change-2
661 backend.pull_heads(source, heads=['change-2'])
661 backend.pull_heads(source, heads=['change-2'])
662
662
663 # update PR
663 # update PR
664 self.app.post(
664 self.app.post(
665 route_path('pullrequest_update',
665 route_path('pullrequest_update',
666 repo_name=target.repo_name,
666 repo_name=target.repo_name,
667 pull_request_id=pull_request_id),
667 pull_request_id=pull_request_id),
668 params={'update_commits': 'true',
668 params={'update_commits': 'true',
669 'csrf_token': csrf_token})
669 'csrf_token': csrf_token})
670
670
671 # check that we have now both revisions
671 # check that we have now both revisions
672 pull_request = PullRequest.get(pull_request_id)
672 pull_request = PullRequest.get(pull_request_id)
673 assert pull_request.revisions == [
673 assert pull_request.revisions == [
674 commit_ids['change-2'], commit_ids['change']]
674 commit_ids['change-2'], commit_ids['change']]
675
675
676 # TODO: johbo: this should be a test on its own
676 # TODO: johbo: this should be a test on its own
677 response = self.app.get(route_path(
677 response = self.app.get(route_path(
678 'pullrequest_new',
678 'pullrequest_new',
679 repo_name=target.repo_name))
679 repo_name=target.repo_name))
680 assert response.status_int == 200
680 assert response.status_int == 200
681 assert 'Pull request updated to' in response.body
681 assert 'Pull request updated to' in response.body
682 assert 'with 1 added, 0 removed commits.' in response.body
682 assert 'with 1 added, 0 removed commits.' in response.body
683
683
684 def test_update_target_revision(self, backend, csrf_token):
684 def test_update_target_revision(self, backend, csrf_token):
685 commits = [
685 commits = [
686 {'message': 'ancestor'},
686 {'message': 'ancestor'},
687 {'message': 'change'},
687 {'message': 'change'},
688 {'message': 'ancestor-new', 'parents': ['ancestor']},
688 {'message': 'ancestor-new', 'parents': ['ancestor']},
689 {'message': 'change-rebased'},
689 {'message': 'change-rebased'},
690 ]
690 ]
691 commit_ids = backend.create_master_repo(commits)
691 commit_ids = backend.create_master_repo(commits)
692 target = backend.create_repo(heads=['ancestor'])
692 target = backend.create_repo(heads=['ancestor'])
693 source = backend.create_repo(heads=['change'])
693 source = backend.create_repo(heads=['change'])
694
694
695 # create pr from a in source to A in target
695 # create pr from a in source to A in target
696 pull_request = PullRequest()
696 pull_request = PullRequest()
697 pull_request.source_repo = source
697 pull_request.source_repo = source
698 # TODO: johbo: Make sure that we write the source ref this way!
698 # TODO: johbo: Make sure that we write the source ref this way!
699 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
699 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
700 branch=backend.default_branch_name, commit_id=commit_ids['change'])
700 branch=backend.default_branch_name, commit_id=commit_ids['change'])
701 pull_request.target_repo = target
701 pull_request.target_repo = target
702 # TODO: johbo: Target ref should be branch based, since tip can jump
702 # TODO: johbo: Target ref should be branch based, since tip can jump
703 # from branch to branch
703 # from branch to branch
704 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
704 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
705 branch=backend.default_branch_name,
705 branch=backend.default_branch_name,
706 commit_id=commit_ids['ancestor'])
706 commit_id=commit_ids['ancestor'])
707 pull_request.revisions = [commit_ids['change']]
707 pull_request.revisions = [commit_ids['change']]
708 pull_request.title = u"Test"
708 pull_request.title = u"Test"
709 pull_request.description = u"Description"
709 pull_request.description = u"Description"
710 pull_request.author = UserModel().get_by_username(
710 pull_request.author = UserModel().get_by_username(
711 TEST_USER_ADMIN_LOGIN)
711 TEST_USER_ADMIN_LOGIN)
712 Session().add(pull_request)
712 Session().add(pull_request)
713 Session().commit()
713 Session().commit()
714 pull_request_id = pull_request.pull_request_id
714 pull_request_id = pull_request.pull_request_id
715
715
716 # target has ancestor - ancestor-new
716 # target has ancestor - ancestor-new
717 # source has ancestor - ancestor-new - change-rebased
717 # source has ancestor - ancestor-new - change-rebased
718 backend.pull_heads(target, heads=['ancestor-new'])
718 backend.pull_heads(target, heads=['ancestor-new'])
719 backend.pull_heads(source, heads=['change-rebased'])
719 backend.pull_heads(source, heads=['change-rebased'])
720
720
721 # update PR
721 # update PR
722 self.app.post(
722 self.app.post(
723 route_path('pullrequest_update',
723 route_path('pullrequest_update',
724 repo_name=target.repo_name,
724 repo_name=target.repo_name,
725 pull_request_id=pull_request_id),
725 pull_request_id=pull_request_id),
726 params={'update_commits': 'true',
726 params={'update_commits': 'true',
727 'csrf_token': csrf_token},
727 'csrf_token': csrf_token},
728 status=200)
728 status=200)
729
729
730 # check that we have now both revisions
730 # check that we have now both revisions
731 pull_request = PullRequest.get(pull_request_id)
731 pull_request = PullRequest.get(pull_request_id)
732 assert pull_request.revisions == [commit_ids['change-rebased']]
732 assert pull_request.revisions == [commit_ids['change-rebased']]
733 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
733 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
734 branch=backend.default_branch_name,
734 branch=backend.default_branch_name,
735 commit_id=commit_ids['ancestor-new'])
735 commit_id=commit_ids['ancestor-new'])
736
736
737 # TODO: johbo: This should be a test on its own
737 # TODO: johbo: This should be a test on its own
738 response = self.app.get(route_path(
738 response = self.app.get(route_path(
739 'pullrequest_new',
739 'pullrequest_new',
740 repo_name=target.repo_name))
740 repo_name=target.repo_name))
741 assert response.status_int == 200
741 assert response.status_int == 200
742 assert 'Pull request updated to' in response.body
742 assert 'Pull request updated to' in response.body
743 assert 'with 1 added, 1 removed commits.' in response.body
743 assert 'with 1 added, 1 removed commits.' in response.body
744
744
745 def test_update_of_ancestor_reference(self, backend, csrf_token):
745 def test_update_of_ancestor_reference(self, backend, csrf_token):
746 commits = [
746 commits = [
747 {'message': 'ancestor'},
747 {'message': 'ancestor'},
748 {'message': 'change'},
748 {'message': 'change'},
749 {'message': 'change-2'},
749 {'message': 'change-2'},
750 {'message': 'ancestor-new', 'parents': ['ancestor']},
750 {'message': 'ancestor-new', 'parents': ['ancestor']},
751 {'message': 'change-rebased'},
751 {'message': 'change-rebased'},
752 ]
752 ]
753 commit_ids = backend.create_master_repo(commits)
753 commit_ids = backend.create_master_repo(commits)
754 target = backend.create_repo(heads=['ancestor'])
754 target = backend.create_repo(heads=['ancestor'])
755 source = backend.create_repo(heads=['change'])
755 source = backend.create_repo(heads=['change'])
756
756
757 # create pr from a in source to A in target
757 # create pr from a in source to A in target
758 pull_request = PullRequest()
758 pull_request = PullRequest()
759 pull_request.source_repo = source
759 pull_request.source_repo = source
760 # TODO: johbo: Make sure that we write the source ref this way!
760 # TODO: johbo: Make sure that we write the source ref this way!
761 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
761 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
762 branch=backend.default_branch_name,
762 branch=backend.default_branch_name,
763 commit_id=commit_ids['change'])
763 commit_id=commit_ids['change'])
764 pull_request.target_repo = target
764 pull_request.target_repo = target
765 # TODO: johbo: Target ref should be branch based, since tip can jump
765 # TODO: johbo: Target ref should be branch based, since tip can jump
766 # from branch to branch
766 # from branch to branch
767 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
767 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
768 branch=backend.default_branch_name,
768 branch=backend.default_branch_name,
769 commit_id=commit_ids['ancestor'])
769 commit_id=commit_ids['ancestor'])
770 pull_request.revisions = [commit_ids['change']]
770 pull_request.revisions = [commit_ids['change']]
771 pull_request.title = u"Test"
771 pull_request.title = u"Test"
772 pull_request.description = u"Description"
772 pull_request.description = u"Description"
773 pull_request.author = UserModel().get_by_username(
773 pull_request.author = UserModel().get_by_username(
774 TEST_USER_ADMIN_LOGIN)
774 TEST_USER_ADMIN_LOGIN)
775 Session().add(pull_request)
775 Session().add(pull_request)
776 Session().commit()
776 Session().commit()
777 pull_request_id = pull_request.pull_request_id
777 pull_request_id = pull_request.pull_request_id
778
778
779 # target has ancestor - ancestor-new
779 # target has ancestor - ancestor-new
780 # source has ancestor - ancestor-new - change-rebased
780 # source has ancestor - ancestor-new - change-rebased
781 backend.pull_heads(target, heads=['ancestor-new'])
781 backend.pull_heads(target, heads=['ancestor-new'])
782 backend.pull_heads(source, heads=['change-rebased'])
782 backend.pull_heads(source, heads=['change-rebased'])
783
783
784 # update PR
784 # update PR
785 self.app.post(
785 self.app.post(
786 route_path('pullrequest_update',
786 route_path('pullrequest_update',
787 repo_name=target.repo_name,
787 repo_name=target.repo_name,
788 pull_request_id=pull_request_id),
788 pull_request_id=pull_request_id),
789 params={'update_commits': 'true',
789 params={'update_commits': 'true',
790 'csrf_token': csrf_token},
790 'csrf_token': csrf_token},
791 status=200)
791 status=200)
792
792
793 # Expect the target reference to be updated correctly
793 # Expect the target reference to be updated correctly
794 pull_request = PullRequest.get(pull_request_id)
794 pull_request = PullRequest.get(pull_request_id)
795 assert pull_request.revisions == [commit_ids['change-rebased']]
795 assert pull_request.revisions == [commit_ids['change-rebased']]
796 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
796 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
797 branch=backend.default_branch_name,
797 branch=backend.default_branch_name,
798 commit_id=commit_ids['ancestor-new'])
798 commit_id=commit_ids['ancestor-new'])
799 assert pull_request.target_ref == expected_target_ref
799 assert pull_request.target_ref == expected_target_ref
800
800
801 def test_remove_pull_request_branch(self, backend_git, csrf_token):
801 def test_remove_pull_request_branch(self, backend_git, csrf_token):
802 branch_name = 'development'
802 branch_name = 'development'
803 commits = [
803 commits = [
804 {'message': 'initial-commit'},
804 {'message': 'initial-commit'},
805 {'message': 'old-feature'},
805 {'message': 'old-feature'},
806 {'message': 'new-feature', 'branch': branch_name},
806 {'message': 'new-feature', 'branch': branch_name},
807 ]
807 ]
808 repo = backend_git.create_repo(commits)
808 repo = backend_git.create_repo(commits)
809 commit_ids = backend_git.commit_ids
809 commit_ids = backend_git.commit_ids
810
810
811 pull_request = PullRequest()
811 pull_request = PullRequest()
812 pull_request.source_repo = repo
812 pull_request.source_repo = repo
813 pull_request.target_repo = repo
813 pull_request.target_repo = repo
814 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
814 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
815 branch=branch_name, commit_id=commit_ids['new-feature'])
815 branch=branch_name, commit_id=commit_ids['new-feature'])
816 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
816 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
817 branch=backend_git.default_branch_name,
817 branch=backend_git.default_branch_name,
818 commit_id=commit_ids['old-feature'])
818 commit_id=commit_ids['old-feature'])
819 pull_request.revisions = [commit_ids['new-feature']]
819 pull_request.revisions = [commit_ids['new-feature']]
820 pull_request.title = u"Test"
820 pull_request.title = u"Test"
821 pull_request.description = u"Description"
821 pull_request.description = u"Description"
822 pull_request.author = UserModel().get_by_username(
822 pull_request.author = UserModel().get_by_username(
823 TEST_USER_ADMIN_LOGIN)
823 TEST_USER_ADMIN_LOGIN)
824 Session().add(pull_request)
824 Session().add(pull_request)
825 Session().commit()
825 Session().commit()
826
826
827 vcs = repo.scm_instance()
827 vcs = repo.scm_instance()
828 vcs.remove_ref('refs/heads/{}'.format(branch_name))
828 vcs.remove_ref('refs/heads/{}'.format(branch_name))
829
829
830 response = self.app.get(route_path(
830 response = self.app.get(route_path(
831 'pullrequest_show',
831 'pullrequest_show',
832 repo_name=repo.repo_name,
832 repo_name=repo.repo_name,
833 pull_request_id=pull_request.pull_request_id))
833 pull_request_id=pull_request.pull_request_id))
834
834
835 assert response.status_int == 200
835 assert response.status_int == 200
836 assert_response = AssertResponse(response)
836 assert_response = AssertResponse(response)
837 assert_response.element_contains(
837 assert_response.element_contains(
838 '#changeset_compare_view_content .alert strong',
838 '#changeset_compare_view_content .alert strong',
839 'Missing commits')
839 'Missing commits')
840 assert_response.element_contains(
840 assert_response.element_contains(
841 '#changeset_compare_view_content .alert',
841 '#changeset_compare_view_content .alert',
842 'This pull request cannot be displayed, because one or more'
842 'This pull request cannot be displayed, because one or more'
843 ' commits no longer exist in the source repository.')
843 ' commits no longer exist in the source repository.')
844
844
845 def test_strip_commits_from_pull_request(
845 def test_strip_commits_from_pull_request(
846 self, backend, pr_util, csrf_token):
846 self, backend, pr_util, csrf_token):
847 commits = [
847 commits = [
848 {'message': 'initial-commit'},
848 {'message': 'initial-commit'},
849 {'message': 'old-feature'},
849 {'message': 'old-feature'},
850 {'message': 'new-feature', 'parents': ['initial-commit']},
850 {'message': 'new-feature', 'parents': ['initial-commit']},
851 ]
851 ]
852 pull_request = pr_util.create_pull_request(
852 pull_request = pr_util.create_pull_request(
853 commits, target_head='initial-commit', source_head='new-feature',
853 commits, target_head='initial-commit', source_head='new-feature',
854 revisions=['new-feature'])
854 revisions=['new-feature'])
855
855
856 vcs = pr_util.source_repository.scm_instance()
856 vcs = pr_util.source_repository.scm_instance()
857 if backend.alias == 'git':
857 if backend.alias == 'git':
858 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
858 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
859 else:
859 else:
860 vcs.strip(pr_util.commit_ids['new-feature'])
860 vcs.strip(pr_util.commit_ids['new-feature'])
861
861
862 response = self.app.get(route_path(
862 response = self.app.get(route_path(
863 'pullrequest_show',
863 'pullrequest_show',
864 repo_name=pr_util.target_repository.repo_name,
864 repo_name=pr_util.target_repository.repo_name,
865 pull_request_id=pull_request.pull_request_id))
865 pull_request_id=pull_request.pull_request_id))
866
866
867 assert response.status_int == 200
867 assert response.status_int == 200
868 assert_response = AssertResponse(response)
868 assert_response = AssertResponse(response)
869 assert_response.element_contains(
869 assert_response.element_contains(
870 '#changeset_compare_view_content .alert strong',
870 '#changeset_compare_view_content .alert strong',
871 'Missing commits')
871 'Missing commits')
872 assert_response.element_contains(
872 assert_response.element_contains(
873 '#changeset_compare_view_content .alert',
873 '#changeset_compare_view_content .alert',
874 'This pull request cannot be displayed, because one or more'
874 'This pull request cannot be displayed, because one or more'
875 ' commits no longer exist in the source repository.')
875 ' commits no longer exist in the source repository.')
876 assert_response.element_contains(
876 assert_response.element_contains(
877 '#update_commits',
877 '#update_commits',
878 'Update commits')
878 'Update commits')
879
879
880 def test_strip_commits_and_update(
880 def test_strip_commits_and_update(
881 self, backend, pr_util, csrf_token):
881 self, backend, pr_util, csrf_token):
882 commits = [
882 commits = [
883 {'message': 'initial-commit'},
883 {'message': 'initial-commit'},
884 {'message': 'old-feature'},
884 {'message': 'old-feature'},
885 {'message': 'new-feature', 'parents': ['old-feature']},
885 {'message': 'new-feature', 'parents': ['old-feature']},
886 ]
886 ]
887 pull_request = pr_util.create_pull_request(
887 pull_request = pr_util.create_pull_request(
888 commits, target_head='old-feature', source_head='new-feature',
888 commits, target_head='old-feature', source_head='new-feature',
889 revisions=['new-feature'], mergeable=True)
889 revisions=['new-feature'], mergeable=True)
890
890
891 vcs = pr_util.source_repository.scm_instance()
891 vcs = pr_util.source_repository.scm_instance()
892 if backend.alias == 'git':
892 if backend.alias == 'git':
893 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
893 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
894 else:
894 else:
895 vcs.strip(pr_util.commit_ids['new-feature'])
895 vcs.strip(pr_util.commit_ids['new-feature'])
896
896
897 response = self.app.post(
897 response = self.app.post(
898 route_path('pullrequest_update',
898 route_path('pullrequest_update',
899 repo_name=pull_request.target_repo.repo_name,
899 repo_name=pull_request.target_repo.repo_name,
900 pull_request_id=pull_request.pull_request_id),
900 pull_request_id=pull_request.pull_request_id),
901 params={'update_commits': 'true',
901 params={'update_commits': 'true',
902 'csrf_token': csrf_token})
902 'csrf_token': csrf_token})
903
903
904 assert response.status_int == 200
904 assert response.status_int == 200
905 assert response.body == 'true'
905 assert response.body == 'true'
906
906
907 # Make sure that after update, it won't raise 500 errors
907 # Make sure that after update, it won't raise 500 errors
908 response = self.app.get(route_path(
908 response = self.app.get(route_path(
909 'pullrequest_show',
909 'pullrequest_show',
910 repo_name=pr_util.target_repository.repo_name,
910 repo_name=pr_util.target_repository.repo_name,
911 pull_request_id=pull_request.pull_request_id))
911 pull_request_id=pull_request.pull_request_id))
912
912
913 assert response.status_int == 200
913 assert response.status_int == 200
914 assert_response = AssertResponse(response)
914 assert_response = AssertResponse(response)
915 assert_response.element_contains(
915 assert_response.element_contains(
916 '#changeset_compare_view_content .alert strong',
916 '#changeset_compare_view_content .alert strong',
917 'Missing commits')
917 'Missing commits')
918
918
919 def test_branch_is_a_link(self, pr_util):
919 def test_branch_is_a_link(self, pr_util):
920 pull_request = pr_util.create_pull_request()
920 pull_request = pr_util.create_pull_request()
921 pull_request.source_ref = 'branch:origin:1234567890abcdef'
921 pull_request.source_ref = 'branch:origin:1234567890abcdef'
922 pull_request.target_ref = 'branch:target:abcdef1234567890'
922 pull_request.target_ref = 'branch:target:abcdef1234567890'
923 Session().add(pull_request)
923 Session().add(pull_request)
924 Session().commit()
924 Session().commit()
925
925
926 response = self.app.get(route_path(
926 response = self.app.get(route_path(
927 'pullrequest_show',
927 'pullrequest_show',
928 repo_name=pull_request.target_repo.scm_instance().name,
928 repo_name=pull_request.target_repo.scm_instance().name,
929 pull_request_id=pull_request.pull_request_id))
929 pull_request_id=pull_request.pull_request_id))
930 assert response.status_int == 200
930 assert response.status_int == 200
931 assert_response = AssertResponse(response)
931 assert_response = AssertResponse(response)
932
932
933 origin = assert_response.get_element('.pr-origininfo .tag')
933 origin = assert_response.get_element('.pr-origininfo .tag')
934 origin_children = origin.getchildren()
934 origin_children = origin.getchildren()
935 assert len(origin_children) == 1
935 assert len(origin_children) == 1
936 target = assert_response.get_element('.pr-targetinfo .tag')
936 target = assert_response.get_element('.pr-targetinfo .tag')
937 target_children = target.getchildren()
937 target_children = target.getchildren()
938 assert len(target_children) == 1
938 assert len(target_children) == 1
939
939
940 expected_origin_link = route_path(
940 expected_origin_link = route_path(
941 'repo_changelog',
941 'repo_changelog',
942 repo_name=pull_request.source_repo.scm_instance().name,
942 repo_name=pull_request.source_repo.scm_instance().name,
943 params=dict(branch='origin'))
943 params=dict(branch='origin'))
944 expected_target_link = route_path(
944 expected_target_link = route_path(
945 'repo_changelog',
945 'repo_changelog',
946 repo_name=pull_request.target_repo.scm_instance().name,
946 repo_name=pull_request.target_repo.scm_instance().name,
947 params=dict(branch='target'))
947 params=dict(branch='target'))
948 assert origin_children[0].attrib['href'] == expected_origin_link
948 assert origin_children[0].attrib['href'] == expected_origin_link
949 assert origin_children[0].text == 'branch: origin'
949 assert origin_children[0].text == 'branch: origin'
950 assert target_children[0].attrib['href'] == expected_target_link
950 assert target_children[0].attrib['href'] == expected_target_link
951 assert target_children[0].text == 'branch: target'
951 assert target_children[0].text == 'branch: target'
952
952
953 def test_bookmark_is_not_a_link(self, pr_util):
953 def test_bookmark_is_not_a_link(self, pr_util):
954 pull_request = pr_util.create_pull_request()
954 pull_request = pr_util.create_pull_request()
955 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
955 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
956 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
956 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
957 Session().add(pull_request)
957 Session().add(pull_request)
958 Session().commit()
958 Session().commit()
959
959
960 response = self.app.get(route_path(
960 response = self.app.get(route_path(
961 'pullrequest_show',
961 'pullrequest_show',
962 repo_name=pull_request.target_repo.scm_instance().name,
962 repo_name=pull_request.target_repo.scm_instance().name,
963 pull_request_id=pull_request.pull_request_id))
963 pull_request_id=pull_request.pull_request_id))
964 assert response.status_int == 200
964 assert response.status_int == 200
965 assert_response = AssertResponse(response)
965 assert_response = AssertResponse(response)
966
966
967 origin = assert_response.get_element('.pr-origininfo .tag')
967 origin = assert_response.get_element('.pr-origininfo .tag')
968 assert origin.text.strip() == 'bookmark: origin'
968 assert origin.text.strip() == 'bookmark: origin'
969 assert origin.getchildren() == []
969 assert origin.getchildren() == []
970
970
971 target = assert_response.get_element('.pr-targetinfo .tag')
971 target = assert_response.get_element('.pr-targetinfo .tag')
972 assert target.text.strip() == 'bookmark: target'
972 assert target.text.strip() == 'bookmark: target'
973 assert target.getchildren() == []
973 assert target.getchildren() == []
974
974
975 def test_tag_is_not_a_link(self, pr_util):
975 def test_tag_is_not_a_link(self, pr_util):
976 pull_request = pr_util.create_pull_request()
976 pull_request = pr_util.create_pull_request()
977 pull_request.source_ref = 'tag:origin:1234567890abcdef'
977 pull_request.source_ref = 'tag:origin:1234567890abcdef'
978 pull_request.target_ref = 'tag:target:abcdef1234567890'
978 pull_request.target_ref = 'tag:target:abcdef1234567890'
979 Session().add(pull_request)
979 Session().add(pull_request)
980 Session().commit()
980 Session().commit()
981
981
982 response = self.app.get(route_path(
982 response = self.app.get(route_path(
983 'pullrequest_show',
983 'pullrequest_show',
984 repo_name=pull_request.target_repo.scm_instance().name,
984 repo_name=pull_request.target_repo.scm_instance().name,
985 pull_request_id=pull_request.pull_request_id))
985 pull_request_id=pull_request.pull_request_id))
986 assert response.status_int == 200
986 assert response.status_int == 200
987 assert_response = AssertResponse(response)
987 assert_response = AssertResponse(response)
988
988
989 origin = assert_response.get_element('.pr-origininfo .tag')
989 origin = assert_response.get_element('.pr-origininfo .tag')
990 assert origin.text.strip() == 'tag: origin'
990 assert origin.text.strip() == 'tag: origin'
991 assert origin.getchildren() == []
991 assert origin.getchildren() == []
992
992
993 target = assert_response.get_element('.pr-targetinfo .tag')
993 target = assert_response.get_element('.pr-targetinfo .tag')
994 assert target.text.strip() == 'tag: target'
994 assert target.text.strip() == 'tag: target'
995 assert target.getchildren() == []
995 assert target.getchildren() == []
996
996
997 @pytest.mark.parametrize('mergeable', [True, False])
997 @pytest.mark.parametrize('mergeable', [True, False])
998 def test_shadow_repository_link(
998 def test_shadow_repository_link(
999 self, mergeable, pr_util, http_host_only_stub):
999 self, mergeable, pr_util, http_host_only_stub):
1000 """
1000 """
1001 Check that the pull request summary page displays a link to the shadow
1001 Check that the pull request summary page displays a link to the shadow
1002 repository if the pull request is mergeable. If it is not mergeable
1002 repository if the pull request is mergeable. If it is not mergeable
1003 the link should not be displayed.
1003 the link should not be displayed.
1004 """
1004 """
1005 pull_request = pr_util.create_pull_request(
1005 pull_request = pr_util.create_pull_request(
1006 mergeable=mergeable, enable_notifications=False)
1006 mergeable=mergeable, enable_notifications=False)
1007 target_repo = pull_request.target_repo.scm_instance()
1007 target_repo = pull_request.target_repo.scm_instance()
1008 pr_id = pull_request.pull_request_id
1008 pr_id = pull_request.pull_request_id
1009 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1009 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1010 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1010 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1011
1011
1012 response = self.app.get(route_path(
1012 response = self.app.get(route_path(
1013 'pullrequest_show',
1013 'pullrequest_show',
1014 repo_name=target_repo.name,
1014 repo_name=target_repo.name,
1015 pull_request_id=pr_id))
1015 pull_request_id=pr_id))
1016
1016
1017 assertr = AssertResponse(response)
1017 assertr = AssertResponse(response)
1018 if mergeable:
1018 if mergeable:
1019 assertr.element_value_contains(
1019 assertr.element_value_contains('input.pr-mergeinfo', shadow_url)
1020 'div.pr-mergeinfo input', shadow_url)
1020 assertr.element_value_contains('input.pr-mergeinfo ', 'pr-merge')
1021 assertr.element_value_contains(
1022 'div.pr-mergeinfo input', 'pr-merge')
1023 else:
1021 else:
1024 assertr.no_element_exists('div.pr-mergeinfo')
1022 assertr.no_element_exists('.pr-mergeinfo')
1025
1023
1026
1024
1027 @pytest.mark.usefixtures('app')
1025 @pytest.mark.usefixtures('app')
1028 @pytest.mark.backends("git", "hg")
1026 @pytest.mark.backends("git", "hg")
1029 class TestPullrequestsControllerDelete(object):
1027 class TestPullrequestsControllerDelete(object):
1030 def test_pull_request_delete_button_permissions_admin(
1028 def test_pull_request_delete_button_permissions_admin(
1031 self, autologin_user, user_admin, pr_util):
1029 self, autologin_user, user_admin, pr_util):
1032 pull_request = pr_util.create_pull_request(
1030 pull_request = pr_util.create_pull_request(
1033 author=user_admin.username, enable_notifications=False)
1031 author=user_admin.username, enable_notifications=False)
1034
1032
1035 response = self.app.get(route_path(
1033 response = self.app.get(route_path(
1036 'pullrequest_show',
1034 'pullrequest_show',
1037 repo_name=pull_request.target_repo.scm_instance().name,
1035 repo_name=pull_request.target_repo.scm_instance().name,
1038 pull_request_id=pull_request.pull_request_id))
1036 pull_request_id=pull_request.pull_request_id))
1039
1037
1040 response.mustcontain('id="delete_pullrequest"')
1038 response.mustcontain('id="delete_pullrequest"')
1041 response.mustcontain('Confirm to delete this pull request')
1039 response.mustcontain('Confirm to delete this pull request')
1042
1040
1043 def test_pull_request_delete_button_permissions_owner(
1041 def test_pull_request_delete_button_permissions_owner(
1044 self, autologin_regular_user, user_regular, pr_util):
1042 self, autologin_regular_user, user_regular, pr_util):
1045 pull_request = pr_util.create_pull_request(
1043 pull_request = pr_util.create_pull_request(
1046 author=user_regular.username, enable_notifications=False)
1044 author=user_regular.username, enable_notifications=False)
1047
1045
1048 response = self.app.get(route_path(
1046 response = self.app.get(route_path(
1049 'pullrequest_show',
1047 'pullrequest_show',
1050 repo_name=pull_request.target_repo.scm_instance().name,
1048 repo_name=pull_request.target_repo.scm_instance().name,
1051 pull_request_id=pull_request.pull_request_id))
1049 pull_request_id=pull_request.pull_request_id))
1052
1050
1053 response.mustcontain('id="delete_pullrequest"')
1051 response.mustcontain('id="delete_pullrequest"')
1054 response.mustcontain('Confirm to delete this pull request')
1052 response.mustcontain('Confirm to delete this pull request')
1055
1053
1056 def test_pull_request_delete_button_permissions_forbidden(
1054 def test_pull_request_delete_button_permissions_forbidden(
1057 self, autologin_regular_user, user_regular, user_admin, pr_util):
1055 self, autologin_regular_user, user_regular, user_admin, pr_util):
1058 pull_request = pr_util.create_pull_request(
1056 pull_request = pr_util.create_pull_request(
1059 author=user_admin.username, enable_notifications=False)
1057 author=user_admin.username, enable_notifications=False)
1060
1058
1061 response = self.app.get(route_path(
1059 response = self.app.get(route_path(
1062 'pullrequest_show',
1060 'pullrequest_show',
1063 repo_name=pull_request.target_repo.scm_instance().name,
1061 repo_name=pull_request.target_repo.scm_instance().name,
1064 pull_request_id=pull_request.pull_request_id))
1062 pull_request_id=pull_request.pull_request_id))
1065 response.mustcontain(no=['id="delete_pullrequest"'])
1063 response.mustcontain(no=['id="delete_pullrequest"'])
1066 response.mustcontain(no=['Confirm to delete this pull request'])
1064 response.mustcontain(no=['Confirm to delete this pull request'])
1067
1065
1068 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1066 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1069 self, autologin_regular_user, user_regular, user_admin, pr_util,
1067 self, autologin_regular_user, user_regular, user_admin, pr_util,
1070 user_util):
1068 user_util):
1071
1069
1072 pull_request = pr_util.create_pull_request(
1070 pull_request = pr_util.create_pull_request(
1073 author=user_admin.username, enable_notifications=False)
1071 author=user_admin.username, enable_notifications=False)
1074
1072
1075 user_util.grant_user_permission_to_repo(
1073 user_util.grant_user_permission_to_repo(
1076 pull_request.target_repo, user_regular,
1074 pull_request.target_repo, user_regular,
1077 'repository.write')
1075 'repository.write')
1078
1076
1079 response = self.app.get(route_path(
1077 response = self.app.get(route_path(
1080 'pullrequest_show',
1078 'pullrequest_show',
1081 repo_name=pull_request.target_repo.scm_instance().name,
1079 repo_name=pull_request.target_repo.scm_instance().name,
1082 pull_request_id=pull_request.pull_request_id))
1080 pull_request_id=pull_request.pull_request_id))
1083
1081
1084 response.mustcontain('id="open_edit_pullrequest"')
1082 response.mustcontain('id="open_edit_pullrequest"')
1085 response.mustcontain('id="delete_pullrequest"')
1083 response.mustcontain('id="delete_pullrequest"')
1086 response.mustcontain(no=['Confirm to delete this pull request'])
1084 response.mustcontain(no=['Confirm to delete this pull request'])
1087
1085
1088 def test_delete_comment_returns_404_if_comment_does_not_exist(
1086 def test_delete_comment_returns_404_if_comment_does_not_exist(
1089 self, autologin_user, pr_util, user_admin):
1087 self, autologin_user, pr_util, user_admin):
1090
1088
1091 pull_request = pr_util.create_pull_request(
1089 pull_request = pr_util.create_pull_request(
1092 author=user_admin.username, enable_notifications=False)
1090 author=user_admin.username, enable_notifications=False)
1093
1091
1094 self.app.get(route_path(
1092 self.app.get(route_path(
1095 'pullrequest_comment_delete',
1093 'pullrequest_comment_delete',
1096 repo_name=pull_request.target_repo.scm_instance().name,
1094 repo_name=pull_request.target_repo.scm_instance().name,
1097 pull_request_id=pull_request.pull_request_id,
1095 pull_request_id=pull_request.pull_request_id,
1098 comment_id=1024404), status=404)
1096 comment_id=1024404), status=404)
1099
1097
1100
1098
1101 def assert_pull_request_status(pull_request, expected_status):
1099 def assert_pull_request_status(pull_request, expected_status):
1102 status = ChangesetStatusModel().calculated_review_status(
1100 status = ChangesetStatusModel().calculated_review_status(
1103 pull_request=pull_request)
1101 pull_request=pull_request)
1104 assert status == expected_status
1102 assert status == expected_status
1105
1103
1106
1104
1107 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1105 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1108 @pytest.mark.usefixtures("autologin_user")
1106 @pytest.mark.usefixtures("autologin_user")
1109 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1107 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1110 response = app.get(
1108 response = app.get(
1111 route_path(route, repo_name=backend_svn.repo_name), status=404)
1109 route_path(route, repo_name=backend_svn.repo_name), status=404)
1112
1110
General Comments 0
You need to be logged in to leave comments. Login now