##// END OF EJS Templates
tests: fixed some tests after sidebar introduction
marcink -
r4486:8b48fea3 default
parent child Browse files
Show More
@@ -1,667 +1,672 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 lxml.html
23 import lxml.html
24
24
25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 from rhodecode.tests import assert_session_flash
26 from rhodecode.tests import assert_session_flash
27 from rhodecode.tests.utils import AssertResponse, commit_change
27 from rhodecode.tests.utils import AssertResponse, commit_change
28
28
29
29
30 def route_path(name, params=None, **kwargs):
30 def route_path(name, params=None, **kwargs):
31 import urllib
31 import urllib
32
32
33 base_url = {
33 base_url = {
34 'repo_compare_select': '/{repo_name}/compare',
34 'repo_compare_select': '/{repo_name}/compare',
35 'repo_compare': '/{repo_name}/compare/{source_ref_type}@{source_ref}...{target_ref_type}@{target_ref}',
35 'repo_compare': '/{repo_name}/compare/{source_ref_type}@{source_ref}...{target_ref_type}@{target_ref}',
36 }[name].format(**kwargs)
36 }[name].format(**kwargs)
37
37
38 if params:
38 if params:
39 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
39 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
40 return base_url
40 return base_url
41
41
42
42
43 @pytest.mark.usefixtures("autologin_user", "app")
43 @pytest.mark.usefixtures("autologin_user", "app")
44 class TestCompareView(object):
44 class TestCompareView(object):
45
45
46 def test_compare_index_is_reached_at_least_once(self, backend):
46 def test_compare_index_is_reached_at_least_once(self, backend):
47 repo = backend.repo
47 repo = backend.repo
48 self.app.get(
48 self.app.get(
49 route_path('repo_compare_select', repo_name=repo.repo_name))
49 route_path('repo_compare_select', repo_name=repo.repo_name))
50
50
51 @pytest.mark.xfail_backends("svn", reason="Requires pull")
51 @pytest.mark.xfail_backends("svn", reason="Requires pull")
52 def test_compare_remote_with_different_commit_indexes(self, backend):
52 def test_compare_remote_with_different_commit_indexes(self, backend):
53 # Preparing the following repository structure:
53 # Preparing the following repository structure:
54 #
54 #
55 # Origin repository has two commits:
55 # Origin repository has two commits:
56 #
56 #
57 # 0 1
57 # 0 1
58 # A -- D
58 # A -- D
59 #
59 #
60 # The fork of it has a few more commits and "D" has a commit index
60 # The fork of it has a few more commits and "D" has a commit index
61 # which does not exist in origin.
61 # which does not exist in origin.
62 #
62 #
63 # 0 1 2 3 4
63 # 0 1 2 3 4
64 # A -- -- -- D -- E
64 # A -- -- -- D -- E
65 # \- B -- C
65 # \- B -- C
66 #
66 #
67
67
68 fork = backend.create_repo()
68 fork = backend.create_repo()
69
69
70 # prepare fork
70 # prepare fork
71 commit0 = commit_change(
71 commit0 = commit_change(
72 fork.repo_name, filename='file1', content='A',
72 fork.repo_name, filename='file1', content='A',
73 message='A', vcs_type=backend.alias, parent=None, newfile=True)
73 message='A', vcs_type=backend.alias, parent=None, newfile=True)
74
74
75 commit1 = commit_change(
75 commit1 = commit_change(
76 fork.repo_name, filename='file1', content='B',
76 fork.repo_name, filename='file1', content='B',
77 message='B, child of A', vcs_type=backend.alias, parent=commit0)
77 message='B, child of A', vcs_type=backend.alias, parent=commit0)
78
78
79 commit_change( # commit 2
79 commit_change( # commit 2
80 fork.repo_name, filename='file1', content='C',
80 fork.repo_name, filename='file1', content='C',
81 message='C, child of B', vcs_type=backend.alias, parent=commit1)
81 message='C, child of B', vcs_type=backend.alias, parent=commit1)
82
82
83 commit3 = commit_change(
83 commit3 = commit_change(
84 fork.repo_name, filename='file1', content='D',
84 fork.repo_name, filename='file1', content='D',
85 message='D, child of A', vcs_type=backend.alias, parent=commit0)
85 message='D, child of A', vcs_type=backend.alias, parent=commit0)
86
86
87 commit4 = commit_change(
87 commit4 = commit_change(
88 fork.repo_name, filename='file1', content='E',
88 fork.repo_name, filename='file1', content='E',
89 message='E, child of D', vcs_type=backend.alias, parent=commit3)
89 message='E, child of D', vcs_type=backend.alias, parent=commit3)
90
90
91 # prepare origin repository, taking just the history up to D
91 # prepare origin repository, taking just the history up to D
92 origin = backend.create_repo()
92 origin = backend.create_repo()
93
93
94 origin_repo = origin.scm_instance(cache=False)
94 origin_repo = origin.scm_instance(cache=False)
95 origin_repo.config.clear_section('hooks')
95 origin_repo.config.clear_section('hooks')
96 origin_repo.pull(fork.repo_full_path, commit_ids=[commit3.raw_id])
96 origin_repo.pull(fork.repo_full_path, commit_ids=[commit3.raw_id])
97 origin_repo = origin.scm_instance(cache=False) # cache rebuild
97 origin_repo = origin.scm_instance(cache=False) # cache rebuild
98
98
99 # Verify test fixture setup
99 # Verify test fixture setup
100 # This does not work for git
100 # This does not work for git
101 if backend.alias != 'git':
101 if backend.alias != 'git':
102 assert 5 == len(fork.scm_instance().commit_ids)
102 assert 5 == len(fork.scm_instance().commit_ids)
103 assert 2 == len(origin_repo.commit_ids)
103 assert 2 == len(origin_repo.commit_ids)
104
104
105 # Comparing the revisions
105 # Comparing the revisions
106 response = self.app.get(
106 response = self.app.get(
107 route_path('repo_compare',
107 route_path('repo_compare',
108 repo_name=origin.repo_name,
108 repo_name=origin.repo_name,
109 source_ref_type="rev", source_ref=commit3.raw_id,
109 source_ref_type="rev", source_ref=commit3.raw_id,
110 target_ref_type="rev", target_ref=commit4.raw_id,
110 target_ref_type="rev", target_ref=commit4.raw_id,
111 params=dict(merge='1', target_repo=fork.repo_name)
111 params=dict(merge='1', target_repo=fork.repo_name)
112 ))
112 ))
113
113
114 compare_page = ComparePage(response)
114 compare_page = ComparePage(response)
115 compare_page.contains_commits([commit4])
115 compare_page.contains_commits([commit4])
116
116
117 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
117 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
118 def test_compare_forks_on_branch_extra_commits(self, backend):
118 def test_compare_forks_on_branch_extra_commits(self, backend):
119 repo1 = backend.create_repo()
119 repo1 = backend.create_repo()
120
120
121 # commit something !
121 # commit something !
122 commit0 = commit_change(
122 commit0 = commit_change(
123 repo1.repo_name, filename='file1', content='line1\n',
123 repo1.repo_name, filename='file1', content='line1\n',
124 message='commit1', vcs_type=backend.alias, parent=None,
124 message='commit1', vcs_type=backend.alias, parent=None,
125 newfile=True)
125 newfile=True)
126
126
127 # fork this repo
127 # fork this repo
128 repo2 = backend.create_fork()
128 repo2 = backend.create_fork()
129
129
130 # add two extra commit into fork
130 # add two extra commit into fork
131 commit1 = commit_change(
131 commit1 = commit_change(
132 repo2.repo_name, filename='file1', content='line1\nline2\n',
132 repo2.repo_name, filename='file1', content='line1\nline2\n',
133 message='commit2', vcs_type=backend.alias, parent=commit0)
133 message='commit2', vcs_type=backend.alias, parent=commit0)
134
134
135 commit2 = commit_change(
135 commit2 = commit_change(
136 repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
136 repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
137 message='commit3', vcs_type=backend.alias, parent=commit1)
137 message='commit3', vcs_type=backend.alias, parent=commit1)
138
138
139 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
139 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
140 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
140 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
141
141
142 response = self.app.get(
142 response = self.app.get(
143 route_path('repo_compare',
143 route_path('repo_compare',
144 repo_name=repo1.repo_name,
144 repo_name=repo1.repo_name,
145 source_ref_type="branch", source_ref=commit_id2,
145 source_ref_type="branch", source_ref=commit_id2,
146 target_ref_type="branch", target_ref=commit_id1,
146 target_ref_type="branch", target_ref=commit_id1,
147 params=dict(merge='1', target_repo=repo2.repo_name)
147 params=dict(merge='1', target_repo=repo2.repo_name)
148 ))
148 ))
149
149
150 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id2))
150 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id2))
151 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id1))
151 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id1))
152
152
153 compare_page = ComparePage(response)
153 compare_page = ComparePage(response)
154 compare_page.contains_change_summary(1, 2, 0)
154 compare_page.contains_change_summary(1, 2, 0)
155 compare_page.contains_commits([commit1, commit2])
155 compare_page.contains_commits([commit1, commit2])
156
156
157 anchor = 'a_c-{}-826e8142e6ba'.format(commit0.short_id)
157 anchor = 'a_c-{}-826e8142e6ba'.format(commit0.short_id)
158 compare_page.contains_file_links_and_anchors([('file1', anchor), ])
158 compare_page.contains_file_links_and_anchors([('file1', anchor), ])
159
159
160 # Swap is removed when comparing branches since it's a PR feature and
160 # Swap is removed when comparing branches since it's a PR feature and
161 # it is then a preview mode
161 # it is then a preview mode
162 compare_page.swap_is_hidden()
162 compare_page.swap_is_hidden()
163 compare_page.target_source_are_disabled()
163 compare_page.target_source_are_disabled()
164
164
165 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
165 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
166 def test_compare_forks_on_branch_extra_commits_origin_has_incomming(self, backend):
166 def test_compare_forks_on_branch_extra_commits_origin_has_incomming(self, backend):
167 repo1 = backend.create_repo()
167 repo1 = backend.create_repo()
168
168
169 # commit something !
169 # commit something !
170 commit0 = commit_change(
170 commit0 = commit_change(
171 repo1.repo_name, filename='file1', content='line1\n',
171 repo1.repo_name, filename='file1', content='line1\n',
172 message='commit1', vcs_type=backend.alias, parent=None,
172 message='commit1', vcs_type=backend.alias, parent=None,
173 newfile=True)
173 newfile=True)
174
174
175 # fork this repo
175 # fork this repo
176 repo2 = backend.create_fork()
176 repo2 = backend.create_fork()
177
177
178 # now commit something to origin repo
178 # now commit something to origin repo
179 commit_change(
179 commit_change(
180 repo1.repo_name, filename='file2', content='line1file2\n',
180 repo1.repo_name, filename='file2', content='line1file2\n',
181 message='commit2', vcs_type=backend.alias, parent=commit0,
181 message='commit2', vcs_type=backend.alias, parent=commit0,
182 newfile=True)
182 newfile=True)
183
183
184 # add two extra commit into fork
184 # add two extra commit into fork
185 commit1 = commit_change(
185 commit1 = commit_change(
186 repo2.repo_name, filename='file1', content='line1\nline2\n',
186 repo2.repo_name, filename='file1', content='line1\nline2\n',
187 message='commit2', vcs_type=backend.alias, parent=commit0)
187 message='commit2', vcs_type=backend.alias, parent=commit0)
188
188
189 commit2 = commit_change(
189 commit2 = commit_change(
190 repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
190 repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
191 message='commit3', vcs_type=backend.alias, parent=commit1)
191 message='commit3', vcs_type=backend.alias, parent=commit1)
192
192
193 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
193 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
194 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
194 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
195
195
196 response = self.app.get(
196 response = self.app.get(
197 route_path('repo_compare',
197 route_path('repo_compare',
198 repo_name=repo1.repo_name,
198 repo_name=repo1.repo_name,
199 source_ref_type="branch", source_ref=commit_id2,
199 source_ref_type="branch", source_ref=commit_id2,
200 target_ref_type="branch", target_ref=commit_id1,
200 target_ref_type="branch", target_ref=commit_id1,
201 params=dict(merge='1', target_repo=repo2.repo_name),
201 params=dict(merge='1', target_repo=repo2.repo_name),
202 ))
202 ))
203
203
204 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id2))
204 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id2))
205 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id1))
205 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id1))
206
206
207 compare_page = ComparePage(response)
207 compare_page = ComparePage(response)
208 compare_page.contains_change_summary(1, 2, 0)
208 compare_page.contains_change_summary(1, 2, 0)
209 compare_page.contains_commits([commit1, commit2])
209 compare_page.contains_commits([commit1, commit2])
210 anchor = 'a_c-{}-826e8142e6ba'.format(commit0.short_id)
210 anchor = 'a_c-{}-826e8142e6ba'.format(commit0.short_id)
211 compare_page.contains_file_links_and_anchors([('file1', anchor), ])
211 compare_page.contains_file_links_and_anchors([('file1', anchor), ])
212
212
213 # Swap is removed when comparing branches since it's a PR feature and
213 # Swap is removed when comparing branches since it's a PR feature and
214 # it is then a preview mode
214 # it is then a preview mode
215 compare_page.swap_is_hidden()
215 compare_page.swap_is_hidden()
216 compare_page.target_source_are_disabled()
216 compare_page.target_source_are_disabled()
217
217
218 @pytest.mark.xfail_backends("svn")
218 @pytest.mark.xfail_backends("svn")
219 # TODO(marcink): no svn support for compare two seperate repos
219 # TODO(marcink): no svn support for compare two seperate repos
220 def test_compare_of_unrelated_forks(self, backend):
220 def test_compare_of_unrelated_forks(self, backend):
221 orig = backend.create_repo(number_of_commits=1)
221 orig = backend.create_repo(number_of_commits=1)
222 fork = backend.create_repo(number_of_commits=1)
222 fork = backend.create_repo(number_of_commits=1)
223
223
224 response = self.app.get(
224 response = self.app.get(
225 route_path('repo_compare',
225 route_path('repo_compare',
226 repo_name=orig.repo_name,
226 repo_name=orig.repo_name,
227 source_ref_type="rev", source_ref="tip",
227 source_ref_type="rev", source_ref="tip",
228 target_ref_type="rev", target_ref="tip",
228 target_ref_type="rev", target_ref="tip",
229 params=dict(merge='1', target_repo=fork.repo_name),
229 params=dict(merge='1', target_repo=fork.repo_name),
230 ),
230 ),
231 status=302)
231 status=302)
232 response = response.follow()
232 response = response.follow()
233 response.mustcontain("Repositories unrelated.")
233 response.mustcontain("Repositories unrelated.")
234
234
235 @pytest.mark.xfail_backends("svn")
235 @pytest.mark.xfail_backends("svn")
236 def test_compare_cherry_pick_commits_from_bottom(self, backend):
236 def test_compare_cherry_pick_commits_from_bottom(self, backend):
237
237
238 # repo1:
238 # repo1:
239 # commit0:
239 # commit0:
240 # commit1:
240 # commit1:
241 # repo1-fork- in which we will cherry pick bottom commits
241 # repo1-fork- in which we will cherry pick bottom commits
242 # commit0:
242 # commit0:
243 # commit1:
243 # commit1:
244 # commit2: x
244 # commit2: x
245 # commit3: x
245 # commit3: x
246 # commit4: x
246 # commit4: x
247 # commit5:
247 # commit5:
248 # make repo1, and commit1+commit2
248 # make repo1, and commit1+commit2
249
249
250 repo1 = backend.create_repo()
250 repo1 = backend.create_repo()
251
251
252 # commit something !
252 # commit something !
253 commit0 = commit_change(
253 commit0 = commit_change(
254 repo1.repo_name, filename='file1', content='line1\n',
254 repo1.repo_name, filename='file1', content='line1\n',
255 message='commit1', vcs_type=backend.alias, parent=None,
255 message='commit1', vcs_type=backend.alias, parent=None,
256 newfile=True)
256 newfile=True)
257 commit1 = commit_change(
257 commit1 = commit_change(
258 repo1.repo_name, filename='file1', content='line1\nline2\n',
258 repo1.repo_name, filename='file1', content='line1\nline2\n',
259 message='commit2', vcs_type=backend.alias, parent=commit0)
259 message='commit2', vcs_type=backend.alias, parent=commit0)
260
260
261 # fork this repo
261 # fork this repo
262 repo2 = backend.create_fork()
262 repo2 = backend.create_fork()
263
263
264 # now make commit3-6
264 # now make commit3-6
265 commit2 = commit_change(
265 commit2 = commit_change(
266 repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
266 repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
267 message='commit3', vcs_type=backend.alias, parent=commit1)
267 message='commit3', vcs_type=backend.alias, parent=commit1)
268 commit3 = commit_change(
268 commit3 = commit_change(
269 repo1.repo_name, filename='file1',
269 repo1.repo_name, filename='file1',
270 content='line1\nline2\nline3\nline4\n', message='commit4',
270 content='line1\nline2\nline3\nline4\n', message='commit4',
271 vcs_type=backend.alias, parent=commit2)
271 vcs_type=backend.alias, parent=commit2)
272 commit4 = commit_change(
272 commit4 = commit_change(
273 repo1.repo_name, filename='file1',
273 repo1.repo_name, filename='file1',
274 content='line1\nline2\nline3\nline4\nline5\n', message='commit5',
274 content='line1\nline2\nline3\nline4\nline5\n', message='commit5',
275 vcs_type=backend.alias, parent=commit3)
275 vcs_type=backend.alias, parent=commit3)
276 commit_change( # commit 5
276 commit_change( # commit 5
277 repo1.repo_name, filename='file1',
277 repo1.repo_name, filename='file1',
278 content='line1\nline2\nline3\nline4\nline5\nline6\n',
278 content='line1\nline2\nline3\nline4\nline5\nline6\n',
279 message='commit6', vcs_type=backend.alias, parent=commit4)
279 message='commit6', vcs_type=backend.alias, parent=commit4)
280
280
281 response = self.app.get(
281 response = self.app.get(
282 route_path('repo_compare',
282 route_path('repo_compare',
283 repo_name=repo2.repo_name,
283 repo_name=repo2.repo_name,
284 # parent of commit2, in target repo2
284 # parent of commit2, in target repo2
285 source_ref_type="rev", source_ref=commit1.raw_id,
285 source_ref_type="rev", source_ref=commit1.raw_id,
286 target_ref_type="rev", target_ref=commit4.raw_id,
286 target_ref_type="rev", target_ref=commit4.raw_id,
287 params=dict(merge='1', target_repo=repo1.repo_name),
287 params=dict(merge='1', target_repo=repo1.repo_name),
288 ))
288 ))
289 response.mustcontain('%s@%s' % (repo2.repo_name, commit1.short_id))
289 response.mustcontain('%s@%s' % (repo2.repo_name, commit1.short_id))
290 response.mustcontain('%s@%s' % (repo1.repo_name, commit4.short_id))
290 response.mustcontain('%s@%s' % (repo1.repo_name, commit4.short_id))
291
291
292 # files
292 # files
293 compare_page = ComparePage(response)
293 compare_page = ComparePage(response)
294 compare_page.contains_change_summary(1, 3, 0)
294 compare_page.contains_change_summary(1, 3, 0)
295 compare_page.contains_commits([commit2, commit3, commit4])
295 compare_page.contains_commits([commit2, commit3, commit4])
296 anchor = 'a_c-{}-826e8142e6ba'.format(commit1.short_id)
296 anchor = 'a_c-{}-826e8142e6ba'.format(commit1.short_id)
297 compare_page.contains_file_links_and_anchors([('file1', anchor),])
297 compare_page.contains_file_links_and_anchors([('file1', anchor),])
298
298
299 @pytest.mark.xfail_backends("svn")
299 @pytest.mark.xfail_backends("svn")
300 def test_compare_cherry_pick_commits_from_top(self, backend):
300 def test_compare_cherry_pick_commits_from_top(self, backend):
301 # repo1:
301 # repo1:
302 # commit0:
302 # commit0:
303 # commit1:
303 # commit1:
304 # repo1-fork- in which we will cherry pick bottom commits
304 # repo1-fork- in which we will cherry pick bottom commits
305 # commit0:
305 # commit0:
306 # commit1:
306 # commit1:
307 # commit2:
307 # commit2:
308 # commit3: x
308 # commit3: x
309 # commit4: x
309 # commit4: x
310 # commit5: x
310 # commit5: x
311
311
312 # make repo1, and commit1+commit2
312 # make repo1, and commit1+commit2
313 repo1 = backend.create_repo()
313 repo1 = backend.create_repo()
314
314
315 # commit something !
315 # commit something !
316 commit0 = commit_change(
316 commit0 = commit_change(
317 repo1.repo_name, filename='file1', content='line1\n',
317 repo1.repo_name, filename='file1', content='line1\n',
318 message='commit1', vcs_type=backend.alias, parent=None,
318 message='commit1', vcs_type=backend.alias, parent=None,
319 newfile=True)
319 newfile=True)
320 commit1 = commit_change(
320 commit1 = commit_change(
321 repo1.repo_name, filename='file1', content='line1\nline2\n',
321 repo1.repo_name, filename='file1', content='line1\nline2\n',
322 message='commit2', vcs_type=backend.alias, parent=commit0)
322 message='commit2', vcs_type=backend.alias, parent=commit0)
323
323
324 # fork this repo
324 # fork this repo
325 backend.create_fork()
325 backend.create_fork()
326
326
327 # now make commit3-6
327 # now make commit3-6
328 commit2 = commit_change(
328 commit2 = commit_change(
329 repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
329 repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
330 message='commit3', vcs_type=backend.alias, parent=commit1)
330 message='commit3', vcs_type=backend.alias, parent=commit1)
331 commit3 = commit_change(
331 commit3 = commit_change(
332 repo1.repo_name, filename='file1',
332 repo1.repo_name, filename='file1',
333 content='line1\nline2\nline3\nline4\n', message='commit4',
333 content='line1\nline2\nline3\nline4\n', message='commit4',
334 vcs_type=backend.alias, parent=commit2)
334 vcs_type=backend.alias, parent=commit2)
335 commit4 = commit_change(
335 commit4 = commit_change(
336 repo1.repo_name, filename='file1',
336 repo1.repo_name, filename='file1',
337 content='line1\nline2\nline3\nline4\nline5\n', message='commit5',
337 content='line1\nline2\nline3\nline4\nline5\n', message='commit5',
338 vcs_type=backend.alias, parent=commit3)
338 vcs_type=backend.alias, parent=commit3)
339 commit5 = commit_change(
339 commit5 = commit_change(
340 repo1.repo_name, filename='file1',
340 repo1.repo_name, filename='file1',
341 content='line1\nline2\nline3\nline4\nline5\nline6\n',
341 content='line1\nline2\nline3\nline4\nline5\nline6\n',
342 message='commit6', vcs_type=backend.alias, parent=commit4)
342 message='commit6', vcs_type=backend.alias, parent=commit4)
343
343
344 response = self.app.get(
344 response = self.app.get(
345 route_path('repo_compare',
345 route_path('repo_compare',
346 repo_name=repo1.repo_name,
346 repo_name=repo1.repo_name,
347 # parent of commit3, not in source repo2
347 # parent of commit3, not in source repo2
348 source_ref_type="rev", source_ref=commit2.raw_id,
348 source_ref_type="rev", source_ref=commit2.raw_id,
349 target_ref_type="rev", target_ref=commit5.raw_id,
349 target_ref_type="rev", target_ref=commit5.raw_id,
350 params=dict(merge='1'),))
350 params=dict(merge='1'),))
351
351
352 response.mustcontain('%s@%s' % (repo1.repo_name, commit2.short_id))
352 response.mustcontain('%s@%s' % (repo1.repo_name, commit2.short_id))
353 response.mustcontain('%s@%s' % (repo1.repo_name, commit5.short_id))
353 response.mustcontain('%s@%s' % (repo1.repo_name, commit5.short_id))
354
354
355 compare_page = ComparePage(response)
355 compare_page = ComparePage(response)
356 compare_page.contains_change_summary(1, 3, 0)
356 compare_page.contains_change_summary(1, 3, 0)
357 compare_page.contains_commits([commit3, commit4, commit5])
357 compare_page.contains_commits([commit3, commit4, commit5])
358
358
359 # files
359 # files
360 anchor = 'a_c-{}-826e8142e6ba'.format(commit2.short_id)
360 anchor = 'a_c-{}-826e8142e6ba'.format(commit2.short_id)
361 compare_page.contains_file_links_and_anchors([('file1', anchor),])
361 compare_page.contains_file_links_and_anchors([('file1', anchor),])
362
362
363 @pytest.mark.xfail_backends("svn")
363 @pytest.mark.xfail_backends("svn")
364 def test_compare_remote_branches(self, backend):
364 def test_compare_remote_branches(self, backend):
365 repo1 = backend.repo
365 repo1 = backend.repo
366 repo2 = backend.create_fork()
366 repo2 = backend.create_fork()
367
367
368 commit_id1 = repo1.get_commit(commit_idx=3).raw_id
368 commit_id1 = repo1.get_commit(commit_idx=3).raw_id
369 commit_id1_short = repo1.get_commit(commit_idx=3).short_id
369 commit_id1_short = repo1.get_commit(commit_idx=3).short_id
370 commit_id2 = repo1.get_commit(commit_idx=6).raw_id
370 commit_id2 = repo1.get_commit(commit_idx=6).raw_id
371 commit_id2_short = repo1.get_commit(commit_idx=6).short_id
371 commit_id2_short = repo1.get_commit(commit_idx=6).short_id
372
372
373 response = self.app.get(
373 response = self.app.get(
374 route_path('repo_compare',
374 route_path('repo_compare',
375 repo_name=repo1.repo_name,
375 repo_name=repo1.repo_name,
376 source_ref_type="rev", source_ref=commit_id1,
376 source_ref_type="rev", source_ref=commit_id1,
377 target_ref_type="rev", target_ref=commit_id2,
377 target_ref_type="rev", target_ref=commit_id2,
378 params=dict(merge='1', target_repo=repo2.repo_name),
378 params=dict(merge='1', target_repo=repo2.repo_name),
379 ))
379 ))
380
380
381 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id1))
381 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id1))
382 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id2))
382 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id2))
383
383
384 compare_page = ComparePage(response)
384 compare_page = ComparePage(response)
385
385
386 # outgoing commits between those commits
386 # outgoing commits between those commits
387 compare_page.contains_commits(
387 compare_page.contains_commits(
388 [repo2.get_commit(commit_idx=x) for x in [4, 5, 6]])
388 [repo2.get_commit(commit_idx=x) for x in [4, 5, 6]])
389
389
390 # files
390 # files
391 compare_page.contains_file_links_and_anchors([
391 compare_page.contains_file_links_and_anchors([
392 ('vcs/backends/hg.py', 'a_c-{}-9c390eb52cd6'.format(commit_id2_short)),
392 ('vcs/backends/hg.py', 'a_c-{}-9c390eb52cd6'.format(commit_id2_short)),
393 ('vcs/backends/__init__.py', 'a_c-{}-41b41c1f2796'.format(commit_id1_short)),
393 ('vcs/backends/__init__.py', 'a_c-{}-41b41c1f2796'.format(commit_id1_short)),
394 ('vcs/backends/base.py', 'a_c-{}-2f574d260608'.format(commit_id1_short)),
394 ('vcs/backends/base.py', 'a_c-{}-2f574d260608'.format(commit_id1_short)),
395 ])
395 ])
396
396
397 @pytest.mark.xfail_backends("svn")
397 @pytest.mark.xfail_backends("svn")
398 def test_source_repo_new_commits_after_forking_simple_diff(self, backend):
398 def test_source_repo_new_commits_after_forking_simple_diff(self, backend):
399 repo1 = backend.create_repo()
399 repo1 = backend.create_repo()
400 r1_name = repo1.repo_name
400 r1_name = repo1.repo_name
401
401
402 commit0 = commit_change(
402 commit0 = commit_change(
403 repo=r1_name, filename='file1',
403 repo=r1_name, filename='file1',
404 content='line1', message='commit1', vcs_type=backend.alias,
404 content='line1', message='commit1', vcs_type=backend.alias,
405 newfile=True)
405 newfile=True)
406 assert repo1.scm_instance().commit_ids == [commit0.raw_id]
406 assert repo1.scm_instance().commit_ids == [commit0.raw_id]
407
407
408 # fork the repo1
408 # fork the repo1
409 repo2 = backend.create_fork()
409 repo2 = backend.create_fork()
410 assert repo2.scm_instance().commit_ids == [commit0.raw_id]
410 assert repo2.scm_instance().commit_ids == [commit0.raw_id]
411
411
412 self.r2_id = repo2.repo_id
412 self.r2_id = repo2.repo_id
413 r2_name = repo2.repo_name
413 r2_name = repo2.repo_name
414
414
415 commit1 = commit_change(
415 commit1 = commit_change(
416 repo=r2_name, filename='file1-fork',
416 repo=r2_name, filename='file1-fork',
417 content='file1-line1-from-fork', message='commit1-fork',
417 content='file1-line1-from-fork', message='commit1-fork',
418 vcs_type=backend.alias, parent=repo2.scm_instance()[-1],
418 vcs_type=backend.alias, parent=repo2.scm_instance()[-1],
419 newfile=True)
419 newfile=True)
420
420
421 commit2 = commit_change(
421 commit2 = commit_change(
422 repo=r2_name, filename='file2-fork',
422 repo=r2_name, filename='file2-fork',
423 content='file2-line1-from-fork', message='commit2-fork',
423 content='file2-line1-from-fork', message='commit2-fork',
424 vcs_type=backend.alias, parent=commit1,
424 vcs_type=backend.alias, parent=commit1,
425 newfile=True)
425 newfile=True)
426
426
427 commit_change( # commit 3
427 commit_change( # commit 3
428 repo=r2_name, filename='file3-fork',
428 repo=r2_name, filename='file3-fork',
429 content='file3-line1-from-fork', message='commit3-fork',
429 content='file3-line1-from-fork', message='commit3-fork',
430 vcs_type=backend.alias, parent=commit2, newfile=True)
430 vcs_type=backend.alias, parent=commit2, newfile=True)
431
431
432 # compare !
432 # compare !
433 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
433 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
434 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
434 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
435
435
436 response = self.app.get(
436 response = self.app.get(
437 route_path('repo_compare',
437 route_path('repo_compare',
438 repo_name=r2_name,
438 repo_name=r2_name,
439 source_ref_type="branch", source_ref=commit_id1,
439 source_ref_type="branch", source_ref=commit_id1,
440 target_ref_type="branch", target_ref=commit_id2,
440 target_ref_type="branch", target_ref=commit_id2,
441 params=dict(merge='1', target_repo=r1_name),
441 params=dict(merge='1', target_repo=r1_name),
442 ))
442 ))
443
443
444 response.mustcontain('%s@%s' % (r2_name, commit_id1))
444 response.mustcontain('%s@%s' % (r2_name, commit_id1))
445 response.mustcontain('%s@%s' % (r1_name, commit_id2))
445 response.mustcontain('%s@%s' % (r1_name, commit_id2))
446 response.mustcontain('No files')
446 response.mustcontain('No files')
447 response.mustcontain('No commits in this compare')
447 response.mustcontain('No commits in this compare')
448
448
449 commit0 = commit_change(
449 commit0 = commit_change(
450 repo=r1_name, filename='file2',
450 repo=r1_name, filename='file2',
451 content='line1-added-after-fork', message='commit2-parent',
451 content='line1-added-after-fork', message='commit2-parent',
452 vcs_type=backend.alias, parent=None, newfile=True)
452 vcs_type=backend.alias, parent=None, newfile=True)
453
453
454 # compare !
454 # compare !
455 response = self.app.get(
455 response = self.app.get(
456 route_path('repo_compare',
456 route_path('repo_compare',
457 repo_name=r2_name,
457 repo_name=r2_name,
458 source_ref_type="branch", source_ref=commit_id1,
458 source_ref_type="branch", source_ref=commit_id1,
459 target_ref_type="branch", target_ref=commit_id2,
459 target_ref_type="branch", target_ref=commit_id2,
460 params=dict(merge='1', target_repo=r1_name),
460 params=dict(merge='1', target_repo=r1_name),
461 ))
461 ))
462
462
463 response.mustcontain('%s@%s' % (r2_name, commit_id1))
463 response.mustcontain('%s@%s' % (r2_name, commit_id1))
464 response.mustcontain('%s@%s' % (r1_name, commit_id2))
464 response.mustcontain('%s@%s' % (r1_name, commit_id2))
465
465
466 response.mustcontain("""commit2-parent""")
466 response.mustcontain("""commit2-parent""")
467 response.mustcontain("""line1-added-after-fork""")
467 response.mustcontain("""line1-added-after-fork""")
468 compare_page = ComparePage(response)
468 compare_page = ComparePage(response)
469 compare_page.contains_change_summary(1, 1, 0)
469 compare_page.contains_change_summary(1, 1, 0)
470
470
471 @pytest.mark.xfail_backends("svn")
471 @pytest.mark.xfail_backends("svn")
472 def test_compare_commits(self, backend, xhr_header):
472 def test_compare_commits(self, backend, xhr_header):
473 commit0 = backend.repo.get_commit(commit_idx=0)
473 commit0 = backend.repo.get_commit(commit_idx=0)
474 commit1 = backend.repo.get_commit(commit_idx=1)
474 commit1 = backend.repo.get_commit(commit_idx=1)
475
475
476 response = self.app.get(
476 response = self.app.get(
477 route_path('repo_compare',
477 route_path('repo_compare',
478 repo_name=backend.repo_name,
478 repo_name=backend.repo_name,
479 source_ref_type="rev", source_ref=commit0.raw_id,
479 source_ref_type="rev", source_ref=commit0.raw_id,
480 target_ref_type="rev", target_ref=commit1.raw_id,
480 target_ref_type="rev", target_ref=commit1.raw_id,
481 params=dict(merge='1')
481 params=dict(merge='1')
482 ),
482 ),
483 extra_environ=xhr_header, )
483 extra_environ=xhr_header, )
484
484
485 # outgoing commits between those commits
485 # outgoing commits between those commits
486 compare_page = ComparePage(response)
486 compare_page = ComparePage(response)
487 compare_page.contains_commits(commits=[commit1])
487 compare_page.contains_commits(commits=[commit1])
488
488
489 def test_errors_when_comparing_unknown_source_repo(self, backend):
489 def test_errors_when_comparing_unknown_source_repo(self, backend):
490 repo = backend.repo
490 repo = backend.repo
491 badrepo = 'badrepo'
491 badrepo = 'badrepo'
492
492
493 response = self.app.get(
493 response = self.app.get(
494 route_path('repo_compare',
494 route_path('repo_compare',
495 repo_name=badrepo,
495 repo_name=badrepo,
496 source_ref_type="rev", source_ref='tip',
496 source_ref_type="rev", source_ref='tip',
497 target_ref_type="rev", target_ref='tip',
497 target_ref_type="rev", target_ref='tip',
498 params=dict(merge='1', target_repo=repo.repo_name)
498 params=dict(merge='1', target_repo=repo.repo_name)
499 ),
499 ),
500 status=404)
500 status=404)
501
501
502 def test_errors_when_comparing_unknown_target_repo(self, backend):
502 def test_errors_when_comparing_unknown_target_repo(self, backend):
503 repo = backend.repo
503 repo = backend.repo
504 badrepo = 'badrepo'
504 badrepo = 'badrepo'
505
505
506 response = self.app.get(
506 response = self.app.get(
507 route_path('repo_compare',
507 route_path('repo_compare',
508 repo_name=repo.repo_name,
508 repo_name=repo.repo_name,
509 source_ref_type="rev", source_ref='tip',
509 source_ref_type="rev", source_ref='tip',
510 target_ref_type="rev", target_ref='tip',
510 target_ref_type="rev", target_ref='tip',
511 params=dict(merge='1', target_repo=badrepo),
511 params=dict(merge='1', target_repo=badrepo),
512 ),
512 ),
513 status=302)
513 status=302)
514 redirected = response.follow()
514 redirected = response.follow()
515 redirected.mustcontain(
515 redirected.mustcontain(
516 'Could not find the target repo: `{}`'.format(badrepo))
516 'Could not find the target repo: `{}`'.format(badrepo))
517
517
518 def test_compare_not_in_preview_mode(self, backend_stub):
518 def test_compare_not_in_preview_mode(self, backend_stub):
519 commit0 = backend_stub.repo.get_commit(commit_idx=0)
519 commit0 = backend_stub.repo.get_commit(commit_idx=0)
520 commit1 = backend_stub.repo.get_commit(commit_idx=1)
520 commit1 = backend_stub.repo.get_commit(commit_idx=1)
521
521
522 response = self.app.get(
522 response = self.app.get(
523 route_path('repo_compare',
523 route_path('repo_compare',
524 repo_name=backend_stub.repo_name,
524 repo_name=backend_stub.repo_name,
525 source_ref_type="rev", source_ref=commit0.raw_id,
525 source_ref_type="rev", source_ref=commit0.raw_id,
526 target_ref_type="rev", target_ref=commit1.raw_id,
526 target_ref_type="rev", target_ref=commit1.raw_id,
527 ))
527 ))
528
528
529 # outgoing commits between those commits
529 # outgoing commits between those commits
530 compare_page = ComparePage(response)
530 compare_page = ComparePage(response)
531 compare_page.swap_is_visible()
531 compare_page.swap_is_visible()
532 compare_page.target_source_are_enabled()
532 compare_page.target_source_are_enabled()
533
533
534 def test_compare_of_fork_with_largefiles(self, backend_hg, settings_util):
534 def test_compare_of_fork_with_largefiles(self, backend_hg, settings_util):
535 orig = backend_hg.create_repo(number_of_commits=1)
535 orig = backend_hg.create_repo(number_of_commits=1)
536 fork = backend_hg.create_fork()
536 fork = backend_hg.create_fork()
537
537
538 settings_util.create_repo_rhodecode_ui(
538 settings_util.create_repo_rhodecode_ui(
539 orig, 'extensions', value='', key='largefiles', active=False)
539 orig, 'extensions', value='', key='largefiles', active=False)
540 settings_util.create_repo_rhodecode_ui(
540 settings_util.create_repo_rhodecode_ui(
541 fork, 'extensions', value='', key='largefiles', active=True)
541 fork, 'extensions', value='', key='largefiles', active=True)
542
542
543 compare_module = ('rhodecode.lib.vcs.backends.hg.repository.'
543 compare_module = ('rhodecode.lib.vcs.backends.hg.repository.'
544 'MercurialRepository.compare')
544 'MercurialRepository.compare')
545 with mock.patch(compare_module) as compare_mock:
545 with mock.patch(compare_module) as compare_mock:
546 compare_mock.side_effect = RepositoryRequirementError()
546 compare_mock.side_effect = RepositoryRequirementError()
547
547
548 response = self.app.get(
548 response = self.app.get(
549 route_path('repo_compare',
549 route_path('repo_compare',
550 repo_name=orig.repo_name,
550 repo_name=orig.repo_name,
551 source_ref_type="rev", source_ref="tip",
551 source_ref_type="rev", source_ref="tip",
552 target_ref_type="rev", target_ref="tip",
552 target_ref_type="rev", target_ref="tip",
553 params=dict(merge='1', target_repo=fork.repo_name),
553 params=dict(merge='1', target_repo=fork.repo_name),
554 ),
554 ),
555 status=302)
555 status=302)
556
556
557 assert_session_flash(
557 assert_session_flash(
558 response,
558 response,
559 'Could not compare repos with different large file settings')
559 'Could not compare repos with different large file settings')
560
560
561
561
562 @pytest.mark.usefixtures("autologin_user")
562 @pytest.mark.usefixtures("autologin_user")
563 class TestCompareControllerSvn(object):
563 class TestCompareControllerSvn(object):
564
564
565 def test_supports_references_with_path(self, app, backend_svn):
565 def test_supports_references_with_path(self, app, backend_svn):
566 repo = backend_svn['svn-simple-layout']
566 repo = backend_svn['svn-simple-layout']
567 commit_id = repo.get_commit(commit_idx=-1).raw_id
567 commit_id = repo.get_commit(commit_idx=-1).raw_id
568 response = app.get(
568 response = app.get(
569 route_path('repo_compare',
569 route_path('repo_compare',
570 repo_name=repo.repo_name,
570 repo_name=repo.repo_name,
571 source_ref_type="tag",
571 source_ref_type="tag",
572 source_ref="%s@%s" % ('tags/v0.1', commit_id),
572 source_ref="%s@%s" % ('tags/v0.1', commit_id),
573 target_ref_type="tag",
573 target_ref_type="tag",
574 target_ref="%s@%s" % ('tags/v0.2', commit_id),
574 target_ref="%s@%s" % ('tags/v0.2', commit_id),
575 params=dict(merge='1'),
575 params=dict(merge='1'),
576 ),
576 ),
577 status=200)
577 status=200)
578
578
579 # Expecting no commits, since both paths are at the same revision
579 # Expecting no commits, since both paths are at the same revision
580 response.mustcontain('No commits in this compare')
580 response.mustcontain('No commits in this compare')
581
581
582 # Should find only one file changed when comparing those two tags
582 # Should find only one file changed when comparing those two tags
583 response.mustcontain('example.py')
583 response.mustcontain('example.py')
584 compare_page = ComparePage(response)
584 compare_page = ComparePage(response)
585 compare_page.contains_change_summary(1, 5, 1)
585 compare_page.contains_change_summary(1, 5, 1)
586
586
587 def test_shows_commits_if_different_ids(self, app, backend_svn):
587 def test_shows_commits_if_different_ids(self, app, backend_svn):
588 repo = backend_svn['svn-simple-layout']
588 repo = backend_svn['svn-simple-layout']
589 source_id = repo.get_commit(commit_idx=-6).raw_id
589 source_id = repo.get_commit(commit_idx=-6).raw_id
590 target_id = repo.get_commit(commit_idx=-1).raw_id
590 target_id = repo.get_commit(commit_idx=-1).raw_id
591 response = app.get(
591 response = app.get(
592 route_path('repo_compare',
592 route_path('repo_compare',
593 repo_name=repo.repo_name,
593 repo_name=repo.repo_name,
594 source_ref_type="tag",
594 source_ref_type="tag",
595 source_ref="%s@%s" % ('tags/v0.1', source_id),
595 source_ref="%s@%s" % ('tags/v0.1', source_id),
596 target_ref_type="tag",
596 target_ref_type="tag",
597 target_ref="%s@%s" % ('tags/v0.2', target_id),
597 target_ref="%s@%s" % ('tags/v0.2', target_id),
598 params=dict(merge='1')
598 params=dict(merge='1')
599 ),
599 ),
600 status=200)
600 status=200)
601
601
602 # It should show commits
602 # It should show commits
603 assert 'No commits in this compare' not in response.body
603 assert 'No commits in this compare' not in response.body
604
604
605 # Should find only one file changed when comparing those two tags
605 # Should find only one file changed when comparing those two tags
606 response.mustcontain('example.py')
606 response.mustcontain('example.py')
607 compare_page = ComparePage(response)
607 compare_page = ComparePage(response)
608 compare_page.contains_change_summary(1, 5, 1)
608 compare_page.contains_change_summary(1, 5, 1)
609
609
610
610
611 class ComparePage(AssertResponse):
611 class ComparePage(AssertResponse):
612 """
612 """
613 Abstracts the page template from the tests
613 Abstracts the page template from the tests
614 """
614 """
615
615
616 def contains_file_links_and_anchors(self, files):
616 def contains_file_links_and_anchors(self, files):
617 doc = lxml.html.fromstring(self.response.body)
617 doc = lxml.html.fromstring(self.response.body)
618 for filename, file_id in files:
618 for filename, file_id in files:
619 self.contains_one_anchor(file_id)
619 self.contains_one_anchor(file_id)
620 diffblock = doc.cssselect('[data-f-path="%s"]' % filename)
620 diffblock = doc.cssselect('[data-f-path="%s"]' % filename)
621 assert len(diffblock) == 2
621 assert len(diffblock) == 2
622 assert len(diffblock[0].cssselect('a[href="#%s"]' % file_id)) == 1
622 for lnk in diffblock[0].cssselect('a'):
623 if 'permalink' in lnk.text:
624 assert '#{}'.format(file_id) in lnk.attrib['href']
625 break
626 else:
627 pytest.fail('Unable to find permalink')
623
628
624 def contains_change_summary(self, files_changed, inserted, deleted):
629 def contains_change_summary(self, files_changed, inserted, deleted):
625 template = (
630 template = (
626 '{files_changed} file{plural} changed: '
631 '{files_changed} file{plural} changed: '
627 '<span class="op-added">{inserted} inserted</span>, <span class="op-deleted">{deleted} deleted</span>')
632 '<span class="op-added">{inserted} inserted</span>, <span class="op-deleted">{deleted} deleted</span>')
628 self.response.mustcontain(template.format(
633 self.response.mustcontain(template.format(
629 files_changed=files_changed,
634 files_changed=files_changed,
630 plural="s" if files_changed > 1 else "",
635 plural="s" if files_changed > 1 else "",
631 inserted=inserted,
636 inserted=inserted,
632 deleted=deleted))
637 deleted=deleted))
633
638
634 def contains_commits(self, commits, ancestors=None):
639 def contains_commits(self, commits, ancestors=None):
635 response = self.response
640 response = self.response
636
641
637 for commit in commits:
642 for commit in commits:
638 # Expecting to see the commit message in an element which
643 # Expecting to see the commit message in an element which
639 # has the ID "c-{commit.raw_id}"
644 # has the ID "c-{commit.raw_id}"
640 self.element_contains('#c-' + commit.raw_id, commit.message)
645 self.element_contains('#c-' + commit.raw_id, commit.message)
641 self.contains_one_link(
646 self.contains_one_link(
642 'r%s:%s' % (commit.idx, commit.short_id),
647 'r%s:%s' % (commit.idx, commit.short_id),
643 self._commit_url(commit))
648 self._commit_url(commit))
644
649
645 if ancestors:
650 if ancestors:
646 response.mustcontain('Ancestor')
651 response.mustcontain('Ancestor')
647 for ancestor in ancestors:
652 for ancestor in ancestors:
648 self.contains_one_link(
653 self.contains_one_link(
649 ancestor.short_id, self._commit_url(ancestor))
654 ancestor.short_id, self._commit_url(ancestor))
650
655
651 def _commit_url(self, commit):
656 def _commit_url(self, commit):
652 return '/%s/changeset/%s' % (commit.repository.name, commit.raw_id)
657 return '/%s/changeset/%s' % (commit.repository.name, commit.raw_id)
653
658
654 def swap_is_hidden(self):
659 def swap_is_hidden(self):
655 assert '<a id="btn-swap"' not in self.response.text
660 assert '<a id="btn-swap"' not in self.response.text
656
661
657 def swap_is_visible(self):
662 def swap_is_visible(self):
658 assert '<a id="btn-swap"' in self.response.text
663 assert '<a id="btn-swap"' in self.response.text
659
664
660 def target_source_are_disabled(self):
665 def target_source_are_disabled(self):
661 response = self.response
666 response = self.response
662 response.mustcontain("var enable_fields = false;")
667 response.mustcontain("var enable_fields = false;")
663 response.mustcontain('.select2("enable", enable_fields)')
668 response.mustcontain('.select2("enable", enable_fields)')
664
669
665 def target_source_are_enabled(self):
670 def target_source_are_enabled(self):
666 response = self.response
671 response = self.response
667 response.mustcontain("var enable_fields = true;")
672 response.mustcontain("var enable_fields = true;")
@@ -1,1652 +1,1658 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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, Repository)
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment, Repository)
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.model.comment import CommentsModel
33 from rhodecode.model.comment import CommentsModel
34 from rhodecode.tests import (
34 from rhodecode.tests import (
35 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
35 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
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 'repo_commits': '/{repo_name}/commits',
44 'repo_commits': '/{repo_name}/commits',
45 'repo_commits_file': '/{repo_name}/commits/{commit_id}/{f_path}',
45 'repo_commits_file': '/{repo_name}/commits/{commit_id}/{f_path}',
46 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
46 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
47 'pullrequest_show_all': '/{repo_name}/pull-request',
47 'pullrequest_show_all': '/{repo_name}/pull-request',
48 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
48 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
49 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
49 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
50 'pullrequest_repo_targets': '/{repo_name}/pull-request/repo-destinations',
50 'pullrequest_repo_targets': '/{repo_name}/pull-request/repo-destinations',
51 'pullrequest_new': '/{repo_name}/pull-request/new',
51 'pullrequest_new': '/{repo_name}/pull-request/new',
52 'pullrequest_create': '/{repo_name}/pull-request/create',
52 'pullrequest_create': '/{repo_name}/pull-request/create',
53 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
53 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
54 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
54 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
55 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
55 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
56 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
56 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
57 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
57 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
58 'pullrequest_comment_edit': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/edit',
58 'pullrequest_comment_edit': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/edit',
59 }[name].format(**kwargs)
59 }[name].format(**kwargs)
60
60
61 if params:
61 if params:
62 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
62 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
63 return base_url
63 return base_url
64
64
65
65
66 @pytest.mark.usefixtures('app', 'autologin_user')
66 @pytest.mark.usefixtures('app', 'autologin_user')
67 @pytest.mark.backends("git", "hg")
67 @pytest.mark.backends("git", "hg")
68 class TestPullrequestsView(object):
68 class TestPullrequestsView(object):
69
69
70 def test_index(self, backend):
70 def test_index(self, backend):
71 self.app.get(route_path(
71 self.app.get(route_path(
72 'pullrequest_new',
72 'pullrequest_new',
73 repo_name=backend.repo_name))
73 repo_name=backend.repo_name))
74
74
75 def test_option_menu_create_pull_request_exists(self, backend):
75 def test_option_menu_create_pull_request_exists(self, backend):
76 repo_name = backend.repo_name
76 repo_name = backend.repo_name
77 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
77 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
78
78
79 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
79 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
80 'pullrequest_new', repo_name=repo_name)
80 'pullrequest_new', repo_name=repo_name)
81 response.mustcontain(create_pr_link)
81 response.mustcontain(create_pr_link)
82
82
83 def test_create_pr_form_with_raw_commit_id(self, backend):
83 def test_create_pr_form_with_raw_commit_id(self, backend):
84 repo = backend.repo
84 repo = backend.repo
85
85
86 self.app.get(
86 self.app.get(
87 route_path('pullrequest_new', repo_name=repo.repo_name,
87 route_path('pullrequest_new', repo_name=repo.repo_name,
88 commit=repo.get_commit().raw_id),
88 commit=repo.get_commit().raw_id),
89 status=200)
89 status=200)
90
90
91 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
91 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
92 @pytest.mark.parametrize('range_diff', ["0", "1"])
92 @pytest.mark.parametrize('range_diff', ["0", "1"])
93 def test_show(self, pr_util, pr_merge_enabled, range_diff):
93 def test_show(self, pr_util, pr_merge_enabled, range_diff):
94 pull_request = pr_util.create_pull_request(
94 pull_request = pr_util.create_pull_request(
95 mergeable=pr_merge_enabled, enable_notifications=False)
95 mergeable=pr_merge_enabled, enable_notifications=False)
96
96
97 response = self.app.get(route_path(
97 response = self.app.get(route_path(
98 'pullrequest_show',
98 'pullrequest_show',
99 repo_name=pull_request.target_repo.scm_instance().name,
99 repo_name=pull_request.target_repo.scm_instance().name,
100 pull_request_id=pull_request.pull_request_id,
100 pull_request_id=pull_request.pull_request_id,
101 params={'range-diff': range_diff}))
101 params={'range-diff': range_diff}))
102
102
103 for commit_id in pull_request.revisions:
103 for commit_id in pull_request.revisions:
104 response.mustcontain(commit_id)
104 response.mustcontain(commit_id)
105
105
106 response.mustcontain(pull_request.target_ref_parts.type)
106 response.mustcontain(pull_request.target_ref_parts.type)
107 response.mustcontain(pull_request.target_ref_parts.name)
107 response.mustcontain(pull_request.target_ref_parts.name)
108
108
109 response.mustcontain('class="pull-request-merge"')
109 response.mustcontain('class="pull-request-merge"')
110
110
111 if pr_merge_enabled:
111 if pr_merge_enabled:
112 response.mustcontain('Pull request reviewer approval is pending')
112 response.mustcontain('Pull request reviewer approval is pending')
113 else:
113 else:
114 response.mustcontain('Server-side pull request merging is disabled.')
114 response.mustcontain('Server-side pull request merging is disabled.')
115
115
116 if range_diff == "1":
116 if range_diff == "1":
117 response.mustcontain('Turn off: Show the diff as commit range')
117 response.mustcontain('Turn off: Show the diff as commit range')
118
118
119 def test_show_versions_of_pr(self, backend, csrf_token):
119 def test_show_versions_of_pr(self, backend, csrf_token):
120 commits = [
120 commits = [
121 {'message': 'initial-commit',
121 {'message': 'initial-commit',
122 'added': [FileNode('test-file.txt', 'LINE1\n')]},
122 'added': [FileNode('test-file.txt', 'LINE1\n')]},
123
123
124 {'message': 'commit-1',
124 {'message': 'commit-1',
125 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\n')]},
125 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\n')]},
126 # Above is the initial version of PR that changes a single line
126 # Above is the initial version of PR that changes a single line
127
127
128 # from now on we'll add 3x commit adding a nother line on each step
128 # from now on we'll add 3x commit adding a nother line on each step
129 {'message': 'commit-2',
129 {'message': 'commit-2',
130 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\nLINE3\n')]},
130 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\nLINE3\n')]},
131
131
132 {'message': 'commit-3',
132 {'message': 'commit-3',
133 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\nLINE3\nLINE4\n')]},
133 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\nLINE3\nLINE4\n')]},
134
134
135 {'message': 'commit-4',
135 {'message': 'commit-4',
136 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\nLINE3\nLINE4\nLINE5\n')]},
136 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\nLINE3\nLINE4\nLINE5\n')]},
137 ]
137 ]
138
138
139 commit_ids = backend.create_master_repo(commits)
139 commit_ids = backend.create_master_repo(commits)
140 target = backend.create_repo(heads=['initial-commit'])
140 target = backend.create_repo(heads=['initial-commit'])
141 source = backend.create_repo(heads=['commit-1'])
141 source = backend.create_repo(heads=['commit-1'])
142 source_repo_name = source.repo_name
142 source_repo_name = source.repo_name
143 target_repo_name = target.repo_name
143 target_repo_name = target.repo_name
144
144
145 target_ref = 'branch:{branch}:{commit_id}'.format(
145 target_ref = 'branch:{branch}:{commit_id}'.format(
146 branch=backend.default_branch_name, commit_id=commit_ids['initial-commit'])
146 branch=backend.default_branch_name, commit_id=commit_ids['initial-commit'])
147 source_ref = 'branch:{branch}:{commit_id}'.format(
147 source_ref = 'branch:{branch}:{commit_id}'.format(
148 branch=backend.default_branch_name, commit_id=commit_ids['commit-1'])
148 branch=backend.default_branch_name, commit_id=commit_ids['commit-1'])
149
149
150 response = self.app.post(
150 response = self.app.post(
151 route_path('pullrequest_create', repo_name=source.repo_name),
151 route_path('pullrequest_create', repo_name=source.repo_name),
152 [
152 [
153 ('source_repo', source.repo_name),
153 ('source_repo', source_repo_name),
154 ('source_ref', source_ref),
154 ('source_ref', source_ref),
155 ('target_repo', target.repo_name),
155 ('target_repo', target_repo_name),
156 ('target_ref', target_ref),
156 ('target_ref', target_ref),
157 ('common_ancestor', commit_ids['initial-commit']),
157 ('common_ancestor', commit_ids['initial-commit']),
158 ('pullrequest_title', 'Title'),
158 ('pullrequest_title', 'Title'),
159 ('pullrequest_desc', 'Description'),
159 ('pullrequest_desc', 'Description'),
160 ('description_renderer', 'markdown'),
160 ('description_renderer', 'markdown'),
161 ('__start__', 'review_members:sequence'),
161 ('__start__', 'review_members:sequence'),
162 ('__start__', 'reviewer:mapping'),
162 ('__start__', 'reviewer:mapping'),
163 ('user_id', '1'),
163 ('user_id', '1'),
164 ('__start__', 'reasons:sequence'),
164 ('__start__', 'reasons:sequence'),
165 ('reason', 'Some reason'),
165 ('reason', 'Some reason'),
166 ('__end__', 'reasons:sequence'),
166 ('__end__', 'reasons:sequence'),
167 ('__start__', 'rules:sequence'),
167 ('__start__', 'rules:sequence'),
168 ('__end__', 'rules:sequence'),
168 ('__end__', 'rules:sequence'),
169 ('mandatory', 'False'),
169 ('mandatory', 'False'),
170 ('__end__', 'reviewer:mapping'),
170 ('__end__', 'reviewer:mapping'),
171 ('__end__', 'review_members:sequence'),
171 ('__end__', 'review_members:sequence'),
172 ('__start__', 'revisions:sequence'),
172 ('__start__', 'revisions:sequence'),
173 ('revisions', commit_ids['commit-1']),
173 ('revisions', commit_ids['commit-1']),
174 ('__end__', 'revisions:sequence'),
174 ('__end__', 'revisions:sequence'),
175 ('user', ''),
175 ('user', ''),
176 ('csrf_token', csrf_token),
176 ('csrf_token', csrf_token),
177 ],
177 ],
178 status=302)
178 status=302)
179
179
180 location = response.headers['Location']
180 location = response.headers['Location']
181
181
182 pull_request_id = location.rsplit('/', 1)[1]
182 pull_request_id = location.rsplit('/', 1)[1]
183 assert pull_request_id != 'new'
183 assert pull_request_id != 'new'
184 pull_request = PullRequest.get(int(pull_request_id))
184 pull_request = PullRequest.get(int(pull_request_id))
185
185
186 pull_request_id = pull_request.pull_request_id
186 pull_request_id = pull_request.pull_request_id
187
187
188 # Show initial version of PR
188 # Show initial version of PR
189 response = self.app.get(
189 response = self.app.get(
190 route_path('pullrequest_show',
190 route_path('pullrequest_show',
191 repo_name=target_repo_name,
191 repo_name=target_repo_name,
192 pull_request_id=pull_request_id))
192 pull_request_id=pull_request_id))
193
193
194 response.mustcontain('commit-1')
194 response.mustcontain('commit-1')
195 response.mustcontain(no=['commit-2'])
195 response.mustcontain(no=['commit-2'])
196 response.mustcontain(no=['commit-3'])
196 response.mustcontain(no=['commit-3'])
197 response.mustcontain(no=['commit-4'])
197 response.mustcontain(no=['commit-4'])
198
198
199 response.mustcontain('cb-addition"></span><span>LINE2</span>')
199 response.mustcontain('cb-addition"></span><span>LINE2</span>')
200 response.mustcontain(no=['LINE3'])
200 response.mustcontain(no=['LINE3'])
201 response.mustcontain(no=['LINE4'])
201 response.mustcontain(no=['LINE4'])
202 response.mustcontain(no=['LINE5'])
202 response.mustcontain(no=['LINE5'])
203
203
204 # update PR #1
204 # update PR #1
205 source_repo = Repository.get_by_repo_name(source_repo_name)
205 source_repo = Repository.get_by_repo_name(source_repo_name)
206 backend.pull_heads(source_repo, heads=['commit-2'])
206 backend.pull_heads(source_repo, heads=['commit-2'])
207 response = self.app.post(
207 response = self.app.post(
208 route_path('pullrequest_update',
208 route_path('pullrequest_update',
209 repo_name=target_repo_name, pull_request_id=pull_request_id),
209 repo_name=target_repo_name, pull_request_id=pull_request_id),
210 params={'update_commits': 'true', 'csrf_token': csrf_token})
210 params={'update_commits': 'true', 'csrf_token': csrf_token})
211
211
212 # update PR #2
212 # update PR #2
213 source_repo = Repository.get_by_repo_name(source_repo_name)
213 source_repo = Repository.get_by_repo_name(source_repo_name)
214 backend.pull_heads(source_repo, heads=['commit-3'])
214 backend.pull_heads(source_repo, heads=['commit-3'])
215 response = self.app.post(
215 response = self.app.post(
216 route_path('pullrequest_update',
216 route_path('pullrequest_update',
217 repo_name=target_repo_name, pull_request_id=pull_request_id),
217 repo_name=target_repo_name, pull_request_id=pull_request_id),
218 params={'update_commits': 'true', 'csrf_token': csrf_token})
218 params={'update_commits': 'true', 'csrf_token': csrf_token})
219
219
220 # update PR #3
220 # update PR #3
221 source_repo = Repository.get_by_repo_name(source_repo_name)
221 source_repo = Repository.get_by_repo_name(source_repo_name)
222 backend.pull_heads(source_repo, heads=['commit-4'])
222 backend.pull_heads(source_repo, heads=['commit-4'])
223 response = self.app.post(
223 response = self.app.post(
224 route_path('pullrequest_update',
224 route_path('pullrequest_update',
225 repo_name=target_repo_name, pull_request_id=pull_request_id),
225 repo_name=target_repo_name, pull_request_id=pull_request_id),
226 params={'update_commits': 'true', 'csrf_token': csrf_token})
226 params={'update_commits': 'true', 'csrf_token': csrf_token})
227
227
228 # Show final version !
228 # Show final version !
229 response = self.app.get(
229 response = self.app.get(
230 route_path('pullrequest_show',
230 route_path('pullrequest_show',
231 repo_name=target_repo_name,
231 repo_name=target_repo_name,
232 pull_request_id=pull_request_id))
232 pull_request_id=pull_request_id))
233
233
234 # 3 updates, and the latest == 4
234 # 3 updates, and the latest == 4
235 response.mustcontain('4 versions available for this pull request')
235 response.mustcontain('4 versions available for this pull request')
236 response.mustcontain(no=['rhodecode diff rendering error'])
236 response.mustcontain(no=['rhodecode diff rendering error'])
237
237
238 # initial show must have 3 commits, and 3 adds
238 # initial show must have 3 commits, and 3 adds
239 response.mustcontain('commit-1')
239 response.mustcontain('commit-1')
240 response.mustcontain('commit-2')
240 response.mustcontain('commit-2')
241 response.mustcontain('commit-3')
241 response.mustcontain('commit-3')
242 response.mustcontain('commit-4')
242 response.mustcontain('commit-4')
243
243
244 response.mustcontain('cb-addition"></span><span>LINE2</span>')
244 response.mustcontain('cb-addition"></span><span>LINE2</span>')
245 response.mustcontain('cb-addition"></span><span>LINE3</span>')
245 response.mustcontain('cb-addition"></span><span>LINE3</span>')
246 response.mustcontain('cb-addition"></span><span>LINE4</span>')
246 response.mustcontain('cb-addition"></span><span>LINE4</span>')
247 response.mustcontain('cb-addition"></span><span>LINE5</span>')
247 response.mustcontain('cb-addition"></span><span>LINE5</span>')
248
248
249 # fetch versions
249 # fetch versions
250 pr = PullRequest.get(pull_request_id)
250 pr = PullRequest.get(pull_request_id)
251 versions = [x.pull_request_version_id for x in pr.versions.all()]
251 versions = [x.pull_request_version_id for x in pr.versions.all()]
252 assert len(versions) == 3
252 assert len(versions) == 3
253
253
254 # show v1,v2,v3,v4
254 # show v1,v2,v3,v4
255 def cb_line(text):
255 def cb_line(text):
256 return 'cb-addition"></span><span>{}</span>'.format(text)
256 return 'cb-addition"></span><span>{}</span>'.format(text)
257
257
258 def cb_context(text):
258 def cb_context(text):
259 return '<span class="cb-code"><span class="cb-action cb-context">' \
259 return '<span class="cb-code"><span class="cb-action cb-context">' \
260 '</span><span>{}</span></span>'.format(text)
260 '</span><span>{}</span></span>'.format(text)
261
261
262 commit_tests = {
262 commit_tests = {
263 # in response, not in response
263 # in response, not in response
264 1: (['commit-1'], ['commit-2', 'commit-3', 'commit-4']),
264 1: (['commit-1'], ['commit-2', 'commit-3', 'commit-4']),
265 2: (['commit-1', 'commit-2'], ['commit-3', 'commit-4']),
265 2: (['commit-1', 'commit-2'], ['commit-3', 'commit-4']),
266 3: (['commit-1', 'commit-2', 'commit-3'], ['commit-4']),
266 3: (['commit-1', 'commit-2', 'commit-3'], ['commit-4']),
267 4: (['commit-1', 'commit-2', 'commit-3', 'commit-4'], []),
267 4: (['commit-1', 'commit-2', 'commit-3', 'commit-4'], []),
268 }
268 }
269 diff_tests = {
269 diff_tests = {
270 1: (['LINE2'], ['LINE3', 'LINE4', 'LINE5']),
270 1: (['LINE2'], ['LINE3', 'LINE4', 'LINE5']),
271 2: (['LINE2', 'LINE3'], ['LINE4', 'LINE5']),
271 2: (['LINE2', 'LINE3'], ['LINE4', 'LINE5']),
272 3: (['LINE2', 'LINE3', 'LINE4'], ['LINE5']),
272 3: (['LINE2', 'LINE3', 'LINE4'], ['LINE5']),
273 4: (['LINE2', 'LINE3', 'LINE4', 'LINE5'], []),
273 4: (['LINE2', 'LINE3', 'LINE4', 'LINE5'], []),
274 }
274 }
275 for idx, ver in enumerate(versions, 1):
275 for idx, ver in enumerate(versions, 1):
276
276
277 response = self.app.get(
277 response = self.app.get(
278 route_path('pullrequest_show',
278 route_path('pullrequest_show',
279 repo_name=target_repo_name,
279 repo_name=target_repo_name,
280 pull_request_id=pull_request_id,
280 pull_request_id=pull_request_id,
281 params={'version': ver}))
281 params={'version': ver}))
282
282
283 response.mustcontain(no=['rhodecode diff rendering error'])
283 response.mustcontain(no=['rhodecode diff rendering error'])
284 response.mustcontain('Showing changes at v{}'.format(idx))
284 response.mustcontain('Showing changes at v{}'.format(idx))
285
285
286 yes, no = commit_tests[idx]
286 yes, no = commit_tests[idx]
287 for y in yes:
287 for y in yes:
288 response.mustcontain(y)
288 response.mustcontain(y)
289 for n in no:
289 for n in no:
290 response.mustcontain(no=n)
290 response.mustcontain(no=n)
291
291
292 yes, no = diff_tests[idx]
292 yes, no = diff_tests[idx]
293 for y in yes:
293 for y in yes:
294 response.mustcontain(cb_line(y))
294 response.mustcontain(cb_line(y))
295 for n in no:
295 for n in no:
296 response.mustcontain(no=n)
296 response.mustcontain(no=n)
297
297
298 # show diff between versions
298 # show diff between versions
299 diff_compare_tests = {
299 diff_compare_tests = {
300 1: (['LINE3'], ['LINE1', 'LINE2']),
300 1: (['LINE3'], ['LINE1', 'LINE2']),
301 2: (['LINE3', 'LINE4'], ['LINE1', 'LINE2']),
301 2: (['LINE3', 'LINE4'], ['LINE1', 'LINE2']),
302 3: (['LINE3', 'LINE4', 'LINE5'], ['LINE1', 'LINE2']),
302 3: (['LINE3', 'LINE4', 'LINE5'], ['LINE1', 'LINE2']),
303 }
303 }
304 for idx, ver in enumerate(versions, 1):
304 for idx, ver in enumerate(versions, 1):
305 adds, context = diff_compare_tests[idx]
305 adds, context = diff_compare_tests[idx]
306
306
307 to_ver = ver+1
307 to_ver = ver+1
308 if idx == 3:
308 if idx == 3:
309 to_ver = 'latest'
309 to_ver = 'latest'
310
310
311 response = self.app.get(
311 response = self.app.get(
312 route_path('pullrequest_show',
312 route_path('pullrequest_show',
313 repo_name=target_repo_name,
313 repo_name=target_repo_name,
314 pull_request_id=pull_request_id,
314 pull_request_id=pull_request_id,
315 params={'from_version': versions[0], 'version': to_ver}))
315 params={'from_version': versions[0], 'version': to_ver}))
316
316
317 response.mustcontain(no=['rhodecode diff rendering error'])
317 response.mustcontain(no=['rhodecode diff rendering error'])
318
318
319 for a in adds:
319 for a in adds:
320 response.mustcontain(cb_line(a))
320 response.mustcontain(cb_line(a))
321 for c in context:
321 for c in context:
322 response.mustcontain(cb_context(c))
322 response.mustcontain(cb_context(c))
323
323
324 # test version v2 -> v3
324 # test version v2 -> v3
325 response = self.app.get(
325 response = self.app.get(
326 route_path('pullrequest_show',
326 route_path('pullrequest_show',
327 repo_name=target_repo_name,
327 repo_name=target_repo_name,
328 pull_request_id=pull_request_id,
328 pull_request_id=pull_request_id,
329 params={'from_version': versions[1], 'version': versions[2]}))
329 params={'from_version': versions[1], 'version': versions[2]}))
330
330
331 response.mustcontain(cb_context('LINE1'))
331 response.mustcontain(cb_context('LINE1'))
332 response.mustcontain(cb_context('LINE2'))
332 response.mustcontain(cb_context('LINE2'))
333 response.mustcontain(cb_context('LINE3'))
333 response.mustcontain(cb_context('LINE3'))
334 response.mustcontain(cb_line('LINE4'))
334 response.mustcontain(cb_line('LINE4'))
335
335
336 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
336 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
337 # Logout
337 # Logout
338 response = self.app.post(
338 response = self.app.post(
339 h.route_path('logout'),
339 h.route_path('logout'),
340 params={'csrf_token': csrf_token})
340 params={'csrf_token': csrf_token})
341 # Login as regular user
341 # Login as regular user
342 response = self.app.post(h.route_path('login'),
342 response = self.app.post(h.route_path('login'),
343 {'username': TEST_USER_REGULAR_LOGIN,
343 {'username': TEST_USER_REGULAR_LOGIN,
344 'password': 'test12'})
344 'password': 'test12'})
345
345
346 pull_request = pr_util.create_pull_request(
346 pull_request = pr_util.create_pull_request(
347 author=TEST_USER_REGULAR_LOGIN)
347 author=TEST_USER_REGULAR_LOGIN)
348
348
349 response = self.app.get(route_path(
349 response = self.app.get(route_path(
350 'pullrequest_show',
350 'pullrequest_show',
351 repo_name=pull_request.target_repo.scm_instance().name,
351 repo_name=pull_request.target_repo.scm_instance().name,
352 pull_request_id=pull_request.pull_request_id))
352 pull_request_id=pull_request.pull_request_id))
353
353
354 response.mustcontain('Server-side pull request merging is disabled.')
354 response.mustcontain('Server-side pull request merging is disabled.')
355
355
356 assert_response = response.assert_response()
356 assert_response = response.assert_response()
357 # for regular user without a merge permissions, we don't see it
357 # for regular user without a merge permissions, we don't see it
358 assert_response.no_element_exists('#close-pull-request-action')
358 assert_response.no_element_exists('#close-pull-request-action')
359
359
360 user_util.grant_user_permission_to_repo(
360 user_util.grant_user_permission_to_repo(
361 pull_request.target_repo,
361 pull_request.target_repo,
362 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
362 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
363 'repository.write')
363 'repository.write')
364 response = self.app.get(route_path(
364 response = self.app.get(route_path(
365 'pullrequest_show',
365 'pullrequest_show',
366 repo_name=pull_request.target_repo.scm_instance().name,
366 repo_name=pull_request.target_repo.scm_instance().name,
367 pull_request_id=pull_request.pull_request_id))
367 pull_request_id=pull_request.pull_request_id))
368
368
369 response.mustcontain('Server-side pull request merging is disabled.')
369 response.mustcontain('Server-side pull request merging is disabled.')
370
370
371 assert_response = response.assert_response()
371 assert_response = response.assert_response()
372 # now regular user has a merge permissions, we have CLOSE button
372 # now regular user has a merge permissions, we have CLOSE button
373 assert_response.one_element_exists('#close-pull-request-action')
373 assert_response.one_element_exists('#close-pull-request-action')
374
374
375 def test_show_invalid_commit_id(self, pr_util):
375 def test_show_invalid_commit_id(self, pr_util):
376 # Simulating invalid revisions which will cause a lookup error
376 # Simulating invalid revisions which will cause a lookup error
377 pull_request = pr_util.create_pull_request()
377 pull_request = pr_util.create_pull_request()
378 pull_request.revisions = ['invalid']
378 pull_request.revisions = ['invalid']
379 Session().add(pull_request)
379 Session().add(pull_request)
380 Session().commit()
380 Session().commit()
381
381
382 response = self.app.get(route_path(
382 response = self.app.get(route_path(
383 'pullrequest_show',
383 'pullrequest_show',
384 repo_name=pull_request.target_repo.scm_instance().name,
384 repo_name=pull_request.target_repo.scm_instance().name,
385 pull_request_id=pull_request.pull_request_id))
385 pull_request_id=pull_request.pull_request_id))
386
386
387 for commit_id in pull_request.revisions:
387 for commit_id in pull_request.revisions:
388 response.mustcontain(commit_id)
388 response.mustcontain(commit_id)
389
389
390 def test_show_invalid_source_reference(self, pr_util):
390 def test_show_invalid_source_reference(self, pr_util):
391 pull_request = pr_util.create_pull_request()
391 pull_request = pr_util.create_pull_request()
392 pull_request.source_ref = 'branch:b:invalid'
392 pull_request.source_ref = 'branch:b:invalid'
393 Session().add(pull_request)
393 Session().add(pull_request)
394 Session().commit()
394 Session().commit()
395
395
396 self.app.get(route_path(
396 self.app.get(route_path(
397 'pullrequest_show',
397 'pullrequest_show',
398 repo_name=pull_request.target_repo.scm_instance().name,
398 repo_name=pull_request.target_repo.scm_instance().name,
399 pull_request_id=pull_request.pull_request_id))
399 pull_request_id=pull_request.pull_request_id))
400
400
401 def test_edit_title_description(self, pr_util, csrf_token):
401 def test_edit_title_description(self, pr_util, csrf_token):
402 pull_request = pr_util.create_pull_request()
402 pull_request = pr_util.create_pull_request()
403 pull_request_id = pull_request.pull_request_id
403 pull_request_id = pull_request.pull_request_id
404
404
405 response = self.app.post(
405 response = self.app.post(
406 route_path('pullrequest_update',
406 route_path('pullrequest_update',
407 repo_name=pull_request.target_repo.repo_name,
407 repo_name=pull_request.target_repo.repo_name,
408 pull_request_id=pull_request_id),
408 pull_request_id=pull_request_id),
409 params={
409 params={
410 'edit_pull_request': 'true',
410 'edit_pull_request': 'true',
411 'title': 'New title',
411 'title': 'New title',
412 'description': 'New description',
412 'description': 'New description',
413 'csrf_token': csrf_token})
413 'csrf_token': csrf_token})
414
414
415 assert_session_flash(
415 assert_session_flash(
416 response, u'Pull request title & description updated.',
416 response, u'Pull request title & description updated.',
417 category='success')
417 category='success')
418
418
419 pull_request = PullRequest.get(pull_request_id)
419 pull_request = PullRequest.get(pull_request_id)
420 assert pull_request.title == 'New title'
420 assert pull_request.title == 'New title'
421 assert pull_request.description == 'New description'
421 assert pull_request.description == 'New description'
422
422
423 def test_edit_title_description_closed(self, pr_util, csrf_token):
423 def test_edit_title_description_closed(self, pr_util, csrf_token):
424 pull_request = pr_util.create_pull_request()
424 pull_request = pr_util.create_pull_request()
425 pull_request_id = pull_request.pull_request_id
425 pull_request_id = pull_request.pull_request_id
426 repo_name = pull_request.target_repo.repo_name
426 repo_name = pull_request.target_repo.repo_name
427 pr_util.close()
427 pr_util.close()
428
428
429 response = self.app.post(
429 response = self.app.post(
430 route_path('pullrequest_update',
430 route_path('pullrequest_update',
431 repo_name=repo_name, pull_request_id=pull_request_id),
431 repo_name=repo_name, pull_request_id=pull_request_id),
432 params={
432 params={
433 'edit_pull_request': 'true',
433 'edit_pull_request': 'true',
434 'title': 'New title',
434 'title': 'New title',
435 'description': 'New description',
435 'description': 'New description',
436 'csrf_token': csrf_token}, status=200)
436 'csrf_token': csrf_token}, status=200)
437 assert_session_flash(
437 assert_session_flash(
438 response, u'Cannot update closed pull requests.',
438 response, u'Cannot update closed pull requests.',
439 category='error')
439 category='error')
440
440
441 def test_update_invalid_source_reference(self, pr_util, csrf_token):
441 def test_update_invalid_source_reference(self, pr_util, csrf_token):
442 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
442 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
443
443
444 pull_request = pr_util.create_pull_request()
444 pull_request = pr_util.create_pull_request()
445 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
445 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
446 Session().add(pull_request)
446 Session().add(pull_request)
447 Session().commit()
447 Session().commit()
448
448
449 pull_request_id = pull_request.pull_request_id
449 pull_request_id = pull_request.pull_request_id
450
450
451 response = self.app.post(
451 response = self.app.post(
452 route_path('pullrequest_update',
452 route_path('pullrequest_update',
453 repo_name=pull_request.target_repo.repo_name,
453 repo_name=pull_request.target_repo.repo_name,
454 pull_request_id=pull_request_id),
454 pull_request_id=pull_request_id),
455 params={'update_commits': 'true', 'csrf_token': csrf_token})
455 params={'update_commits': 'true', 'csrf_token': csrf_token})
456
456
457 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
457 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
458 UpdateFailureReason.MISSING_SOURCE_REF])
458 UpdateFailureReason.MISSING_SOURCE_REF])
459 assert_session_flash(response, expected_msg, category='error')
459 assert_session_flash(response, expected_msg, category='error')
460
460
461 def test_missing_target_reference(self, pr_util, csrf_token):
461 def test_missing_target_reference(self, pr_util, csrf_token):
462 from rhodecode.lib.vcs.backends.base import MergeFailureReason
462 from rhodecode.lib.vcs.backends.base import MergeFailureReason
463 pull_request = pr_util.create_pull_request(
463 pull_request = pr_util.create_pull_request(
464 approved=True, mergeable=True)
464 approved=True, mergeable=True)
465 unicode_reference = u'branch:invalid-branch:invalid-commit-id'
465 unicode_reference = u'branch:invalid-branch:invalid-commit-id'
466 pull_request.target_ref = unicode_reference
466 pull_request.target_ref = unicode_reference
467 Session().add(pull_request)
467 Session().add(pull_request)
468 Session().commit()
468 Session().commit()
469
469
470 pull_request_id = pull_request.pull_request_id
470 pull_request_id = pull_request.pull_request_id
471 pull_request_url = route_path(
471 pull_request_url = route_path(
472 'pullrequest_show',
472 'pullrequest_show',
473 repo_name=pull_request.target_repo.repo_name,
473 repo_name=pull_request.target_repo.repo_name,
474 pull_request_id=pull_request_id)
474 pull_request_id=pull_request_id)
475
475
476 response = self.app.get(pull_request_url)
476 response = self.app.get(pull_request_url)
477 target_ref_id = 'invalid-branch'
477 target_ref_id = 'invalid-branch'
478 merge_resp = MergeResponse(
478 merge_resp = MergeResponse(
479 True, True, '', MergeFailureReason.MISSING_TARGET_REF,
479 True, True, '', MergeFailureReason.MISSING_TARGET_REF,
480 metadata={'target_ref': PullRequest.unicode_to_reference(unicode_reference)})
480 metadata={'target_ref': PullRequest.unicode_to_reference(unicode_reference)})
481 response.assert_response().element_contains(
481 response.assert_response().element_contains(
482 'div[data-role="merge-message"]', merge_resp.merge_status_message)
482 'div[data-role="merge-message"]', merge_resp.merge_status_message)
483
483
484 def test_comment_and_close_pull_request_custom_message_approved(
484 def test_comment_and_close_pull_request_custom_message_approved(
485 self, pr_util, csrf_token, xhr_header):
485 self, pr_util, csrf_token, xhr_header):
486
486
487 pull_request = pr_util.create_pull_request(approved=True)
487 pull_request = pr_util.create_pull_request(approved=True)
488 pull_request_id = pull_request.pull_request_id
488 pull_request_id = pull_request.pull_request_id
489 author = pull_request.user_id
489 author = pull_request.user_id
490 repo = pull_request.target_repo.repo_id
490 repo = pull_request.target_repo.repo_id
491
491
492 self.app.post(
492 self.app.post(
493 route_path('pullrequest_comment_create',
493 route_path('pullrequest_comment_create',
494 repo_name=pull_request.target_repo.scm_instance().name,
494 repo_name=pull_request.target_repo.scm_instance().name,
495 pull_request_id=pull_request_id),
495 pull_request_id=pull_request_id),
496 params={
496 params={
497 'close_pull_request': '1',
497 'close_pull_request': '1',
498 'text': 'Closing a PR',
498 'text': 'Closing a PR',
499 'csrf_token': csrf_token},
499 'csrf_token': csrf_token},
500 extra_environ=xhr_header,)
500 extra_environ=xhr_header,)
501
501
502 journal = UserLog.query()\
502 journal = UserLog.query()\
503 .filter(UserLog.user_id == author)\
503 .filter(UserLog.user_id == author)\
504 .filter(UserLog.repository_id == repo) \
504 .filter(UserLog.repository_id == repo) \
505 .order_by(UserLog.user_log_id.asc()) \
505 .order_by(UserLog.user_log_id.asc()) \
506 .all()
506 .all()
507 assert journal[-1].action == 'repo.pull_request.close'
507 assert journal[-1].action == 'repo.pull_request.close'
508
508
509 pull_request = PullRequest.get(pull_request_id)
509 pull_request = PullRequest.get(pull_request_id)
510 assert pull_request.is_closed()
510 assert pull_request.is_closed()
511
511
512 status = ChangesetStatusModel().get_status(
512 status = ChangesetStatusModel().get_status(
513 pull_request.source_repo, pull_request=pull_request)
513 pull_request.source_repo, pull_request=pull_request)
514 assert status == ChangesetStatus.STATUS_APPROVED
514 assert status == ChangesetStatus.STATUS_APPROVED
515 comments = ChangesetComment().query() \
515 comments = ChangesetComment().query() \
516 .filter(ChangesetComment.pull_request == pull_request) \
516 .filter(ChangesetComment.pull_request == pull_request) \
517 .order_by(ChangesetComment.comment_id.asc())\
517 .order_by(ChangesetComment.comment_id.asc())\
518 .all()
518 .all()
519 assert comments[-1].text == 'Closing a PR'
519 assert comments[-1].text == 'Closing a PR'
520
520
521 def test_comment_force_close_pull_request_rejected(
521 def test_comment_force_close_pull_request_rejected(
522 self, pr_util, csrf_token, xhr_header):
522 self, pr_util, csrf_token, xhr_header):
523 pull_request = pr_util.create_pull_request()
523 pull_request = pr_util.create_pull_request()
524 pull_request_id = pull_request.pull_request_id
524 pull_request_id = pull_request.pull_request_id
525 PullRequestModel().update_reviewers(
525 PullRequestModel().update_reviewers(
526 pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
526 pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
527 pull_request.author)
527 pull_request.author)
528 author = pull_request.user_id
528 author = pull_request.user_id
529 repo = pull_request.target_repo.repo_id
529 repo = pull_request.target_repo.repo_id
530
530
531 self.app.post(
531 self.app.post(
532 route_path('pullrequest_comment_create',
532 route_path('pullrequest_comment_create',
533 repo_name=pull_request.target_repo.scm_instance().name,
533 repo_name=pull_request.target_repo.scm_instance().name,
534 pull_request_id=pull_request_id),
534 pull_request_id=pull_request_id),
535 params={
535 params={
536 'close_pull_request': '1',
536 'close_pull_request': '1',
537 'csrf_token': csrf_token},
537 'csrf_token': csrf_token},
538 extra_environ=xhr_header)
538 extra_environ=xhr_header)
539
539
540 pull_request = PullRequest.get(pull_request_id)
540 pull_request = PullRequest.get(pull_request_id)
541
541
542 journal = UserLog.query()\
542 journal = UserLog.query()\
543 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
543 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
544 .order_by(UserLog.user_log_id.asc()) \
544 .order_by(UserLog.user_log_id.asc()) \
545 .all()
545 .all()
546 assert journal[-1].action == 'repo.pull_request.close'
546 assert journal[-1].action == 'repo.pull_request.close'
547
547
548 # check only the latest status, not the review status
548 # check only the latest status, not the review status
549 status = ChangesetStatusModel().get_status(
549 status = ChangesetStatusModel().get_status(
550 pull_request.source_repo, pull_request=pull_request)
550 pull_request.source_repo, pull_request=pull_request)
551 assert status == ChangesetStatus.STATUS_REJECTED
551 assert status == ChangesetStatus.STATUS_REJECTED
552
552
553 def test_comment_and_close_pull_request(
553 def test_comment_and_close_pull_request(
554 self, pr_util, csrf_token, xhr_header):
554 self, pr_util, csrf_token, xhr_header):
555 pull_request = pr_util.create_pull_request()
555 pull_request = pr_util.create_pull_request()
556 pull_request_id = pull_request.pull_request_id
556 pull_request_id = pull_request.pull_request_id
557
557
558 response = self.app.post(
558 response = self.app.post(
559 route_path('pullrequest_comment_create',
559 route_path('pullrequest_comment_create',
560 repo_name=pull_request.target_repo.scm_instance().name,
560 repo_name=pull_request.target_repo.scm_instance().name,
561 pull_request_id=pull_request.pull_request_id),
561 pull_request_id=pull_request.pull_request_id),
562 params={
562 params={
563 'close_pull_request': 'true',
563 'close_pull_request': 'true',
564 'csrf_token': csrf_token},
564 'csrf_token': csrf_token},
565 extra_environ=xhr_header)
565 extra_environ=xhr_header)
566
566
567 assert response.json
567 assert response.json
568
568
569 pull_request = PullRequest.get(pull_request_id)
569 pull_request = PullRequest.get(pull_request_id)
570 assert pull_request.is_closed()
570 assert pull_request.is_closed()
571
571
572 # check only the latest status, not the review status
572 # check only the latest status, not the review status
573 status = ChangesetStatusModel().get_status(
573 status = ChangesetStatusModel().get_status(
574 pull_request.source_repo, pull_request=pull_request)
574 pull_request.source_repo, pull_request=pull_request)
575 assert status == ChangesetStatus.STATUS_REJECTED
575 assert status == ChangesetStatus.STATUS_REJECTED
576
576
577 def test_comment_and_close_pull_request_try_edit_comment(
577 def test_comment_and_close_pull_request_try_edit_comment(
578 self, pr_util, csrf_token, xhr_header
578 self, pr_util, csrf_token, xhr_header
579 ):
579 ):
580 pull_request = pr_util.create_pull_request()
580 pull_request = pr_util.create_pull_request()
581 pull_request_id = pull_request.pull_request_id
581 pull_request_id = pull_request.pull_request_id
582 target_scm = pull_request.target_repo.scm_instance()
582 target_scm = pull_request.target_repo.scm_instance()
583 target_scm_name = target_scm.name
583 target_scm_name = target_scm.name
584
584
585 response = self.app.post(
585 response = self.app.post(
586 route_path(
586 route_path(
587 'pullrequest_comment_create',
587 'pullrequest_comment_create',
588 repo_name=target_scm_name,
588 repo_name=target_scm_name,
589 pull_request_id=pull_request_id,
589 pull_request_id=pull_request_id,
590 ),
590 ),
591 params={
591 params={
592 'close_pull_request': 'true',
592 'close_pull_request': 'true',
593 'csrf_token': csrf_token,
593 'csrf_token': csrf_token,
594 },
594 },
595 extra_environ=xhr_header)
595 extra_environ=xhr_header)
596
596
597 assert response.json
597 assert response.json
598
598
599 pull_request = PullRequest.get(pull_request_id)
599 pull_request = PullRequest.get(pull_request_id)
600 target_scm = pull_request.target_repo.scm_instance()
600 target_scm = pull_request.target_repo.scm_instance()
601 target_scm_name = target_scm.name
601 target_scm_name = target_scm.name
602 assert pull_request.is_closed()
602 assert pull_request.is_closed()
603
603
604 # check only the latest status, not the review status
604 # check only the latest status, not the review status
605 status = ChangesetStatusModel().get_status(
605 status = ChangesetStatusModel().get_status(
606 pull_request.source_repo, pull_request=pull_request)
606 pull_request.source_repo, pull_request=pull_request)
607 assert status == ChangesetStatus.STATUS_REJECTED
607 assert status == ChangesetStatus.STATUS_REJECTED
608
608
609 comment_id = response.json.get('comment_id', None)
609 comment_id = response.json.get('comment_id', None)
610 test_text = 'test'
610 test_text = 'test'
611 response = self.app.post(
611 response = self.app.post(
612 route_path(
612 route_path(
613 'pullrequest_comment_edit',
613 'pullrequest_comment_edit',
614 repo_name=target_scm_name,
614 repo_name=target_scm_name,
615 pull_request_id=pull_request_id,
615 pull_request_id=pull_request_id,
616 comment_id=comment_id,
616 comment_id=comment_id,
617 ),
617 ),
618 extra_environ=xhr_header,
618 extra_environ=xhr_header,
619 params={
619 params={
620 'csrf_token': csrf_token,
620 'csrf_token': csrf_token,
621 'text': test_text,
621 'text': test_text,
622 },
622 },
623 status=403,
623 status=403,
624 )
624 )
625 assert response.status_int == 403
625 assert response.status_int == 403
626
626
627 def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header):
627 def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header):
628 pull_request = pr_util.create_pull_request()
628 pull_request = pr_util.create_pull_request()
629 target_scm = pull_request.target_repo.scm_instance()
629 target_scm = pull_request.target_repo.scm_instance()
630 target_scm_name = target_scm.name
630 target_scm_name = target_scm.name
631
631
632 response = self.app.post(
632 response = self.app.post(
633 route_path(
633 route_path(
634 'pullrequest_comment_create',
634 'pullrequest_comment_create',
635 repo_name=target_scm_name,
635 repo_name=target_scm_name,
636 pull_request_id=pull_request.pull_request_id),
636 pull_request_id=pull_request.pull_request_id),
637 params={
637 params={
638 'csrf_token': csrf_token,
638 'csrf_token': csrf_token,
639 'text': 'init',
639 'text': 'init',
640 },
640 },
641 extra_environ=xhr_header,
641 extra_environ=xhr_header,
642 )
642 )
643 assert response.json
643 assert response.json
644
644
645 comment_id = response.json.get('comment_id', None)
645 comment_id = response.json.get('comment_id', None)
646 assert comment_id
646 assert comment_id
647 test_text = 'test'
647 test_text = 'test'
648 self.app.post(
648 self.app.post(
649 route_path(
649 route_path(
650 'pullrequest_comment_edit',
650 'pullrequest_comment_edit',
651 repo_name=target_scm_name,
651 repo_name=target_scm_name,
652 pull_request_id=pull_request.pull_request_id,
652 pull_request_id=pull_request.pull_request_id,
653 comment_id=comment_id,
653 comment_id=comment_id,
654 ),
654 ),
655 extra_environ=xhr_header,
655 extra_environ=xhr_header,
656 params={
656 params={
657 'csrf_token': csrf_token,
657 'csrf_token': csrf_token,
658 'text': test_text,
658 'text': test_text,
659 'version': '0',
659 'version': '0',
660 },
660 },
661
661
662 )
662 )
663 text_form_db = ChangesetComment.query().filter(
663 text_form_db = ChangesetComment.query().filter(
664 ChangesetComment.comment_id == comment_id).first().text
664 ChangesetComment.comment_id == comment_id).first().text
665 assert test_text == text_form_db
665 assert test_text == text_form_db
666
666
667 def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header):
667 def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header):
668 pull_request = pr_util.create_pull_request()
668 pull_request = pr_util.create_pull_request()
669 target_scm = pull_request.target_repo.scm_instance()
669 target_scm = pull_request.target_repo.scm_instance()
670 target_scm_name = target_scm.name
670 target_scm_name = target_scm.name
671
671
672 response = self.app.post(
672 response = self.app.post(
673 route_path(
673 route_path(
674 'pullrequest_comment_create',
674 'pullrequest_comment_create',
675 repo_name=target_scm_name,
675 repo_name=target_scm_name,
676 pull_request_id=pull_request.pull_request_id),
676 pull_request_id=pull_request.pull_request_id),
677 params={
677 params={
678 'csrf_token': csrf_token,
678 'csrf_token': csrf_token,
679 'text': 'init',
679 'text': 'init',
680 },
680 },
681 extra_environ=xhr_header,
681 extra_environ=xhr_header,
682 )
682 )
683 assert response.json
683 assert response.json
684
684
685 comment_id = response.json.get('comment_id', None)
685 comment_id = response.json.get('comment_id', None)
686 assert comment_id
686 assert comment_id
687 test_text = 'init'
687 test_text = 'init'
688 response = self.app.post(
688 response = self.app.post(
689 route_path(
689 route_path(
690 'pullrequest_comment_edit',
690 'pullrequest_comment_edit',
691 repo_name=target_scm_name,
691 repo_name=target_scm_name,
692 pull_request_id=pull_request.pull_request_id,
692 pull_request_id=pull_request.pull_request_id,
693 comment_id=comment_id,
693 comment_id=comment_id,
694 ),
694 ),
695 extra_environ=xhr_header,
695 extra_environ=xhr_header,
696 params={
696 params={
697 'csrf_token': csrf_token,
697 'csrf_token': csrf_token,
698 'text': test_text,
698 'text': test_text,
699 'version': '0',
699 'version': '0',
700 },
700 },
701 status=404,
701 status=404,
702
702
703 )
703 )
704 assert response.status_int == 404
704 assert response.status_int == 404
705
705
706 def test_comment_and_try_edit_already_edited(self, pr_util, csrf_token, xhr_header):
706 def test_comment_and_try_edit_already_edited(self, pr_util, csrf_token, xhr_header):
707 pull_request = pr_util.create_pull_request()
707 pull_request = pr_util.create_pull_request()
708 target_scm = pull_request.target_repo.scm_instance()
708 target_scm = pull_request.target_repo.scm_instance()
709 target_scm_name = target_scm.name
709 target_scm_name = target_scm.name
710
710
711 response = self.app.post(
711 response = self.app.post(
712 route_path(
712 route_path(
713 'pullrequest_comment_create',
713 'pullrequest_comment_create',
714 repo_name=target_scm_name,
714 repo_name=target_scm_name,
715 pull_request_id=pull_request.pull_request_id),
715 pull_request_id=pull_request.pull_request_id),
716 params={
716 params={
717 'csrf_token': csrf_token,
717 'csrf_token': csrf_token,
718 'text': 'init',
718 'text': 'init',
719 },
719 },
720 extra_environ=xhr_header,
720 extra_environ=xhr_header,
721 )
721 )
722 assert response.json
722 assert response.json
723 comment_id = response.json.get('comment_id', None)
723 comment_id = response.json.get('comment_id', None)
724 assert comment_id
724 assert comment_id
725
725
726 test_text = 'test'
726 test_text = 'test'
727 self.app.post(
727 self.app.post(
728 route_path(
728 route_path(
729 'pullrequest_comment_edit',
729 'pullrequest_comment_edit',
730 repo_name=target_scm_name,
730 repo_name=target_scm_name,
731 pull_request_id=pull_request.pull_request_id,
731 pull_request_id=pull_request.pull_request_id,
732 comment_id=comment_id,
732 comment_id=comment_id,
733 ),
733 ),
734 extra_environ=xhr_header,
734 extra_environ=xhr_header,
735 params={
735 params={
736 'csrf_token': csrf_token,
736 'csrf_token': csrf_token,
737 'text': test_text,
737 'text': test_text,
738 'version': '0',
738 'version': '0',
739 },
739 },
740
740
741 )
741 )
742 test_text_v2 = 'test_v2'
742 test_text_v2 = 'test_v2'
743 response = self.app.post(
743 response = self.app.post(
744 route_path(
744 route_path(
745 'pullrequest_comment_edit',
745 'pullrequest_comment_edit',
746 repo_name=target_scm_name,
746 repo_name=target_scm_name,
747 pull_request_id=pull_request.pull_request_id,
747 pull_request_id=pull_request.pull_request_id,
748 comment_id=comment_id,
748 comment_id=comment_id,
749 ),
749 ),
750 extra_environ=xhr_header,
750 extra_environ=xhr_header,
751 params={
751 params={
752 'csrf_token': csrf_token,
752 'csrf_token': csrf_token,
753 'text': test_text_v2,
753 'text': test_text_v2,
754 'version': '0',
754 'version': '0',
755 },
755 },
756 status=409,
756 status=409,
757 )
757 )
758 assert response.status_int == 409
758 assert response.status_int == 409
759
759
760 text_form_db = ChangesetComment.query().filter(
760 text_form_db = ChangesetComment.query().filter(
761 ChangesetComment.comment_id == comment_id).first().text
761 ChangesetComment.comment_id == comment_id).first().text
762
762
763 assert test_text == text_form_db
763 assert test_text == text_form_db
764 assert test_text_v2 != text_form_db
764 assert test_text_v2 != text_form_db
765
765
766 def test_comment_and_comment_edit_permissions_forbidden(
766 def test_comment_and_comment_edit_permissions_forbidden(
767 self, autologin_regular_user, user_regular, user_admin, pr_util,
767 self, autologin_regular_user, user_regular, user_admin, pr_util,
768 csrf_token, xhr_header):
768 csrf_token, xhr_header):
769 pull_request = pr_util.create_pull_request(
769 pull_request = pr_util.create_pull_request(
770 author=user_admin.username, enable_notifications=False)
770 author=user_admin.username, enable_notifications=False)
771 comment = CommentsModel().create(
771 comment = CommentsModel().create(
772 text='test',
772 text='test',
773 repo=pull_request.target_repo.scm_instance().name,
773 repo=pull_request.target_repo.scm_instance().name,
774 user=user_admin,
774 user=user_admin,
775 pull_request=pull_request,
775 pull_request=pull_request,
776 )
776 )
777 response = self.app.post(
777 response = self.app.post(
778 route_path(
778 route_path(
779 'pullrequest_comment_edit',
779 'pullrequest_comment_edit',
780 repo_name=pull_request.target_repo.scm_instance().name,
780 repo_name=pull_request.target_repo.scm_instance().name,
781 pull_request_id=pull_request.pull_request_id,
781 pull_request_id=pull_request.pull_request_id,
782 comment_id=comment.comment_id,
782 comment_id=comment.comment_id,
783 ),
783 ),
784 extra_environ=xhr_header,
784 extra_environ=xhr_header,
785 params={
785 params={
786 'csrf_token': csrf_token,
786 'csrf_token': csrf_token,
787 'text': 'test_text',
787 'text': 'test_text',
788 },
788 },
789 status=403,
789 status=403,
790 )
790 )
791 assert response.status_int == 403
791 assert response.status_int == 403
792
792
793 def test_create_pull_request(self, backend, csrf_token):
793 def test_create_pull_request(self, backend, csrf_token):
794 commits = [
794 commits = [
795 {'message': 'ancestor'},
795 {'message': 'ancestor'},
796 {'message': 'change'},
796 {'message': 'change'},
797 {'message': 'change2'},
797 {'message': 'change2'},
798 ]
798 ]
799 commit_ids = backend.create_master_repo(commits)
799 commit_ids = backend.create_master_repo(commits)
800 target = backend.create_repo(heads=['ancestor'])
800 target = backend.create_repo(heads=['ancestor'])
801 source = backend.create_repo(heads=['change2'])
801 source = backend.create_repo(heads=['change2'])
802
802
803 response = self.app.post(
803 response = self.app.post(
804 route_path('pullrequest_create', repo_name=source.repo_name),
804 route_path('pullrequest_create', repo_name=source.repo_name),
805 [
805 [
806 ('source_repo', source.repo_name),
806 ('source_repo', source.repo_name),
807 ('source_ref', 'branch:default:' + commit_ids['change2']),
807 ('source_ref', 'branch:default:' + commit_ids['change2']),
808 ('target_repo', target.repo_name),
808 ('target_repo', target.repo_name),
809 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
809 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
810 ('common_ancestor', commit_ids['ancestor']),
810 ('common_ancestor', commit_ids['ancestor']),
811 ('pullrequest_title', 'Title'),
811 ('pullrequest_title', 'Title'),
812 ('pullrequest_desc', 'Description'),
812 ('pullrequest_desc', 'Description'),
813 ('description_renderer', 'markdown'),
813 ('description_renderer', 'markdown'),
814 ('__start__', 'review_members:sequence'),
814 ('__start__', 'review_members:sequence'),
815 ('__start__', 'reviewer:mapping'),
815 ('__start__', 'reviewer:mapping'),
816 ('user_id', '1'),
816 ('user_id', '1'),
817 ('__start__', 'reasons:sequence'),
817 ('__start__', 'reasons:sequence'),
818 ('reason', 'Some reason'),
818 ('reason', 'Some reason'),
819 ('__end__', 'reasons:sequence'),
819 ('__end__', 'reasons:sequence'),
820 ('__start__', 'rules:sequence'),
820 ('__start__', 'rules:sequence'),
821 ('__end__', 'rules:sequence'),
821 ('__end__', 'rules:sequence'),
822 ('mandatory', 'False'),
822 ('mandatory', 'False'),
823 ('__end__', 'reviewer:mapping'),
823 ('__end__', 'reviewer:mapping'),
824 ('__end__', 'review_members:sequence'),
824 ('__end__', 'review_members:sequence'),
825 ('__start__', 'revisions:sequence'),
825 ('__start__', 'revisions:sequence'),
826 ('revisions', commit_ids['change']),
826 ('revisions', commit_ids['change']),
827 ('revisions', commit_ids['change2']),
827 ('revisions', commit_ids['change2']),
828 ('__end__', 'revisions:sequence'),
828 ('__end__', 'revisions:sequence'),
829 ('user', ''),
829 ('user', ''),
830 ('csrf_token', csrf_token),
830 ('csrf_token', csrf_token),
831 ],
831 ],
832 status=302)
832 status=302)
833
833
834 location = response.headers['Location']
834 location = response.headers['Location']
835 pull_request_id = location.rsplit('/', 1)[1]
835 pull_request_id = location.rsplit('/', 1)[1]
836 assert pull_request_id != 'new'
836 assert pull_request_id != 'new'
837 pull_request = PullRequest.get(int(pull_request_id))
837 pull_request = PullRequest.get(int(pull_request_id))
838
838
839 # check that we have now both revisions
839 # check that we have now both revisions
840 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
840 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
841 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
841 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
842 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
842 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
843 assert pull_request.target_ref == expected_target_ref
843 assert pull_request.target_ref == expected_target_ref
844
844
845 def test_reviewer_notifications(self, backend, csrf_token):
845 def test_reviewer_notifications(self, backend, csrf_token):
846 # We have to use the app.post for this test so it will create the
846 # We have to use the app.post for this test so it will create the
847 # notifications properly with the new PR
847 # notifications properly with the new PR
848 commits = [
848 commits = [
849 {'message': 'ancestor',
849 {'message': 'ancestor',
850 'added': [FileNode('file_A', content='content_of_ancestor')]},
850 'added': [FileNode('file_A', content='content_of_ancestor')]},
851 {'message': 'change',
851 {'message': 'change',
852 'added': [FileNode('file_a', content='content_of_change')]},
852 'added': [FileNode('file_a', content='content_of_change')]},
853 {'message': 'change-child'},
853 {'message': 'change-child'},
854 {'message': 'ancestor-child', 'parents': ['ancestor'],
854 {'message': 'ancestor-child', 'parents': ['ancestor'],
855 'added': [
855 'added': [
856 FileNode('file_B', content='content_of_ancestor_child')]},
856 FileNode('file_B', content='content_of_ancestor_child')]},
857 {'message': 'ancestor-child-2'},
857 {'message': 'ancestor-child-2'},
858 ]
858 ]
859 commit_ids = backend.create_master_repo(commits)
859 commit_ids = backend.create_master_repo(commits)
860 target = backend.create_repo(heads=['ancestor-child'])
860 target = backend.create_repo(heads=['ancestor-child'])
861 source = backend.create_repo(heads=['change'])
861 source = backend.create_repo(heads=['change'])
862
862
863 response = self.app.post(
863 response = self.app.post(
864 route_path('pullrequest_create', repo_name=source.repo_name),
864 route_path('pullrequest_create', repo_name=source.repo_name),
865 [
865 [
866 ('source_repo', source.repo_name),
866 ('source_repo', source.repo_name),
867 ('source_ref', 'branch:default:' + commit_ids['change']),
867 ('source_ref', 'branch:default:' + commit_ids['change']),
868 ('target_repo', target.repo_name),
868 ('target_repo', target.repo_name),
869 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
869 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
870 ('common_ancestor', commit_ids['ancestor']),
870 ('common_ancestor', commit_ids['ancestor']),
871 ('pullrequest_title', 'Title'),
871 ('pullrequest_title', 'Title'),
872 ('pullrequest_desc', 'Description'),
872 ('pullrequest_desc', 'Description'),
873 ('description_renderer', 'markdown'),
873 ('description_renderer', 'markdown'),
874 ('__start__', 'review_members:sequence'),
874 ('__start__', 'review_members:sequence'),
875 ('__start__', 'reviewer:mapping'),
875 ('__start__', 'reviewer:mapping'),
876 ('user_id', '2'),
876 ('user_id', '2'),
877 ('__start__', 'reasons:sequence'),
877 ('__start__', 'reasons:sequence'),
878 ('reason', 'Some reason'),
878 ('reason', 'Some reason'),
879 ('__end__', 'reasons:sequence'),
879 ('__end__', 'reasons:sequence'),
880 ('__start__', 'rules:sequence'),
880 ('__start__', 'rules:sequence'),
881 ('__end__', 'rules:sequence'),
881 ('__end__', 'rules:sequence'),
882 ('mandatory', 'False'),
882 ('mandatory', 'False'),
883 ('__end__', 'reviewer:mapping'),
883 ('__end__', 'reviewer:mapping'),
884 ('__end__', 'review_members:sequence'),
884 ('__end__', 'review_members:sequence'),
885 ('__start__', 'revisions:sequence'),
885 ('__start__', 'revisions:sequence'),
886 ('revisions', commit_ids['change']),
886 ('revisions', commit_ids['change']),
887 ('__end__', 'revisions:sequence'),
887 ('__end__', 'revisions:sequence'),
888 ('user', ''),
888 ('user', ''),
889 ('csrf_token', csrf_token),
889 ('csrf_token', csrf_token),
890 ],
890 ],
891 status=302)
891 status=302)
892
892
893 location = response.headers['Location']
893 location = response.headers['Location']
894
894
895 pull_request_id = location.rsplit('/', 1)[1]
895 pull_request_id = location.rsplit('/', 1)[1]
896 assert pull_request_id != 'new'
896 assert pull_request_id != 'new'
897 pull_request = PullRequest.get(int(pull_request_id))
897 pull_request = PullRequest.get(int(pull_request_id))
898
898
899 # Check that a notification was made
899 # Check that a notification was made
900 notifications = Notification.query()\
900 notifications = Notification.query()\
901 .filter(Notification.created_by == pull_request.author.user_id,
901 .filter(Notification.created_by == pull_request.author.user_id,
902 Notification.type_ == Notification.TYPE_PULL_REQUEST,
902 Notification.type_ == Notification.TYPE_PULL_REQUEST,
903 Notification.subject.contains(
903 Notification.subject.contains(
904 "requested a pull request review. !%s" % pull_request_id))
904 "requested a pull request review. !%s" % pull_request_id))
905 assert len(notifications.all()) == 1
905 assert len(notifications.all()) == 1
906
906
907 # Change reviewers and check that a notification was made
907 # Change reviewers and check that a notification was made
908 PullRequestModel().update_reviewers(
908 PullRequestModel().update_reviewers(
909 pull_request.pull_request_id, [(1, [], False, [])],
909 pull_request.pull_request_id, [(1, [], False, [])],
910 pull_request.author)
910 pull_request.author)
911 assert len(notifications.all()) == 2
911 assert len(notifications.all()) == 2
912
912
913 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
913 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
914 csrf_token):
914 csrf_token):
915 commits = [
915 commits = [
916 {'message': 'ancestor',
916 {'message': 'ancestor',
917 'added': [FileNode('file_A', content='content_of_ancestor')]},
917 'added': [FileNode('file_A', content='content_of_ancestor')]},
918 {'message': 'change',
918 {'message': 'change',
919 'added': [FileNode('file_a', content='content_of_change')]},
919 'added': [FileNode('file_a', content='content_of_change')]},
920 {'message': 'change-child'},
920 {'message': 'change-child'},
921 {'message': 'ancestor-child', 'parents': ['ancestor'],
921 {'message': 'ancestor-child', 'parents': ['ancestor'],
922 'added': [
922 'added': [
923 FileNode('file_B', content='content_of_ancestor_child')]},
923 FileNode('file_B', content='content_of_ancestor_child')]},
924 {'message': 'ancestor-child-2'},
924 {'message': 'ancestor-child-2'},
925 ]
925 ]
926 commit_ids = backend.create_master_repo(commits)
926 commit_ids = backend.create_master_repo(commits)
927 target = backend.create_repo(heads=['ancestor-child'])
927 target = backend.create_repo(heads=['ancestor-child'])
928 source = backend.create_repo(heads=['change'])
928 source = backend.create_repo(heads=['change'])
929
929
930 response = self.app.post(
930 response = self.app.post(
931 route_path('pullrequest_create', repo_name=source.repo_name),
931 route_path('pullrequest_create', repo_name=source.repo_name),
932 [
932 [
933 ('source_repo', source.repo_name),
933 ('source_repo', source.repo_name),
934 ('source_ref', 'branch:default:' + commit_ids['change']),
934 ('source_ref', 'branch:default:' + commit_ids['change']),
935 ('target_repo', target.repo_name),
935 ('target_repo', target.repo_name),
936 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
936 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
937 ('common_ancestor', commit_ids['ancestor']),
937 ('common_ancestor', commit_ids['ancestor']),
938 ('pullrequest_title', 'Title'),
938 ('pullrequest_title', 'Title'),
939 ('pullrequest_desc', 'Description'),
939 ('pullrequest_desc', 'Description'),
940 ('description_renderer', 'markdown'),
940 ('description_renderer', 'markdown'),
941 ('__start__', 'review_members:sequence'),
941 ('__start__', 'review_members:sequence'),
942 ('__start__', 'reviewer:mapping'),
942 ('__start__', 'reviewer:mapping'),
943 ('user_id', '1'),
943 ('user_id', '1'),
944 ('__start__', 'reasons:sequence'),
944 ('__start__', 'reasons:sequence'),
945 ('reason', 'Some reason'),
945 ('reason', 'Some reason'),
946 ('__end__', 'reasons:sequence'),
946 ('__end__', 'reasons:sequence'),
947 ('__start__', 'rules:sequence'),
947 ('__start__', 'rules:sequence'),
948 ('__end__', 'rules:sequence'),
948 ('__end__', 'rules:sequence'),
949 ('mandatory', 'False'),
949 ('mandatory', 'False'),
950 ('__end__', 'reviewer:mapping'),
950 ('__end__', 'reviewer:mapping'),
951 ('__end__', 'review_members:sequence'),
951 ('__end__', 'review_members:sequence'),
952 ('__start__', 'revisions:sequence'),
952 ('__start__', 'revisions:sequence'),
953 ('revisions', commit_ids['change']),
953 ('revisions', commit_ids['change']),
954 ('__end__', 'revisions:sequence'),
954 ('__end__', 'revisions:sequence'),
955 ('user', ''),
955 ('user', ''),
956 ('csrf_token', csrf_token),
956 ('csrf_token', csrf_token),
957 ],
957 ],
958 status=302)
958 status=302)
959
959
960 location = response.headers['Location']
960 location = response.headers['Location']
961
961
962 pull_request_id = location.rsplit('/', 1)[1]
962 pull_request_id = location.rsplit('/', 1)[1]
963 assert pull_request_id != 'new'
963 assert pull_request_id != 'new'
964 pull_request = PullRequest.get(int(pull_request_id))
964 pull_request = PullRequest.get(int(pull_request_id))
965
965
966 # target_ref has to point to the ancestor's commit_id in order to
966 # target_ref has to point to the ancestor's commit_id in order to
967 # show the correct diff
967 # show the correct diff
968 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
968 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
969 assert pull_request.target_ref == expected_target_ref
969 assert pull_request.target_ref == expected_target_ref
970
970
971 # Check generated diff contents
971 # Check generated diff contents
972 response = response.follow()
972 response = response.follow()
973 response.mustcontain(no=['content_of_ancestor'])
973 response.mustcontain(no=['content_of_ancestor'])
974 response.mustcontain(no=['content_of_ancestor-child'])
974 response.mustcontain(no=['content_of_ancestor-child'])
975 response.mustcontain('content_of_change')
975 response.mustcontain('content_of_change')
976
976
977 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
977 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
978 # Clear any previous calls to rcextensions
978 # Clear any previous calls to rcextensions
979 rhodecode.EXTENSIONS.calls.clear()
979 rhodecode.EXTENSIONS.calls.clear()
980
980
981 pull_request = pr_util.create_pull_request(
981 pull_request = pr_util.create_pull_request(
982 approved=True, mergeable=True)
982 approved=True, mergeable=True)
983 pull_request_id = pull_request.pull_request_id
983 pull_request_id = pull_request.pull_request_id
984 repo_name = pull_request.target_repo.scm_instance().name,
984 repo_name = pull_request.target_repo.scm_instance().name,
985
985
986 url = route_path('pullrequest_merge',
986 url = route_path('pullrequest_merge',
987 repo_name=str(repo_name[0]),
987 repo_name=str(repo_name[0]),
988 pull_request_id=pull_request_id)
988 pull_request_id=pull_request_id)
989 response = self.app.post(url, params={'csrf_token': csrf_token}).follow()
989 response = self.app.post(url, params={'csrf_token': csrf_token}).follow()
990
990
991 pull_request = PullRequest.get(pull_request_id)
991 pull_request = PullRequest.get(pull_request_id)
992
992
993 assert response.status_int == 200
993 assert response.status_int == 200
994 assert pull_request.is_closed()
994 assert pull_request.is_closed()
995 assert_pull_request_status(
995 assert_pull_request_status(
996 pull_request, ChangesetStatus.STATUS_APPROVED)
996 pull_request, ChangesetStatus.STATUS_APPROVED)
997
997
998 # Check the relevant log entries were added
998 # Check the relevant log entries were added
999 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(3)
999 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(3)
1000 actions = [log.action for log in user_logs]
1000 actions = [log.action for log in user_logs]
1001 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
1001 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
1002 expected_actions = [
1002 expected_actions = [
1003 u'repo.pull_request.close',
1003 u'repo.pull_request.close',
1004 u'repo.pull_request.merge',
1004 u'repo.pull_request.merge',
1005 u'repo.pull_request.comment.create'
1005 u'repo.pull_request.comment.create'
1006 ]
1006 ]
1007 assert actions == expected_actions
1007 assert actions == expected_actions
1008
1008
1009 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(4)
1009 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(4)
1010 actions = [log for log in user_logs]
1010 actions = [log for log in user_logs]
1011 assert actions[-1].action == 'user.push'
1011 assert actions[-1].action == 'user.push'
1012 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
1012 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
1013
1013
1014 # Check post_push rcextension was really executed
1014 # Check post_push rcextension was really executed
1015 push_calls = rhodecode.EXTENSIONS.calls['_push_hook']
1015 push_calls = rhodecode.EXTENSIONS.calls['_push_hook']
1016 assert len(push_calls) == 1
1016 assert len(push_calls) == 1
1017 unused_last_call_args, last_call_kwargs = push_calls[0]
1017 unused_last_call_args, last_call_kwargs = push_calls[0]
1018 assert last_call_kwargs['action'] == 'push'
1018 assert last_call_kwargs['action'] == 'push'
1019 assert last_call_kwargs['commit_ids'] == pr_commit_ids
1019 assert last_call_kwargs['commit_ids'] == pr_commit_ids
1020
1020
1021 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
1021 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
1022 pull_request = pr_util.create_pull_request(mergeable=False)
1022 pull_request = pr_util.create_pull_request(mergeable=False)
1023 pull_request_id = pull_request.pull_request_id
1023 pull_request_id = pull_request.pull_request_id
1024 pull_request = PullRequest.get(pull_request_id)
1024 pull_request = PullRequest.get(pull_request_id)
1025
1025
1026 response = self.app.post(
1026 response = self.app.post(
1027 route_path('pullrequest_merge',
1027 route_path('pullrequest_merge',
1028 repo_name=pull_request.target_repo.scm_instance().name,
1028 repo_name=pull_request.target_repo.scm_instance().name,
1029 pull_request_id=pull_request.pull_request_id),
1029 pull_request_id=pull_request.pull_request_id),
1030 params={'csrf_token': csrf_token}).follow()
1030 params={'csrf_token': csrf_token}).follow()
1031
1031
1032 assert response.status_int == 200
1032 assert response.status_int == 200
1033 response.mustcontain(
1033 response.mustcontain(
1034 'Merge is not currently possible because of below failed checks.')
1034 'Merge is not currently possible because of below failed checks.')
1035 response.mustcontain('Server-side pull request merging is disabled.')
1035 response.mustcontain('Server-side pull request merging is disabled.')
1036
1036
1037 @pytest.mark.skip_backends('svn')
1037 @pytest.mark.skip_backends('svn')
1038 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
1038 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
1039 pull_request = pr_util.create_pull_request(mergeable=True)
1039 pull_request = pr_util.create_pull_request(mergeable=True)
1040 pull_request_id = pull_request.pull_request_id
1040 pull_request_id = pull_request.pull_request_id
1041 repo_name = pull_request.target_repo.scm_instance().name
1041 repo_name = pull_request.target_repo.scm_instance().name
1042
1042
1043 response = self.app.post(
1043 response = self.app.post(
1044 route_path('pullrequest_merge',
1044 route_path('pullrequest_merge',
1045 repo_name=repo_name, pull_request_id=pull_request_id),
1045 repo_name=repo_name, pull_request_id=pull_request_id),
1046 params={'csrf_token': csrf_token}).follow()
1046 params={'csrf_token': csrf_token}).follow()
1047
1047
1048 assert response.status_int == 200
1048 assert response.status_int == 200
1049
1049
1050 response.mustcontain(
1050 response.mustcontain(
1051 'Merge is not currently possible because of below failed checks.')
1051 'Merge is not currently possible because of below failed checks.')
1052 response.mustcontain('Pull request reviewer approval is pending.')
1052 response.mustcontain('Pull request reviewer approval is pending.')
1053
1053
1054 def test_merge_pull_request_renders_failure_reason(
1054 def test_merge_pull_request_renders_failure_reason(
1055 self, user_regular, csrf_token, pr_util):
1055 self, user_regular, csrf_token, pr_util):
1056 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
1056 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
1057 pull_request_id = pull_request.pull_request_id
1057 pull_request_id = pull_request.pull_request_id
1058 repo_name = pull_request.target_repo.scm_instance().name
1058 repo_name = pull_request.target_repo.scm_instance().name
1059
1059
1060 merge_resp = MergeResponse(True, False, 'STUB_COMMIT_ID',
1060 merge_resp = MergeResponse(True, False, 'STUB_COMMIT_ID',
1061 MergeFailureReason.PUSH_FAILED,
1061 MergeFailureReason.PUSH_FAILED,
1062 metadata={'target': 'shadow repo',
1062 metadata={'target': 'shadow repo',
1063 'merge_commit': 'xxx'})
1063 'merge_commit': 'xxx'})
1064 model_patcher = mock.patch.multiple(
1064 model_patcher = mock.patch.multiple(
1065 PullRequestModel,
1065 PullRequestModel,
1066 merge_repo=mock.Mock(return_value=merge_resp),
1066 merge_repo=mock.Mock(return_value=merge_resp),
1067 merge_status=mock.Mock(return_value=(None, True, 'WRONG_MESSAGE')))
1067 merge_status=mock.Mock(return_value=(None, True, 'WRONG_MESSAGE')))
1068
1068
1069 with model_patcher:
1069 with model_patcher:
1070 response = self.app.post(
1070 response = self.app.post(
1071 route_path('pullrequest_merge',
1071 route_path('pullrequest_merge',
1072 repo_name=repo_name,
1072 repo_name=repo_name,
1073 pull_request_id=pull_request_id),
1073 pull_request_id=pull_request_id),
1074 params={'csrf_token': csrf_token}, status=302)
1074 params={'csrf_token': csrf_token}, status=302)
1075
1075
1076 merge_resp = MergeResponse(True, True, '', MergeFailureReason.PUSH_FAILED,
1076 merge_resp = MergeResponse(True, True, '', MergeFailureReason.PUSH_FAILED,
1077 metadata={'target': 'shadow repo',
1077 metadata={'target': 'shadow repo',
1078 'merge_commit': 'xxx'})
1078 'merge_commit': 'xxx'})
1079 assert_session_flash(response, merge_resp.merge_status_message)
1079 assert_session_flash(response, merge_resp.merge_status_message)
1080
1080
1081 def test_update_source_revision(self, backend, csrf_token):
1081 def test_update_source_revision(self, backend, csrf_token):
1082 commits = [
1082 commits = [
1083 {'message': 'ancestor'},
1083 {'message': 'ancestor'},
1084 {'message': 'change'},
1084 {'message': 'change'},
1085 {'message': 'change-2'},
1085 {'message': 'change-2'},
1086 ]
1086 ]
1087 commit_ids = backend.create_master_repo(commits)
1087 commit_ids = backend.create_master_repo(commits)
1088 target = backend.create_repo(heads=['ancestor'])
1088 target = backend.create_repo(heads=['ancestor'])
1089 source = backend.create_repo(heads=['change'])
1089 source = backend.create_repo(heads=['change'])
1090
1090
1091 # create pr from a in source to A in target
1091 # create pr from a in source to A in target
1092 pull_request = PullRequest()
1092 pull_request = PullRequest()
1093
1093
1094 pull_request.source_repo = source
1094 pull_request.source_repo = source
1095 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1095 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1096 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1096 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1097
1097
1098 pull_request.target_repo = target
1098 pull_request.target_repo = target
1099 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1099 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1100 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1100 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1101
1101
1102 pull_request.revisions = [commit_ids['change']]
1102 pull_request.revisions = [commit_ids['change']]
1103 pull_request.title = u"Test"
1103 pull_request.title = u"Test"
1104 pull_request.description = u"Description"
1104 pull_request.description = u"Description"
1105 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1105 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1106 pull_request.pull_request_state = PullRequest.STATE_CREATED
1106 pull_request.pull_request_state = PullRequest.STATE_CREATED
1107 Session().add(pull_request)
1107 Session().add(pull_request)
1108 Session().commit()
1108 Session().commit()
1109 pull_request_id = pull_request.pull_request_id
1109 pull_request_id = pull_request.pull_request_id
1110
1110
1111 # source has ancestor - change - change-2
1111 # source has ancestor - change - change-2
1112 backend.pull_heads(source, heads=['change-2'])
1112 backend.pull_heads(source, heads=['change-2'])
1113 target_repo_name = target.repo_name
1113
1114
1114 # update PR
1115 # update PR
1115 self.app.post(
1116 self.app.post(
1116 route_path('pullrequest_update',
1117 route_path('pullrequest_update',
1117 repo_name=target.repo_name, pull_request_id=pull_request_id),
1118 repo_name=target_repo_name, pull_request_id=pull_request_id),
1118 params={'update_commits': 'true', 'csrf_token': csrf_token})
1119 params={'update_commits': 'true', 'csrf_token': csrf_token})
1119
1120
1120 response = self.app.get(
1121 response = self.app.get(
1121 route_path('pullrequest_show',
1122 route_path('pullrequest_show',
1122 repo_name=target.repo_name,
1123 repo_name=target_repo_name,
1123 pull_request_id=pull_request.pull_request_id))
1124 pull_request_id=pull_request.pull_request_id))
1124
1125
1125 assert response.status_int == 200
1126 assert response.status_int == 200
1126 response.mustcontain('Pull request updated to')
1127 response.mustcontain('Pull request updated to')
1127 response.mustcontain('with 1 added, 0 removed commits.')
1128 response.mustcontain('with 1 added, 0 removed commits.')
1128
1129
1129 # check that we have now both revisions
1130 # check that we have now both revisions
1130 pull_request = PullRequest.get(pull_request_id)
1131 pull_request = PullRequest.get(pull_request_id)
1131 assert pull_request.revisions == [commit_ids['change-2'], commit_ids['change']]
1132 assert pull_request.revisions == [commit_ids['change-2'], commit_ids['change']]
1132
1133
1133 def test_update_target_revision(self, backend, csrf_token):
1134 def test_update_target_revision(self, backend, csrf_token):
1134 commits = [
1135 commits = [
1135 {'message': 'ancestor'},
1136 {'message': 'ancestor'},
1136 {'message': 'change'},
1137 {'message': 'change'},
1137 {'message': 'ancestor-new', 'parents': ['ancestor']},
1138 {'message': 'ancestor-new', 'parents': ['ancestor']},
1138 {'message': 'change-rebased'},
1139 {'message': 'change-rebased'},
1139 ]
1140 ]
1140 commit_ids = backend.create_master_repo(commits)
1141 commit_ids = backend.create_master_repo(commits)
1141 target = backend.create_repo(heads=['ancestor'])
1142 target = backend.create_repo(heads=['ancestor'])
1142 source = backend.create_repo(heads=['change'])
1143 source = backend.create_repo(heads=['change'])
1143
1144
1144 # create pr from a in source to A in target
1145 # create pr from a in source to A in target
1145 pull_request = PullRequest()
1146 pull_request = PullRequest()
1146
1147
1147 pull_request.source_repo = source
1148 pull_request.source_repo = source
1148 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1149 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1149 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1150 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1150
1151
1151 pull_request.target_repo = target
1152 pull_request.target_repo = target
1152 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1153 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1153 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1154 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1154
1155
1155 pull_request.revisions = [commit_ids['change']]
1156 pull_request.revisions = [commit_ids['change']]
1156 pull_request.title = u"Test"
1157 pull_request.title = u"Test"
1157 pull_request.description = u"Description"
1158 pull_request.description = u"Description"
1158 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1159 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1159 pull_request.pull_request_state = PullRequest.STATE_CREATED
1160 pull_request.pull_request_state = PullRequest.STATE_CREATED
1160
1161
1161 Session().add(pull_request)
1162 Session().add(pull_request)
1162 Session().commit()
1163 Session().commit()
1163 pull_request_id = pull_request.pull_request_id
1164 pull_request_id = pull_request.pull_request_id
1164
1165
1165 # target has ancestor - ancestor-new
1166 # target has ancestor - ancestor-new
1166 # source has ancestor - ancestor-new - change-rebased
1167 # source has ancestor - ancestor-new - change-rebased
1167 backend.pull_heads(target, heads=['ancestor-new'])
1168 backend.pull_heads(target, heads=['ancestor-new'])
1168 backend.pull_heads(source, heads=['change-rebased'])
1169 backend.pull_heads(source, heads=['change-rebased'])
1170 target_repo_name = target.repo_name
1169
1171
1170 # update PR
1172 # update PR
1171 url = route_path('pullrequest_update',
1173 url = route_path('pullrequest_update',
1172 repo_name=target.repo_name,
1174 repo_name=target_repo_name,
1173 pull_request_id=pull_request_id)
1175 pull_request_id=pull_request_id)
1174 self.app.post(url,
1176 self.app.post(url,
1175 params={'update_commits': 'true', 'csrf_token': csrf_token},
1177 params={'update_commits': 'true', 'csrf_token': csrf_token},
1176 status=200)
1178 status=200)
1177
1179
1178 # check that we have now both revisions
1180 # check that we have now both revisions
1179 pull_request = PullRequest.get(pull_request_id)
1181 pull_request = PullRequest.get(pull_request_id)
1180 assert pull_request.revisions == [commit_ids['change-rebased']]
1182 assert pull_request.revisions == [commit_ids['change-rebased']]
1181 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
1183 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
1182 branch=backend.default_branch_name, commit_id=commit_ids['ancestor-new'])
1184 branch=backend.default_branch_name, commit_id=commit_ids['ancestor-new'])
1183
1185
1184 response = self.app.get(
1186 response = self.app.get(
1185 route_path('pullrequest_show',
1187 route_path('pullrequest_show',
1186 repo_name=target.repo_name,
1188 repo_name=target_repo_name,
1187 pull_request_id=pull_request.pull_request_id))
1189 pull_request_id=pull_request.pull_request_id))
1188 assert response.status_int == 200
1190 assert response.status_int == 200
1189 response.mustcontain('Pull request updated to')
1191 response.mustcontain('Pull request updated to')
1190 response.mustcontain('with 1 added, 1 removed commits.')
1192 response.mustcontain('with 1 added, 1 removed commits.')
1191
1193
1192 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
1194 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
1193 backend = backend_git
1195 backend = backend_git
1194 commits = [
1196 commits = [
1195 {'message': 'master-commit-1'},
1197 {'message': 'master-commit-1'},
1196 {'message': 'master-commit-2-change-1'},
1198 {'message': 'master-commit-2-change-1'},
1197 {'message': 'master-commit-3-change-2'},
1199 {'message': 'master-commit-3-change-2'},
1198
1200
1199 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
1201 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
1200 {'message': 'feat-commit-2'},
1202 {'message': 'feat-commit-2'},
1201 ]
1203 ]
1202 commit_ids = backend.create_master_repo(commits)
1204 commit_ids = backend.create_master_repo(commits)
1203 target = backend.create_repo(heads=['master-commit-3-change-2'])
1205 target = backend.create_repo(heads=['master-commit-3-change-2'])
1204 source = backend.create_repo(heads=['feat-commit-2'])
1206 source = backend.create_repo(heads=['feat-commit-2'])
1205
1207
1206 # create pr from a in source to A in target
1208 # create pr from a in source to A in target
1207 pull_request = PullRequest()
1209 pull_request = PullRequest()
1208 pull_request.source_repo = source
1210 pull_request.source_repo = source
1209
1211
1210 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1212 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1211 branch=backend.default_branch_name,
1213 branch=backend.default_branch_name,
1212 commit_id=commit_ids['master-commit-3-change-2'])
1214 commit_id=commit_ids['master-commit-3-change-2'])
1213
1215
1214 pull_request.target_repo = target
1216 pull_request.target_repo = target
1215 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1217 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1216 branch=backend.default_branch_name, commit_id=commit_ids['feat-commit-2'])
1218 branch=backend.default_branch_name, commit_id=commit_ids['feat-commit-2'])
1217
1219
1218 pull_request.revisions = [
1220 pull_request.revisions = [
1219 commit_ids['feat-commit-1'],
1221 commit_ids['feat-commit-1'],
1220 commit_ids['feat-commit-2']
1222 commit_ids['feat-commit-2']
1221 ]
1223 ]
1222 pull_request.title = u"Test"
1224 pull_request.title = u"Test"
1223 pull_request.description = u"Description"
1225 pull_request.description = u"Description"
1224 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1226 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1225 pull_request.pull_request_state = PullRequest.STATE_CREATED
1227 pull_request.pull_request_state = PullRequest.STATE_CREATED
1226 Session().add(pull_request)
1228 Session().add(pull_request)
1227 Session().commit()
1229 Session().commit()
1228 pull_request_id = pull_request.pull_request_id
1230 pull_request_id = pull_request.pull_request_id
1229
1231
1230 # PR is created, now we simulate a force-push into target,
1232 # PR is created, now we simulate a force-push into target,
1231 # that drops a 2 last commits
1233 # that drops a 2 last commits
1232 vcsrepo = target.scm_instance()
1234 vcsrepo = target.scm_instance()
1233 vcsrepo.config.clear_section('hooks')
1235 vcsrepo.config.clear_section('hooks')
1234 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
1236 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
1237 target_repo_name = target.repo_name
1235
1238
1236 # update PR
1239 # update PR
1237 url = route_path('pullrequest_update',
1240 url = route_path('pullrequest_update',
1238 repo_name=target.repo_name,
1241 repo_name=target_repo_name,
1239 pull_request_id=pull_request_id)
1242 pull_request_id=pull_request_id)
1240 self.app.post(url,
1243 self.app.post(url,
1241 params={'update_commits': 'true', 'csrf_token': csrf_token},
1244 params={'update_commits': 'true', 'csrf_token': csrf_token},
1242 status=200)
1245 status=200)
1243
1246
1244 response = self.app.get(route_path('pullrequest_new', repo_name=target.repo_name))
1247 response = self.app.get(route_path('pullrequest_new', repo_name=target_repo_name))
1245 assert response.status_int == 200
1248 assert response.status_int == 200
1246 response.mustcontain('Pull request updated to')
1249 response.mustcontain('Pull request updated to')
1247 response.mustcontain('with 0 added, 0 removed commits.')
1250 response.mustcontain('with 0 added, 0 removed commits.')
1248
1251
1249 def test_update_of_ancestor_reference(self, backend, csrf_token):
1252 def test_update_of_ancestor_reference(self, backend, csrf_token):
1250 commits = [
1253 commits = [
1251 {'message': 'ancestor'},
1254 {'message': 'ancestor'},
1252 {'message': 'change'},
1255 {'message': 'change'},
1253 {'message': 'change-2'},
1256 {'message': 'change-2'},
1254 {'message': 'ancestor-new', 'parents': ['ancestor']},
1257 {'message': 'ancestor-new', 'parents': ['ancestor']},
1255 {'message': 'change-rebased'},
1258 {'message': 'change-rebased'},
1256 ]
1259 ]
1257 commit_ids = backend.create_master_repo(commits)
1260 commit_ids = backend.create_master_repo(commits)
1258 target = backend.create_repo(heads=['ancestor'])
1261 target = backend.create_repo(heads=['ancestor'])
1259 source = backend.create_repo(heads=['change'])
1262 source = backend.create_repo(heads=['change'])
1260
1263
1261 # create pr from a in source to A in target
1264 # create pr from a in source to A in target
1262 pull_request = PullRequest()
1265 pull_request = PullRequest()
1263 pull_request.source_repo = source
1266 pull_request.source_repo = source
1264
1267
1265 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1268 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1266 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1269 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1267 pull_request.target_repo = target
1270 pull_request.target_repo = target
1268 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1271 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1269 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1272 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1270 pull_request.revisions = [commit_ids['change']]
1273 pull_request.revisions = [commit_ids['change']]
1271 pull_request.title = u"Test"
1274 pull_request.title = u"Test"
1272 pull_request.description = u"Description"
1275 pull_request.description = u"Description"
1273 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1276 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1274 pull_request.pull_request_state = PullRequest.STATE_CREATED
1277 pull_request.pull_request_state = PullRequest.STATE_CREATED
1275 Session().add(pull_request)
1278 Session().add(pull_request)
1276 Session().commit()
1279 Session().commit()
1277 pull_request_id = pull_request.pull_request_id
1280 pull_request_id = pull_request.pull_request_id
1278
1281
1279 # target has ancestor - ancestor-new
1282 # target has ancestor - ancestor-new
1280 # source has ancestor - ancestor-new - change-rebased
1283 # source has ancestor - ancestor-new - change-rebased
1281 backend.pull_heads(target, heads=['ancestor-new'])
1284 backend.pull_heads(target, heads=['ancestor-new'])
1282 backend.pull_heads(source, heads=['change-rebased'])
1285 backend.pull_heads(source, heads=['change-rebased'])
1286 target_repo_name = target.repo_name
1283
1287
1284 # update PR
1288 # update PR
1285 self.app.post(
1289 self.app.post(
1286 route_path('pullrequest_update',
1290 route_path('pullrequest_update',
1287 repo_name=target.repo_name, pull_request_id=pull_request_id),
1291 repo_name=target_repo_name, pull_request_id=pull_request_id),
1288 params={'update_commits': 'true', 'csrf_token': csrf_token},
1292 params={'update_commits': 'true', 'csrf_token': csrf_token},
1289 status=200)
1293 status=200)
1290
1294
1291 # Expect the target reference to be updated correctly
1295 # Expect the target reference to be updated correctly
1292 pull_request = PullRequest.get(pull_request_id)
1296 pull_request = PullRequest.get(pull_request_id)
1293 assert pull_request.revisions == [commit_ids['change-rebased']]
1297 assert pull_request.revisions == [commit_ids['change-rebased']]
1294 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
1298 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
1295 branch=backend.default_branch_name,
1299 branch=backend.default_branch_name,
1296 commit_id=commit_ids['ancestor-new'])
1300 commit_id=commit_ids['ancestor-new'])
1297 assert pull_request.target_ref == expected_target_ref
1301 assert pull_request.target_ref == expected_target_ref
1298
1302
1299 def test_remove_pull_request_branch(self, backend_git, csrf_token):
1303 def test_remove_pull_request_branch(self, backend_git, csrf_token):
1300 branch_name = 'development'
1304 branch_name = 'development'
1301 commits = [
1305 commits = [
1302 {'message': 'initial-commit'},
1306 {'message': 'initial-commit'},
1303 {'message': 'old-feature'},
1307 {'message': 'old-feature'},
1304 {'message': 'new-feature', 'branch': branch_name},
1308 {'message': 'new-feature', 'branch': branch_name},
1305 ]
1309 ]
1306 repo = backend_git.create_repo(commits)
1310 repo = backend_git.create_repo(commits)
1307 repo_name = repo.repo_name
1311 repo_name = repo.repo_name
1308 commit_ids = backend_git.commit_ids
1312 commit_ids = backend_git.commit_ids
1309
1313
1310 pull_request = PullRequest()
1314 pull_request = PullRequest()
1311 pull_request.source_repo = repo
1315 pull_request.source_repo = repo
1312 pull_request.target_repo = repo
1316 pull_request.target_repo = repo
1313 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1317 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1314 branch=branch_name, commit_id=commit_ids['new-feature'])
1318 branch=branch_name, commit_id=commit_ids['new-feature'])
1315 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1319 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1316 branch=backend_git.default_branch_name, commit_id=commit_ids['old-feature'])
1320 branch=backend_git.default_branch_name, commit_id=commit_ids['old-feature'])
1317 pull_request.revisions = [commit_ids['new-feature']]
1321 pull_request.revisions = [commit_ids['new-feature']]
1318 pull_request.title = u"Test"
1322 pull_request.title = u"Test"
1319 pull_request.description = u"Description"
1323 pull_request.description = u"Description"
1320 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1324 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1321 pull_request.pull_request_state = PullRequest.STATE_CREATED
1325 pull_request.pull_request_state = PullRequest.STATE_CREATED
1322 Session().add(pull_request)
1326 Session().add(pull_request)
1323 Session().commit()
1327 Session().commit()
1324
1328
1325 pull_request_id = pull_request.pull_request_id
1329 pull_request_id = pull_request.pull_request_id
1326
1330
1327 vcs = repo.scm_instance()
1331 vcs = repo.scm_instance()
1328 vcs.remove_ref('refs/heads/{}'.format(branch_name))
1332 vcs.remove_ref('refs/heads/{}'.format(branch_name))
1329 # NOTE(marcink): run GC to ensure the commits are gone
1333 # NOTE(marcink): run GC to ensure the commits are gone
1330 vcs.run_gc()
1334 vcs.run_gc()
1331
1335
1332 response = self.app.get(route_path(
1336 response = self.app.get(route_path(
1333 'pullrequest_show',
1337 'pullrequest_show',
1334 repo_name=repo_name,
1338 repo_name=repo_name,
1335 pull_request_id=pull_request_id))
1339 pull_request_id=pull_request_id))
1336
1340
1337 assert response.status_int == 200
1341 assert response.status_int == 200
1338
1342
1339 response.assert_response().element_contains(
1343 response.assert_response().element_contains(
1340 '#changeset_compare_view_content .alert strong',
1344 '#changeset_compare_view_content .alert strong',
1341 'Missing commits')
1345 'Missing commits')
1342 response.assert_response().element_contains(
1346 response.assert_response().element_contains(
1343 '#changeset_compare_view_content .alert',
1347 '#changeset_compare_view_content .alert',
1344 'This pull request cannot be displayed, because one or more'
1348 'This pull request cannot be displayed, because one or more'
1345 ' commits no longer exist in the source repository.')
1349 ' commits no longer exist in the source repository.')
1346
1350
1347 def test_strip_commits_from_pull_request(
1351 def test_strip_commits_from_pull_request(
1348 self, backend, pr_util, csrf_token):
1352 self, backend, pr_util, csrf_token):
1349 commits = [
1353 commits = [
1350 {'message': 'initial-commit'},
1354 {'message': 'initial-commit'},
1351 {'message': 'old-feature'},
1355 {'message': 'old-feature'},
1352 {'message': 'new-feature', 'parents': ['initial-commit']},
1356 {'message': 'new-feature', 'parents': ['initial-commit']},
1353 ]
1357 ]
1354 pull_request = pr_util.create_pull_request(
1358 pull_request = pr_util.create_pull_request(
1355 commits, target_head='initial-commit', source_head='new-feature',
1359 commits, target_head='initial-commit', source_head='new-feature',
1356 revisions=['new-feature'])
1360 revisions=['new-feature'])
1357
1361
1358 vcs = pr_util.source_repository.scm_instance()
1362 vcs = pr_util.source_repository.scm_instance()
1359 if backend.alias == 'git':
1363 if backend.alias == 'git':
1360 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
1364 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
1361 else:
1365 else:
1362 vcs.strip(pr_util.commit_ids['new-feature'])
1366 vcs.strip(pr_util.commit_ids['new-feature'])
1363
1367
1364 response = self.app.get(route_path(
1368 response = self.app.get(route_path(
1365 'pullrequest_show',
1369 'pullrequest_show',
1366 repo_name=pr_util.target_repository.repo_name,
1370 repo_name=pr_util.target_repository.repo_name,
1367 pull_request_id=pull_request.pull_request_id))
1371 pull_request_id=pull_request.pull_request_id))
1368
1372
1369 assert response.status_int == 200
1373 assert response.status_int == 200
1370
1374
1371 response.assert_response().element_contains(
1375 response.assert_response().element_contains(
1372 '#changeset_compare_view_content .alert strong',
1376 '#changeset_compare_view_content .alert strong',
1373 'Missing commits')
1377 'Missing commits')
1374 response.assert_response().element_contains(
1378 response.assert_response().element_contains(
1375 '#changeset_compare_view_content .alert',
1379 '#changeset_compare_view_content .alert',
1376 'This pull request cannot be displayed, because one or more'
1380 'This pull request cannot be displayed, because one or more'
1377 ' commits no longer exist in the source repository.')
1381 ' commits no longer exist in the source repository.')
1378 response.assert_response().element_contains(
1382 response.assert_response().element_contains(
1379 '#update_commits',
1383 '#update_commits',
1380 'Update commits')
1384 'Update commits')
1381
1385
1382 def test_strip_commits_and_update(
1386 def test_strip_commits_and_update(
1383 self, backend, pr_util, csrf_token):
1387 self, backend, pr_util, csrf_token):
1384 commits = [
1388 commits = [
1385 {'message': 'initial-commit'},
1389 {'message': 'initial-commit'},
1386 {'message': 'old-feature'},
1390 {'message': 'old-feature'},
1387 {'message': 'new-feature', 'parents': ['old-feature']},
1391 {'message': 'new-feature', 'parents': ['old-feature']},
1388 ]
1392 ]
1389 pull_request = pr_util.create_pull_request(
1393 pull_request = pr_util.create_pull_request(
1390 commits, target_head='old-feature', source_head='new-feature',
1394 commits, target_head='old-feature', source_head='new-feature',
1391 revisions=['new-feature'], mergeable=True)
1395 revisions=['new-feature'], mergeable=True)
1396 pr_id = pull_request.pull_request_id
1397 target_repo_name = pull_request.target_repo.repo_name
1392
1398
1393 vcs = pr_util.source_repository.scm_instance()
1399 vcs = pr_util.source_repository.scm_instance()
1394 if backend.alias == 'git':
1400 if backend.alias == 'git':
1395 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
1401 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
1396 else:
1402 else:
1397 vcs.strip(pr_util.commit_ids['new-feature'])
1403 vcs.strip(pr_util.commit_ids['new-feature'])
1398
1404
1399 url = route_path('pullrequest_update',
1405 url = route_path('pullrequest_update',
1400 repo_name=pull_request.target_repo.repo_name,
1406 repo_name=target_repo_name,
1401 pull_request_id=pull_request.pull_request_id)
1407 pull_request_id=pr_id)
1402 response = self.app.post(url,
1408 response = self.app.post(url,
1403 params={'update_commits': 'true',
1409 params={'update_commits': 'true',
1404 'csrf_token': csrf_token})
1410 'csrf_token': csrf_token})
1405
1411
1406 assert response.status_int == 200
1412 assert response.status_int == 200
1407 assert response.body == '{"response": true, "redirect_url": null}'
1413 assert response.body == '{"response": true, "redirect_url": null}'
1408
1414
1409 # Make sure that after update, it won't raise 500 errors
1415 # Make sure that after update, it won't raise 500 errors
1410 response = self.app.get(route_path(
1416 response = self.app.get(route_path(
1411 'pullrequest_show',
1417 'pullrequest_show',
1412 repo_name=pr_util.target_repository.repo_name,
1418 repo_name=target_repo_name,
1413 pull_request_id=pull_request.pull_request_id))
1419 pull_request_id=pr_id))
1414
1420
1415 assert response.status_int == 200
1421 assert response.status_int == 200
1416 response.assert_response().element_contains(
1422 response.assert_response().element_contains(
1417 '#changeset_compare_view_content .alert strong',
1423 '#changeset_compare_view_content .alert strong',
1418 'Missing commits')
1424 'Missing commits')
1419
1425
1420 def test_branch_is_a_link(self, pr_util):
1426 def test_branch_is_a_link(self, pr_util):
1421 pull_request = pr_util.create_pull_request()
1427 pull_request = pr_util.create_pull_request()
1422 pull_request.source_ref = 'branch:origin:1234567890abcdef'
1428 pull_request.source_ref = 'branch:origin:1234567890abcdef'
1423 pull_request.target_ref = 'branch:target:abcdef1234567890'
1429 pull_request.target_ref = 'branch:target:abcdef1234567890'
1424 Session().add(pull_request)
1430 Session().add(pull_request)
1425 Session().commit()
1431 Session().commit()
1426
1432
1427 response = self.app.get(route_path(
1433 response = self.app.get(route_path(
1428 'pullrequest_show',
1434 'pullrequest_show',
1429 repo_name=pull_request.target_repo.scm_instance().name,
1435 repo_name=pull_request.target_repo.scm_instance().name,
1430 pull_request_id=pull_request.pull_request_id))
1436 pull_request_id=pull_request.pull_request_id))
1431 assert response.status_int == 200
1437 assert response.status_int == 200
1432
1438
1433 source = response.assert_response().get_element('.pr-source-info')
1439 source = response.assert_response().get_element('.pr-source-info')
1434 source_parent = source.getparent()
1440 source_parent = source.getparent()
1435 assert len(source_parent) == 1
1441 assert len(source_parent) == 1
1436
1442
1437 target = response.assert_response().get_element('.pr-target-info')
1443 target = response.assert_response().get_element('.pr-target-info')
1438 target_parent = target.getparent()
1444 target_parent = target.getparent()
1439 assert len(target_parent) == 1
1445 assert len(target_parent) == 1
1440
1446
1441 expected_origin_link = route_path(
1447 expected_origin_link = route_path(
1442 'repo_commits',
1448 'repo_commits',
1443 repo_name=pull_request.source_repo.scm_instance().name,
1449 repo_name=pull_request.source_repo.scm_instance().name,
1444 params=dict(branch='origin'))
1450 params=dict(branch='origin'))
1445 expected_target_link = route_path(
1451 expected_target_link = route_path(
1446 'repo_commits',
1452 'repo_commits',
1447 repo_name=pull_request.target_repo.scm_instance().name,
1453 repo_name=pull_request.target_repo.scm_instance().name,
1448 params=dict(branch='target'))
1454 params=dict(branch='target'))
1449 assert source_parent.attrib['href'] == expected_origin_link
1455 assert source_parent.attrib['href'] == expected_origin_link
1450 assert target_parent.attrib['href'] == expected_target_link
1456 assert target_parent.attrib['href'] == expected_target_link
1451
1457
1452 def test_bookmark_is_not_a_link(self, pr_util):
1458 def test_bookmark_is_not_a_link(self, pr_util):
1453 pull_request = pr_util.create_pull_request()
1459 pull_request = pr_util.create_pull_request()
1454 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1460 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1455 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1461 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1456 Session().add(pull_request)
1462 Session().add(pull_request)
1457 Session().commit()
1463 Session().commit()
1458
1464
1459 response = self.app.get(route_path(
1465 response = self.app.get(route_path(
1460 'pullrequest_show',
1466 'pullrequest_show',
1461 repo_name=pull_request.target_repo.scm_instance().name,
1467 repo_name=pull_request.target_repo.scm_instance().name,
1462 pull_request_id=pull_request.pull_request_id))
1468 pull_request_id=pull_request.pull_request_id))
1463 assert response.status_int == 200
1469 assert response.status_int == 200
1464
1470
1465 source = response.assert_response().get_element('.pr-source-info')
1471 source = response.assert_response().get_element('.pr-source-info')
1466 assert source.text.strip() == 'bookmark:origin'
1472 assert source.text.strip() == 'bookmark:origin'
1467 assert source.getparent().attrib.get('href') is None
1473 assert source.getparent().attrib.get('href') is None
1468
1474
1469 target = response.assert_response().get_element('.pr-target-info')
1475 target = response.assert_response().get_element('.pr-target-info')
1470 assert target.text.strip() == 'bookmark:target'
1476 assert target.text.strip() == 'bookmark:target'
1471 assert target.getparent().attrib.get('href') is None
1477 assert target.getparent().attrib.get('href') is None
1472
1478
1473 def test_tag_is_not_a_link(self, pr_util):
1479 def test_tag_is_not_a_link(self, pr_util):
1474 pull_request = pr_util.create_pull_request()
1480 pull_request = pr_util.create_pull_request()
1475 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1481 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1476 pull_request.target_ref = 'tag:target:abcdef1234567890'
1482 pull_request.target_ref = 'tag:target:abcdef1234567890'
1477 Session().add(pull_request)
1483 Session().add(pull_request)
1478 Session().commit()
1484 Session().commit()
1479
1485
1480 response = self.app.get(route_path(
1486 response = self.app.get(route_path(
1481 'pullrequest_show',
1487 'pullrequest_show',
1482 repo_name=pull_request.target_repo.scm_instance().name,
1488 repo_name=pull_request.target_repo.scm_instance().name,
1483 pull_request_id=pull_request.pull_request_id))
1489 pull_request_id=pull_request.pull_request_id))
1484 assert response.status_int == 200
1490 assert response.status_int == 200
1485
1491
1486 source = response.assert_response().get_element('.pr-source-info')
1492 source = response.assert_response().get_element('.pr-source-info')
1487 assert source.text.strip() == 'tag:origin'
1493 assert source.text.strip() == 'tag:origin'
1488 assert source.getparent().attrib.get('href') is None
1494 assert source.getparent().attrib.get('href') is None
1489
1495
1490 target = response.assert_response().get_element('.pr-target-info')
1496 target = response.assert_response().get_element('.pr-target-info')
1491 assert target.text.strip() == 'tag:target'
1497 assert target.text.strip() == 'tag:target'
1492 assert target.getparent().attrib.get('href') is None
1498 assert target.getparent().attrib.get('href') is None
1493
1499
1494 @pytest.mark.parametrize('mergeable', [True, False])
1500 @pytest.mark.parametrize('mergeable', [True, False])
1495 def test_shadow_repository_link(
1501 def test_shadow_repository_link(
1496 self, mergeable, pr_util, http_host_only_stub):
1502 self, mergeable, pr_util, http_host_only_stub):
1497 """
1503 """
1498 Check that the pull request summary page displays a link to the shadow
1504 Check that the pull request summary page displays a link to the shadow
1499 repository if the pull request is mergeable. If it is not mergeable
1505 repository if the pull request is mergeable. If it is not mergeable
1500 the link should not be displayed.
1506 the link should not be displayed.
1501 """
1507 """
1502 pull_request = pr_util.create_pull_request(
1508 pull_request = pr_util.create_pull_request(
1503 mergeable=mergeable, enable_notifications=False)
1509 mergeable=mergeable, enable_notifications=False)
1504 target_repo = pull_request.target_repo.scm_instance()
1510 target_repo = pull_request.target_repo.scm_instance()
1505 pr_id = pull_request.pull_request_id
1511 pr_id = pull_request.pull_request_id
1506 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1512 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1507 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1513 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1508
1514
1509 response = self.app.get(route_path(
1515 response = self.app.get(route_path(
1510 'pullrequest_show',
1516 'pullrequest_show',
1511 repo_name=target_repo.name,
1517 repo_name=target_repo.name,
1512 pull_request_id=pr_id))
1518 pull_request_id=pr_id))
1513
1519
1514 if mergeable:
1520 if mergeable:
1515 response.assert_response().element_value_contains(
1521 response.assert_response().element_value_contains(
1516 'input.pr-mergeinfo', shadow_url)
1522 'input.pr-mergeinfo', shadow_url)
1517 response.assert_response().element_value_contains(
1523 response.assert_response().element_value_contains(
1518 'input.pr-mergeinfo ', 'pr-merge')
1524 'input.pr-mergeinfo ', 'pr-merge')
1519 else:
1525 else:
1520 response.assert_response().no_element_exists('.pr-mergeinfo')
1526 response.assert_response().no_element_exists('.pr-mergeinfo')
1521
1527
1522
1528
1523 @pytest.mark.usefixtures('app')
1529 @pytest.mark.usefixtures('app')
1524 @pytest.mark.backends("git", "hg")
1530 @pytest.mark.backends("git", "hg")
1525 class TestPullrequestsControllerDelete(object):
1531 class TestPullrequestsControllerDelete(object):
1526 def test_pull_request_delete_button_permissions_admin(
1532 def test_pull_request_delete_button_permissions_admin(
1527 self, autologin_user, user_admin, pr_util):
1533 self, autologin_user, user_admin, pr_util):
1528 pull_request = pr_util.create_pull_request(
1534 pull_request = pr_util.create_pull_request(
1529 author=user_admin.username, enable_notifications=False)
1535 author=user_admin.username, enable_notifications=False)
1530
1536
1531 response = self.app.get(route_path(
1537 response = self.app.get(route_path(
1532 'pullrequest_show',
1538 'pullrequest_show',
1533 repo_name=pull_request.target_repo.scm_instance().name,
1539 repo_name=pull_request.target_repo.scm_instance().name,
1534 pull_request_id=pull_request.pull_request_id))
1540 pull_request_id=pull_request.pull_request_id))
1535
1541
1536 response.mustcontain('id="delete_pullrequest"')
1542 response.mustcontain('id="delete_pullrequest"')
1537 response.mustcontain('Confirm to delete this pull request')
1543 response.mustcontain('Confirm to delete this pull request')
1538
1544
1539 def test_pull_request_delete_button_permissions_owner(
1545 def test_pull_request_delete_button_permissions_owner(
1540 self, autologin_regular_user, user_regular, pr_util):
1546 self, autologin_regular_user, user_regular, pr_util):
1541 pull_request = pr_util.create_pull_request(
1547 pull_request = pr_util.create_pull_request(
1542 author=user_regular.username, enable_notifications=False)
1548 author=user_regular.username, enable_notifications=False)
1543
1549
1544 response = self.app.get(route_path(
1550 response = self.app.get(route_path(
1545 'pullrequest_show',
1551 'pullrequest_show',
1546 repo_name=pull_request.target_repo.scm_instance().name,
1552 repo_name=pull_request.target_repo.scm_instance().name,
1547 pull_request_id=pull_request.pull_request_id))
1553 pull_request_id=pull_request.pull_request_id))
1548
1554
1549 response.mustcontain('id="delete_pullrequest"')
1555 response.mustcontain('id="delete_pullrequest"')
1550 response.mustcontain('Confirm to delete this pull request')
1556 response.mustcontain('Confirm to delete this pull request')
1551
1557
1552 def test_pull_request_delete_button_permissions_forbidden(
1558 def test_pull_request_delete_button_permissions_forbidden(
1553 self, autologin_regular_user, user_regular, user_admin, pr_util):
1559 self, autologin_regular_user, user_regular, user_admin, pr_util):
1554 pull_request = pr_util.create_pull_request(
1560 pull_request = pr_util.create_pull_request(
1555 author=user_admin.username, enable_notifications=False)
1561 author=user_admin.username, enable_notifications=False)
1556
1562
1557 response = self.app.get(route_path(
1563 response = self.app.get(route_path(
1558 'pullrequest_show',
1564 'pullrequest_show',
1559 repo_name=pull_request.target_repo.scm_instance().name,
1565 repo_name=pull_request.target_repo.scm_instance().name,
1560 pull_request_id=pull_request.pull_request_id))
1566 pull_request_id=pull_request.pull_request_id))
1561 response.mustcontain(no=['id="delete_pullrequest"'])
1567 response.mustcontain(no=['id="delete_pullrequest"'])
1562 response.mustcontain(no=['Confirm to delete this pull request'])
1568 response.mustcontain(no=['Confirm to delete this pull request'])
1563
1569
1564 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1570 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1565 self, autologin_regular_user, user_regular, user_admin, pr_util,
1571 self, autologin_regular_user, user_regular, user_admin, pr_util,
1566 user_util):
1572 user_util):
1567
1573
1568 pull_request = pr_util.create_pull_request(
1574 pull_request = pr_util.create_pull_request(
1569 author=user_admin.username, enable_notifications=False)
1575 author=user_admin.username, enable_notifications=False)
1570
1576
1571 user_util.grant_user_permission_to_repo(
1577 user_util.grant_user_permission_to_repo(
1572 pull_request.target_repo, user_regular,
1578 pull_request.target_repo, user_regular,
1573 'repository.write')
1579 'repository.write')
1574
1580
1575 response = self.app.get(route_path(
1581 response = self.app.get(route_path(
1576 'pullrequest_show',
1582 'pullrequest_show',
1577 repo_name=pull_request.target_repo.scm_instance().name,
1583 repo_name=pull_request.target_repo.scm_instance().name,
1578 pull_request_id=pull_request.pull_request_id))
1584 pull_request_id=pull_request.pull_request_id))
1579
1585
1580 response.mustcontain('id="open_edit_pullrequest"')
1586 response.mustcontain('id="open_edit_pullrequest"')
1581 response.mustcontain('id="delete_pullrequest"')
1587 response.mustcontain('id="delete_pullrequest"')
1582 response.mustcontain(no=['Confirm to delete this pull request'])
1588 response.mustcontain(no=['Confirm to delete this pull request'])
1583
1589
1584 def test_delete_comment_returns_404_if_comment_does_not_exist(
1590 def test_delete_comment_returns_404_if_comment_does_not_exist(
1585 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1591 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1586
1592
1587 pull_request = pr_util.create_pull_request(
1593 pull_request = pr_util.create_pull_request(
1588 author=user_admin.username, enable_notifications=False)
1594 author=user_admin.username, enable_notifications=False)
1589
1595
1590 self.app.post(
1596 self.app.post(
1591 route_path(
1597 route_path(
1592 'pullrequest_comment_delete',
1598 'pullrequest_comment_delete',
1593 repo_name=pull_request.target_repo.scm_instance().name,
1599 repo_name=pull_request.target_repo.scm_instance().name,
1594 pull_request_id=pull_request.pull_request_id,
1600 pull_request_id=pull_request.pull_request_id,
1595 comment_id=1024404),
1601 comment_id=1024404),
1596 extra_environ=xhr_header,
1602 extra_environ=xhr_header,
1597 params={'csrf_token': csrf_token},
1603 params={'csrf_token': csrf_token},
1598 status=404
1604 status=404
1599 )
1605 )
1600
1606
1601 def test_delete_comment(
1607 def test_delete_comment(
1602 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1608 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1603
1609
1604 pull_request = pr_util.create_pull_request(
1610 pull_request = pr_util.create_pull_request(
1605 author=user_admin.username, enable_notifications=False)
1611 author=user_admin.username, enable_notifications=False)
1606 comment = pr_util.create_comment()
1612 comment = pr_util.create_comment()
1607 comment_id = comment.comment_id
1613 comment_id = comment.comment_id
1608
1614
1609 response = self.app.post(
1615 response = self.app.post(
1610 route_path(
1616 route_path(
1611 'pullrequest_comment_delete',
1617 'pullrequest_comment_delete',
1612 repo_name=pull_request.target_repo.scm_instance().name,
1618 repo_name=pull_request.target_repo.scm_instance().name,
1613 pull_request_id=pull_request.pull_request_id,
1619 pull_request_id=pull_request.pull_request_id,
1614 comment_id=comment_id),
1620 comment_id=comment_id),
1615 extra_environ=xhr_header,
1621 extra_environ=xhr_header,
1616 params={'csrf_token': csrf_token},
1622 params={'csrf_token': csrf_token},
1617 status=200
1623 status=200
1618 )
1624 )
1619 assert response.body == 'true'
1625 assert response.body == 'true'
1620
1626
1621 @pytest.mark.parametrize('url_type', [
1627 @pytest.mark.parametrize('url_type', [
1622 'pullrequest_new',
1628 'pullrequest_new',
1623 'pullrequest_create',
1629 'pullrequest_create',
1624 'pullrequest_update',
1630 'pullrequest_update',
1625 'pullrequest_merge',
1631 'pullrequest_merge',
1626 ])
1632 ])
1627 def test_pull_request_is_forbidden_on_archived_repo(
1633 def test_pull_request_is_forbidden_on_archived_repo(
1628 self, autologin_user, backend, xhr_header, user_util, url_type):
1634 self, autologin_user, backend, xhr_header, user_util, url_type):
1629
1635
1630 # create a temporary repo
1636 # create a temporary repo
1631 source = user_util.create_repo(repo_type=backend.alias)
1637 source = user_util.create_repo(repo_type=backend.alias)
1632 repo_name = source.repo_name
1638 repo_name = source.repo_name
1633 repo = Repository.get_by_repo_name(repo_name)
1639 repo = Repository.get_by_repo_name(repo_name)
1634 repo.archived = True
1640 repo.archived = True
1635 Session().commit()
1641 Session().commit()
1636
1642
1637 response = self.app.get(
1643 response = self.app.get(
1638 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302)
1644 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302)
1639
1645
1640 msg = 'Action not supported for archived repository.'
1646 msg = 'Action not supported for archived repository.'
1641 assert_session_flash(response, msg)
1647 assert_session_flash(response, msg)
1642
1648
1643
1649
1644 def assert_pull_request_status(pull_request, expected_status):
1650 def assert_pull_request_status(pull_request, expected_status):
1645 status = ChangesetStatusModel().calculated_review_status(pull_request=pull_request)
1651 status = ChangesetStatusModel().calculated_review_status(pull_request=pull_request)
1646 assert status == expected_status
1652 assert status == expected_status
1647
1653
1648
1654
1649 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1655 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1650 @pytest.mark.usefixtures("autologin_user")
1656 @pytest.mark.usefixtures("autologin_user")
1651 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1657 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1652 app.get(route_path(route, repo_name=backend_svn.repo_name), status=404)
1658 app.get(route_path(route, repo_name=backend_svn.repo_name), status=404)
General Comments 0
You need to be logged in to leave comments. Login now