##// END OF EJS Templates
chore(tests): fixed flaky test
super-admin -
r5226:587a1881 default
parent child Browse files
Show More
@@ -1,1936 +1,1937 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 import mock
18 import mock
19 import pytest
19 import pytest
20
20
21 import rhodecode
21 import rhodecode
22 from rhodecode.lib import helpers as h
22 from rhodecode.lib import helpers as h
23 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason, Reference
23 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason, Reference
24 from rhodecode.lib.vcs.nodes import FileNode
24 from rhodecode.lib.vcs.nodes import FileNode
25 from rhodecode.lib.ext_json import json
25 from rhodecode.lib.ext_json import json
26 from rhodecode.model.changeset_status import ChangesetStatusModel
26 from rhodecode.model.changeset_status import ChangesetStatusModel
27 from rhodecode.model.db import (
27 from rhodecode.model.db import (
28 PullRequest,
28 PullRequest,
29 ChangesetStatus,
29 ChangesetStatus,
30 UserLog,
30 UserLog,
31 Notification,
31 Notification,
32 ChangesetComment,
32 ChangesetComment,
33 Repository,
33 Repository,
34 )
34 )
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36 from rhodecode.model.pull_request import PullRequestModel
36 from rhodecode.model.pull_request import PullRequestModel
37 from rhodecode.model.user import UserModel
37 from rhodecode.model.user import UserModel
38 from rhodecode.model.comment import CommentsModel
38 from rhodecode.model.comment import CommentsModel
39 from rhodecode.tests import (
39 from rhodecode.tests import (
40 assert_session_flash,
40 assert_session_flash,
41 TEST_USER_ADMIN_LOGIN,
41 TEST_USER_ADMIN_LOGIN,
42 TEST_USER_REGULAR_LOGIN,
42 TEST_USER_REGULAR_LOGIN,
43 )
43 )
44 from rhodecode.tests.fixture_mods.fixture_utils import PRTestUtility
44 from rhodecode.tests.fixture_mods.fixture_utils import PRTestUtility
45 from rhodecode.tests.routes import route_path
45 from rhodecode.tests.routes import route_path
46
46
47
47
48 @pytest.mark.usefixtures("app", "autologin_user")
48 @pytest.mark.usefixtures("app", "autologin_user")
49 @pytest.mark.backends("git", "hg")
49 @pytest.mark.backends("git", "hg")
50 class TestPullrequestsView(object):
50 class TestPullrequestsView(object):
51 def test_index(self, backend):
51 def test_index(self, backend):
52 self.app.get(route_path("pullrequest_new", repo_name=backend.repo_name))
52 self.app.get(route_path("pullrequest_new", repo_name=backend.repo_name))
53
53
54 def test_option_menu_create_pull_request_exists(self, backend):
54 def test_option_menu_create_pull_request_exists(self, backend):
55 repo_name = backend.repo_name
55 repo_name = backend.repo_name
56 response = self.app.get(h.route_path("repo_summary", repo_name=repo_name))
56 response = self.app.get(h.route_path("repo_summary", repo_name=repo_name))
57
57
58 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
58 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
59 "pullrequest_new", repo_name=repo_name
59 "pullrequest_new", repo_name=repo_name
60 )
60 )
61 response.mustcontain(create_pr_link)
61 response.mustcontain(create_pr_link)
62
62
63 def test_create_pr_form_with_raw_commit_id(self, backend):
63 def test_create_pr_form_with_raw_commit_id(self, backend):
64 repo = backend.repo
64 repo = backend.repo
65
65
66 self.app.get(
66 self.app.get(
67 route_path(
67 route_path(
68 "pullrequest_new",
68 "pullrequest_new",
69 repo_name=repo.repo_name,
69 repo_name=repo.repo_name,
70 commit=repo.get_commit().raw_id,
70 commit=repo.get_commit().raw_id,
71 ),
71 ),
72 status=200,
72 status=200,
73 )
73 )
74
74
75 @pytest.mark.parametrize("pr_merge_enabled", [True, False])
75 @pytest.mark.parametrize("pr_merge_enabled", [True, False])
76 @pytest.mark.parametrize("range_diff", ["0", "1"])
76 @pytest.mark.parametrize("range_diff", ["0", "1"])
77 def test_show(self, pr_util, pr_merge_enabled, range_diff):
77 def test_show(self, pr_util, pr_merge_enabled, range_diff):
78 pull_request = pr_util.create_pull_request(
78 pull_request = pr_util.create_pull_request(
79 mergeable=pr_merge_enabled, enable_notifications=False
79 mergeable=pr_merge_enabled, enable_notifications=False
80 )
80 )
81
81
82 response = self.app.get(
82 response = self.app.get(
83 route_path(
83 route_path(
84 "pullrequest_show",
84 "pullrequest_show",
85 repo_name=pull_request.target_repo.scm_instance().name,
85 repo_name=pull_request.target_repo.scm_instance().name,
86 pull_request_id=pull_request.pull_request_id,
86 pull_request_id=pull_request.pull_request_id,
87 params={"range-diff": range_diff},
87 params={"range-diff": range_diff},
88 )
88 )
89 )
89 )
90
90
91 for commit_id in pull_request.revisions:
91 for commit_id in pull_request.revisions:
92 response.mustcontain(commit_id)
92 response.mustcontain(commit_id)
93
93
94 response.mustcontain(pull_request.target_ref_parts.type)
94 response.mustcontain(pull_request.target_ref_parts.type)
95 response.mustcontain(pull_request.target_ref_parts.name)
95 response.mustcontain(pull_request.target_ref_parts.name)
96
96
97 response.mustcontain('class="pull-request-merge"')
97 response.mustcontain('class="pull-request-merge"')
98
98
99 if pr_merge_enabled:
99 if pr_merge_enabled:
100 response.mustcontain("Pull request reviewer approval is pending")
100 response.mustcontain("Pull request reviewer approval is pending")
101 else:
101 else:
102 response.mustcontain("Server-side pull request merging is disabled.")
102 response.mustcontain("Server-side pull request merging is disabled.")
103
103
104 if range_diff == "1":
104 if range_diff == "1":
105 response.mustcontain("Turn off: Show the diff as commit range")
105 response.mustcontain("Turn off: Show the diff as commit range")
106
106
107 def test_show_versions_of_pr(self, backend, csrf_token):
107 def test_show_versions_of_pr(self, backend, csrf_token):
108 commits = [
108 commits = [
109 {
109 {
110 "message": "initial-commit",
110 "message": "initial-commit",
111 "added": [FileNode(b"test-file.txt", b"LINE1\n")],
111 "added": [FileNode(b"test-file.txt", b"LINE1\n")],
112 },
112 },
113 {
113 {
114 "message": "commit-1",
114 "message": "commit-1",
115 "changed": [FileNode(b"test-file.txt", b"LINE1\nLINE2\n")],
115 "changed": [FileNode(b"test-file.txt", b"LINE1\nLINE2\n")],
116 },
116 },
117 # Above is the initial version of PR that changes a single line
117 # Above is the initial version of PR that changes a single line
118 # from now on we'll add 3x commit adding a nother line on each step
118 # from now on we'll add 3x commit adding a nother line on each step
119 {
119 {
120 "message": "commit-2",
120 "message": "commit-2",
121 "changed": [FileNode(b"test-file.txt", b"LINE1\nLINE2\nLINE3\n")],
121 "changed": [FileNode(b"test-file.txt", b"LINE1\nLINE2\nLINE3\n")],
122 },
122 },
123 {
123 {
124 "message": "commit-3",
124 "message": "commit-3",
125 "changed": [
125 "changed": [
126 FileNode(b"test-file.txt", b"LINE1\nLINE2\nLINE3\nLINE4\n")
126 FileNode(b"test-file.txt", b"LINE1\nLINE2\nLINE3\nLINE4\n")
127 ],
127 ],
128 },
128 },
129 {
129 {
130 "message": "commit-4",
130 "message": "commit-4",
131 "changed": [
131 "changed": [
132 FileNode(b"test-file.txt", b"LINE1\nLINE2\nLINE3\nLINE4\nLINE5\n")
132 FileNode(b"test-file.txt", b"LINE1\nLINE2\nLINE3\nLINE4\nLINE5\n")
133 ],
133 ],
134 },
134 },
135 ]
135 ]
136
136
137 commit_ids = backend.create_master_repo(commits)
137 commit_ids = backend.create_master_repo(commits)
138 target = backend.create_repo(heads=["initial-commit"])
138 target = backend.create_repo(heads=["initial-commit"])
139 source = backend.create_repo(heads=["commit-1"])
139 source = backend.create_repo(heads=["commit-1"])
140 source_repo_name = source.repo_name
140 source_repo_name = source.repo_name
141 target_repo_name = target.repo_name
141 target_repo_name = target.repo_name
142
142
143 target_ref = "branch:{branch}:{commit_id}".format(
143 target_ref = "branch:{branch}:{commit_id}".format(
144 branch=backend.default_branch_name, commit_id=commit_ids["initial-commit"]
144 branch=backend.default_branch_name, commit_id=commit_ids["initial-commit"]
145 )
145 )
146 source_ref = "branch:{branch}:{commit_id}".format(
146 source_ref = "branch:{branch}:{commit_id}".format(
147 branch=backend.default_branch_name, commit_id=commit_ids["commit-1"]
147 branch=backend.default_branch_name, commit_id=commit_ids["commit-1"]
148 )
148 )
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
180
181 location = response.headers["Location"]
181 location = response.headers["Location"]
182
182
183 pull_request_id = location.rsplit("/", 1)[1]
183 pull_request_id = location.rsplit("/", 1)[1]
184 assert pull_request_id != "new"
184 assert pull_request_id != "new"
185 pull_request = PullRequest.get(int(pull_request_id))
185 pull_request = PullRequest.get(int(pull_request_id))
186
186
187 pull_request_id = pull_request.pull_request_id
187 pull_request_id = pull_request.pull_request_id
188
188
189 # Show initial version of PR
189 # Show initial version of PR
190 response = self.app.get(
190 response = self.app.get(
191 route_path(
191 route_path(
192 "pullrequest_show",
192 "pullrequest_show",
193 repo_name=target_repo_name,
193 repo_name=target_repo_name,
194 pull_request_id=pull_request_id,
194 pull_request_id=pull_request_id,
195 )
195 )
196 )
196 )
197
197
198 response.mustcontain("commit-1")
198 response.mustcontain("commit-1")
199 response.mustcontain(no=["commit-2"])
199 response.mustcontain(no=["commit-2"])
200 response.mustcontain(no=["commit-3"])
200 response.mustcontain(no=["commit-3"])
201 response.mustcontain(no=["commit-4"])
201 response.mustcontain(no=["commit-4"])
202
202
203 response.mustcontain('cb-addition"></span><span>LINE2</span>')
203 response.mustcontain('cb-addition"></span><span>LINE2</span>')
204 response.mustcontain(no=["LINE3"])
204 response.mustcontain(no=["LINE3"])
205 response.mustcontain(no=["LINE4"])
205 response.mustcontain(no=["LINE4"])
206 response.mustcontain(no=["LINE5"])
206 response.mustcontain(no=["LINE5"])
207
207
208 # update PR #1
208 # update PR #1
209 source_repo = Repository.get_by_repo_name(source_repo_name)
209 source_repo = Repository.get_by_repo_name(source_repo_name)
210 backend.pull_heads(source_repo, heads=["commit-2"])
210 backend.pull_heads(source_repo, heads=["commit-2"])
211 response = self.app.post(
211 response = self.app.post(
212 route_path(
212 route_path(
213 "pullrequest_update",
213 "pullrequest_update",
214 repo_name=target_repo_name,
214 repo_name=target_repo_name,
215 pull_request_id=pull_request_id,
215 pull_request_id=pull_request_id,
216 ),
216 ),
217 params={"update_commits": "true", "csrf_token": csrf_token},
217 params={"update_commits": "true", "csrf_token": csrf_token},
218 )
218 )
219
219
220 # update PR #2
220 # update PR #2
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-3"])
222 backend.pull_heads(source_repo, heads=["commit-3"])
223 response = self.app.post(
223 response = self.app.post(
224 route_path(
224 route_path(
225 "pullrequest_update",
225 "pullrequest_update",
226 repo_name=target_repo_name,
226 repo_name=target_repo_name,
227 pull_request_id=pull_request_id,
227 pull_request_id=pull_request_id,
228 ),
228 ),
229 params={"update_commits": "true", "csrf_token": csrf_token},
229 params={"update_commits": "true", "csrf_token": csrf_token},
230 )
230 )
231
231
232 # update PR #3
232 # update PR #3
233 source_repo = Repository.get_by_repo_name(source_repo_name)
233 source_repo = Repository.get_by_repo_name(source_repo_name)
234 backend.pull_heads(source_repo, heads=["commit-4"])
234 backend.pull_heads(source_repo, heads=["commit-4"])
235 response = self.app.post(
235 response = self.app.post(
236 route_path(
236 route_path(
237 "pullrequest_update",
237 "pullrequest_update",
238 repo_name=target_repo_name,
238 repo_name=target_repo_name,
239 pull_request_id=pull_request_id,
239 pull_request_id=pull_request_id,
240 ),
240 ),
241 params={"update_commits": "true", "csrf_token": csrf_token},
241 params={"update_commits": "true", "csrf_token": csrf_token},
242 )
242 )
243
243
244 # Show final version !
244 # Show final version !
245 response = self.app.get(
245 response = self.app.get(
246 route_path(
246 route_path(
247 "pullrequest_show",
247 "pullrequest_show",
248 repo_name=target_repo_name,
248 repo_name=target_repo_name,
249 pull_request_id=pull_request_id,
249 pull_request_id=pull_request_id,
250 )
250 )
251 )
251 )
252
252
253 # 3 updates, and the latest == 4
253 # 3 updates, and the latest == 4
254 response.mustcontain("4 versions available for this pull request")
254 response.mustcontain("4 versions available for this pull request")
255 response.mustcontain(no=["rhodecode diff rendering error"])
255 response.mustcontain(no=["rhodecode diff rendering error"])
256
256
257 # initial show must have 3 commits, and 3 adds
257 # initial show must have 3 commits, and 3 adds
258 response.mustcontain("commit-1")
258 response.mustcontain("commit-1")
259 response.mustcontain("commit-2")
259 response.mustcontain("commit-2")
260 response.mustcontain("commit-3")
260 response.mustcontain("commit-3")
261 response.mustcontain("commit-4")
261 response.mustcontain("commit-4")
262
262
263 response.mustcontain('cb-addition"></span><span>LINE2</span>')
263 response.mustcontain('cb-addition"></span><span>LINE2</span>')
264 response.mustcontain('cb-addition"></span><span>LINE3</span>')
264 response.mustcontain('cb-addition"></span><span>LINE3</span>')
265 response.mustcontain('cb-addition"></span><span>LINE4</span>')
265 response.mustcontain('cb-addition"></span><span>LINE4</span>')
266 response.mustcontain('cb-addition"></span><span>LINE5</span>')
266 response.mustcontain('cb-addition"></span><span>LINE5</span>')
267
267
268 # fetch versions
268 # fetch versions
269 pr = PullRequest.get(pull_request_id)
269 pr = PullRequest.get(pull_request_id)
270 versions = [x.pull_request_version_id for x in pr.versions.all()]
270 versions = [x.pull_request_version_id for x in pr.versions.all()]
271 assert len(versions) == 3
271 assert len(versions) == 3
272
272
273 # show v1,v2,v3,v4
273 # show v1,v2,v3,v4
274 def cb_line(text):
274 def cb_line(text):
275 return 'cb-addition"></span><span>{}</span>'.format(text)
275 return 'cb-addition"></span><span>{}</span>'.format(text)
276
276
277 def cb_context(text):
277 def cb_context(text):
278 return (
278 return (
279 '<span class="cb-code"><span class="cb-action cb-context">'
279 '<span class="cb-code"><span class="cb-action cb-context">'
280 "</span><span>{}</span></span>".format(text)
280 "</span><span>{}</span></span>".format(text)
281 )
281 )
282
282
283 commit_tests = {
283 commit_tests = {
284 # in response, not in response
284 # in response, not in response
285 1: (["commit-1"], ["commit-2", "commit-3", "commit-4"]),
285 1: (["commit-1"], ["commit-2", "commit-3", "commit-4"]),
286 2: (["commit-1", "commit-2"], ["commit-3", "commit-4"]),
286 2: (["commit-1", "commit-2"], ["commit-3", "commit-4"]),
287 3: (["commit-1", "commit-2", "commit-3"], ["commit-4"]),
287 3: (["commit-1", "commit-2", "commit-3"], ["commit-4"]),
288 4: (["commit-1", "commit-2", "commit-3", "commit-4"], []),
288 4: (["commit-1", "commit-2", "commit-3", "commit-4"], []),
289 }
289 }
290 diff_tests = {
290 diff_tests = {
291 1: (["LINE2"], ["LINE3", "LINE4", "LINE5"]),
291 1: (["LINE2"], ["LINE3", "LINE4", "LINE5"]),
292 2: (["LINE2", "LINE3"], ["LINE4", "LINE5"]),
292 2: (["LINE2", "LINE3"], ["LINE4", "LINE5"]),
293 3: (["LINE2", "LINE3", "LINE4"], ["LINE5"]),
293 3: (["LINE2", "LINE3", "LINE4"], ["LINE5"]),
294 4: (["LINE2", "LINE3", "LINE4", "LINE5"], []),
294 4: (["LINE2", "LINE3", "LINE4", "LINE5"], []),
295 }
295 }
296 for idx, ver in enumerate(versions, 1):
296 for idx, ver in enumerate(versions, 1):
297 response = self.app.get(
297 response = self.app.get(
298 route_path(
298 route_path(
299 "pullrequest_show",
299 "pullrequest_show",
300 repo_name=target_repo_name,
300 repo_name=target_repo_name,
301 pull_request_id=pull_request_id,
301 pull_request_id=pull_request_id,
302 params={"version": ver},
302 params={"version": ver},
303 )
303 )
304 )
304 )
305
305
306 response.mustcontain(no=["rhodecode diff rendering error"])
306 response.mustcontain(no=["rhodecode diff rendering error"])
307 response.mustcontain("Showing changes at v{}".format(idx))
307 response.mustcontain("Showing changes at v{}".format(idx))
308
308
309 yes, no = commit_tests[idx]
309 yes, no = commit_tests[idx]
310 for y in yes:
310 for y in yes:
311 response.mustcontain(y)
311 response.mustcontain(y)
312 for n in no:
312 for n in no:
313 response.mustcontain(no=n)
313 response.mustcontain(no=n)
314
314
315 yes, no = diff_tests[idx]
315 yes, no = diff_tests[idx]
316 for y in yes:
316 for y in yes:
317 response.mustcontain(cb_line(y))
317 response.mustcontain(cb_line(y))
318 for n in no:
318 for n in no:
319 response.mustcontain(no=n)
319 response.mustcontain(no=n)
320
320
321 # show diff between versions
321 # show diff between versions
322 diff_compare_tests = {
322 diff_compare_tests = {
323 1: (["LINE3"], ["LINE1", "LINE2"]),
323 1: (["LINE3"], ["LINE1", "LINE2"]),
324 2: (["LINE3", "LINE4"], ["LINE1", "LINE2"]),
324 2: (["LINE3", "LINE4"], ["LINE1", "LINE2"]),
325 3: (["LINE3", "LINE4", "LINE5"], ["LINE1", "LINE2"]),
325 3: (["LINE3", "LINE4", "LINE5"], ["LINE1", "LINE2"]),
326 }
326 }
327 for idx, ver in enumerate(versions, 1):
327 for idx, ver in enumerate(versions, 1):
328 adds, context = diff_compare_tests[idx]
328 adds, context = diff_compare_tests[idx]
329
329
330 to_ver = ver + 1
330 to_ver = ver + 1
331 if idx == 3:
331 if idx == 3:
332 to_ver = "latest"
332 to_ver = "latest"
333
333
334 response = self.app.get(
334 response = self.app.get(
335 route_path(
335 route_path(
336 "pullrequest_show",
336 "pullrequest_show",
337 repo_name=target_repo_name,
337 repo_name=target_repo_name,
338 pull_request_id=pull_request_id,
338 pull_request_id=pull_request_id,
339 params={"from_version": versions[0], "version": to_ver},
339 params={"from_version": versions[0], "version": to_ver},
340 )
340 )
341 )
341 )
342
342
343 response.mustcontain(no=["rhodecode diff rendering error"])
343 response.mustcontain(no=["rhodecode diff rendering error"])
344
344
345 for a in adds:
345 for a in adds:
346 response.mustcontain(cb_line(a))
346 response.mustcontain(cb_line(a))
347 for c in context:
347 for c in context:
348 response.mustcontain(cb_context(c))
348 response.mustcontain(cb_context(c))
349
349
350 # test version v2 -> v3
350 # test version v2 -> v3
351 response = self.app.get(
351 response = self.app.get(
352 route_path(
352 route_path(
353 "pullrequest_show",
353 "pullrequest_show",
354 repo_name=target_repo_name,
354 repo_name=target_repo_name,
355 pull_request_id=pull_request_id,
355 pull_request_id=pull_request_id,
356 params={"from_version": versions[1], "version": versions[2]},
356 params={"from_version": versions[1], "version": versions[2]},
357 )
357 )
358 )
358 )
359
359
360 response.mustcontain(cb_context("LINE1"))
360 response.mustcontain(cb_context("LINE1"))
361 response.mustcontain(cb_context("LINE2"))
361 response.mustcontain(cb_context("LINE2"))
362 response.mustcontain(cb_context("LINE3"))
362 response.mustcontain(cb_context("LINE3"))
363 response.mustcontain(cb_line("LINE4"))
363 response.mustcontain(cb_line("LINE4"))
364
364
365 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
365 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
366 # Logout
366 # Logout
367 response = self.app.post(
367 response = self.app.post(
368 h.route_path("logout"), params={"csrf_token": csrf_token}
368 h.route_path("logout"), params={"csrf_token": csrf_token}
369 )
369 )
370 # Login as regular user
370 # Login as regular user
371 response = self.app.post(
371 response = self.app.post(
372 h.route_path("login"),
372 h.route_path("login"),
373 {"username": TEST_USER_REGULAR_LOGIN, "password": "test12"},
373 {"username": TEST_USER_REGULAR_LOGIN, "password": "test12"},
374 )
374 )
375
375
376 pull_request = pr_util.create_pull_request(author=TEST_USER_REGULAR_LOGIN)
376 pull_request = pr_util.create_pull_request(author=TEST_USER_REGULAR_LOGIN)
377
377
378 response = self.app.get(
378 response = self.app.get(
379 route_path(
379 route_path(
380 "pullrequest_show",
380 "pullrequest_show",
381 repo_name=pull_request.target_repo.scm_instance().name,
381 repo_name=pull_request.target_repo.scm_instance().name,
382 pull_request_id=pull_request.pull_request_id,
382 pull_request_id=pull_request.pull_request_id,
383 )
383 )
384 )
384 )
385
385
386 response.mustcontain("Server-side pull request merging is disabled.")
386 response.mustcontain("Server-side pull request merging is disabled.")
387
387
388 assert_response = response.assert_response()
388 assert_response = response.assert_response()
389 # for regular user without a merge permissions, we don't see it
389 # for regular user without a merge permissions, we don't see it
390 assert_response.no_element_exists("#close-pull-request-action")
390 assert_response.no_element_exists("#close-pull-request-action")
391
391
392 user_util.grant_user_permission_to_repo(
392 user_util.grant_user_permission_to_repo(
393 pull_request.target_repo,
393 pull_request.target_repo,
394 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
394 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
395 "repository.write",
395 "repository.write",
396 )
396 )
397 response = self.app.get(
397 response = self.app.get(
398 route_path(
398 route_path(
399 "pullrequest_show",
399 "pullrequest_show",
400 repo_name=pull_request.target_repo.scm_instance().name,
400 repo_name=pull_request.target_repo.scm_instance().name,
401 pull_request_id=pull_request.pull_request_id,
401 pull_request_id=pull_request.pull_request_id,
402 )
402 )
403 )
403 )
404
404
405 response.mustcontain("Server-side pull request merging is disabled.")
405 response.mustcontain("Server-side pull request merging is disabled.")
406
406
407 assert_response = response.assert_response()
407 assert_response = response.assert_response()
408 # now regular user has a merge permissions, we have CLOSE button
408 # now regular user has a merge permissions, we have CLOSE button
409 assert_response.one_element_exists("#close-pull-request-action")
409 assert_response.one_element_exists("#close-pull-request-action")
410
410
411 def test_show_invalid_commit_id(self, pr_util):
411 def test_show_invalid_commit_id(self, pr_util):
412 # Simulating invalid revisions which will cause a lookup error
412 # Simulating invalid revisions which will cause a lookup error
413 pull_request = pr_util.create_pull_request()
413 pull_request = pr_util.create_pull_request()
414 pull_request.revisions = ["invalid"]
414 pull_request.revisions = ["invalid"]
415 Session().add(pull_request)
415 Session().add(pull_request)
416 Session().commit()
416 Session().commit()
417
417
418 response = self.app.get(
418 response = self.app.get(
419 route_path(
419 route_path(
420 "pullrequest_show",
420 "pullrequest_show",
421 repo_name=pull_request.target_repo.scm_instance().name,
421 repo_name=pull_request.target_repo.scm_instance().name,
422 pull_request_id=pull_request.pull_request_id,
422 pull_request_id=pull_request.pull_request_id,
423 )
423 )
424 )
424 )
425
425
426 for commit_id in pull_request.revisions:
426 for commit_id in pull_request.revisions:
427 response.mustcontain(commit_id)
427 response.mustcontain(commit_id)
428
428
429 def test_show_invalid_source_reference(self, pr_util):
429 def test_show_invalid_source_reference(self, pr_util):
430 pull_request = pr_util.create_pull_request()
430 pull_request = pr_util.create_pull_request()
431 pull_request.source_ref = "branch:b:invalid"
431 pull_request.source_ref = "branch:b:invalid"
432 Session().add(pull_request)
432 Session().add(pull_request)
433 Session().commit()
433 Session().commit()
434
434
435 self.app.get(
435 self.app.get(
436 route_path(
436 route_path(
437 "pullrequest_show",
437 "pullrequest_show",
438 repo_name=pull_request.target_repo.scm_instance().name,
438 repo_name=pull_request.target_repo.scm_instance().name,
439 pull_request_id=pull_request.pull_request_id,
439 pull_request_id=pull_request.pull_request_id,
440 )
440 )
441 )
441 )
442
442
443 def test_edit_title_description(self, pr_util, csrf_token):
443 def test_edit_title_description(self, pr_util, csrf_token):
444 pull_request = pr_util.create_pull_request()
444 pull_request = pr_util.create_pull_request()
445 pull_request_id = pull_request.pull_request_id
445 pull_request_id = pull_request.pull_request_id
446
446
447 response = self.app.post(
447 response = self.app.post(
448 route_path(
448 route_path(
449 "pullrequest_update",
449 "pullrequest_update",
450 repo_name=pull_request.target_repo.repo_name,
450 repo_name=pull_request.target_repo.repo_name,
451 pull_request_id=pull_request_id,
451 pull_request_id=pull_request_id,
452 ),
452 ),
453 params={
453 params={
454 "edit_pull_request": "true",
454 "edit_pull_request": "true",
455 "title": "New title",
455 "title": "New title",
456 "description": "New description",
456 "description": "New description",
457 "csrf_token": csrf_token,
457 "csrf_token": csrf_token,
458 },
458 },
459 )
459 )
460
460
461 assert_session_flash(
461 assert_session_flash(
462 response, "Pull request title & description updated.", category="success"
462 response, "Pull request title & description updated.", category="success"
463 )
463 )
464
464
465 pull_request = PullRequest.get(pull_request_id)
465 pull_request = PullRequest.get(pull_request_id)
466 assert pull_request.title == "New title"
466 assert pull_request.title == "New title"
467 assert pull_request.description == "New description"
467 assert pull_request.description == "New description"
468
468
469 def test_edit_title_description_special(self, pr_util, csrf_token):
469 def test_edit_title_description_special(self, pr_util, csrf_token):
470 pull_request = pr_util.create_pull_request()
470 pull_request = pr_util.create_pull_request()
471 pull_request_id = pull_request.pull_request_id
471 pull_request_id = pull_request.pull_request_id
472
472
473 response = self.app.post(
473 response = self.app.post(
474 route_path(
474 route_path(
475 "pullrequest_update",
475 "pullrequest_update",
476 repo_name=pull_request.target_repo.repo_name,
476 repo_name=pull_request.target_repo.repo_name,
477 pull_request_id=pull_request_id,
477 pull_request_id=pull_request_id,
478 ),
478 ),
479 params={
479 params={
480 "edit_pull_request": "true",
480 "edit_pull_request": "true",
481 "title": "New title {} {2} {foo}",
481 "title": "New title {} {2} {foo}",
482 "description": "New description",
482 "description": "New description",
483 "csrf_token": csrf_token,
483 "csrf_token": csrf_token,
484 },
484 },
485 )
485 )
486
486
487 assert_session_flash(
487 assert_session_flash(
488 response, "Pull request title & description updated.", category="success"
488 response, "Pull request title & description updated.", category="success"
489 )
489 )
490
490
491 pull_request = PullRequest.get(pull_request_id)
491 pull_request = PullRequest.get(pull_request_id)
492 assert pull_request.title_safe == "New title {{}} {{2}} {{foo}}"
492 assert pull_request.title_safe == "New title {{}} {{2}} {{foo}}"
493
493
494 def test_edit_title_description_closed(self, pr_util, csrf_token):
494 def test_edit_title_description_closed(self, pr_util, csrf_token):
495 pull_request = pr_util.create_pull_request()
495 pull_request = pr_util.create_pull_request()
496 pull_request_id = pull_request.pull_request_id
496 pull_request_id = pull_request.pull_request_id
497 repo_name = pull_request.target_repo.repo_name
497 repo_name = pull_request.target_repo.repo_name
498 pr_util.close()
498 pr_util.close()
499
499
500 response = self.app.post(
500 response = self.app.post(
501 route_path(
501 route_path(
502 "pullrequest_update",
502 "pullrequest_update",
503 repo_name=repo_name,
503 repo_name=repo_name,
504 pull_request_id=pull_request_id,
504 pull_request_id=pull_request_id,
505 ),
505 ),
506 params={
506 params={
507 "edit_pull_request": "true",
507 "edit_pull_request": "true",
508 "title": "New title",
508 "title": "New title",
509 "description": "New description",
509 "description": "New description",
510 "csrf_token": csrf_token,
510 "csrf_token": csrf_token,
511 },
511 },
512 status=200,
512 status=200,
513 )
513 )
514 assert_session_flash(
514 assert_session_flash(
515 response, "Cannot update closed pull requests.", category="error"
515 response, "Cannot update closed pull requests.", category="error"
516 )
516 )
517
517
518 def test_update_invalid_source_reference(self, pr_util, csrf_token):
518 def test_update_invalid_source_reference(self, pr_util, csrf_token):
519 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
519 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
520
520
521 pull_request = pr_util.create_pull_request()
521 pull_request = pr_util.create_pull_request()
522 pull_request.source_ref = "branch:invalid-branch:invalid-commit-id"
522 pull_request.source_ref = "branch:invalid-branch:invalid-commit-id"
523 Session().add(pull_request)
523 Session().add(pull_request)
524 Session().commit()
524 Session().commit()
525
525
526 pull_request_id = pull_request.pull_request_id
526 pull_request_id = pull_request.pull_request_id
527
527
528 response = self.app.post(
528 response = self.app.post(
529 route_path(
529 route_path(
530 "pullrequest_update",
530 "pullrequest_update",
531 repo_name=pull_request.target_repo.repo_name,
531 repo_name=pull_request.target_repo.repo_name,
532 pull_request_id=pull_request_id,
532 pull_request_id=pull_request_id,
533 ),
533 ),
534 params={"update_commits": "true", "csrf_token": csrf_token},
534 params={"update_commits": "true", "csrf_token": csrf_token},
535 )
535 )
536
536
537 expected_msg = str(
537 expected_msg = str(
538 PullRequestModel.UPDATE_STATUS_MESSAGES[
538 PullRequestModel.UPDATE_STATUS_MESSAGES[
539 UpdateFailureReason.MISSING_SOURCE_REF
539 UpdateFailureReason.MISSING_SOURCE_REF
540 ]
540 ]
541 )
541 )
542 assert_session_flash(response, expected_msg, category="error")
542 assert_session_flash(response, expected_msg, category="error")
543
543
544 def test_missing_target_reference(self, pr_util, csrf_token):
544 def test_missing_target_reference(self, pr_util, csrf_token):
545 from rhodecode.lib.vcs.backends.base import MergeFailureReason
545 from rhodecode.lib.vcs.backends.base import MergeFailureReason
546
546
547 pull_request = pr_util.create_pull_request(approved=True, mergeable=True)
547 pull_request = pr_util.create_pull_request(approved=True, mergeable=True)
548 unicode_reference = "branch:invalid-branch:invalid-commit-id"
548 unicode_reference = "branch:invalid-branch:invalid-commit-id"
549 pull_request.target_ref = unicode_reference
549 pull_request.target_ref = unicode_reference
550 Session().add(pull_request)
550 Session().add(pull_request)
551 Session().commit()
551 Session().commit()
552
552
553 pull_request_id = pull_request.pull_request_id
553 pull_request_id = pull_request.pull_request_id
554 pull_request_url = route_path(
554 pull_request_url = route_path(
555 "pullrequest_show",
555 "pullrequest_show",
556 repo_name=pull_request.target_repo.repo_name,
556 repo_name=pull_request.target_repo.repo_name,
557 pull_request_id=pull_request_id,
557 pull_request_id=pull_request_id,
558 )
558 )
559
559
560 response = self.app.get(pull_request_url)
560 response = self.app.get(pull_request_url)
561 # target_ref_id = "invalid-branch"
561 # target_ref_id = "invalid-branch"
562
562
563 merge_resp = MergeResponse(
563 merge_resp = MergeResponse(
564 True,
564 True,
565 True,
565 True,
566 Reference("commit", "STUB_COMMIT_ID", "STUB_COMMIT_ID"),
566 Reference("commit", "STUB_COMMIT_ID", "STUB_COMMIT_ID"),
567 MergeFailureReason.MISSING_TARGET_REF,
567 MergeFailureReason.MISSING_TARGET_REF,
568 metadata={
568 metadata={
569 "target_ref": PullRequest.unicode_to_reference(unicode_reference)
569 "target_ref": PullRequest.unicode_to_reference(unicode_reference)
570 },
570 },
571 )
571 )
572 response.assert_response().element_contains(
572 response.assert_response().element_contains(
573 'div[data-role="merge-message"]', merge_resp.merge_status_message
573 'div[data-role="merge-message"]', merge_resp.merge_status_message
574 )
574 )
575
575
576 def test_comment_and_close_pull_request_custom_message_approved(
576 def test_comment_and_close_pull_request_custom_message_approved(
577 self, pr_util, csrf_token, xhr_header
577 self, pr_util, csrf_token, xhr_header
578 ):
578 ):
579 pull_request = pr_util.create_pull_request(approved=True)
579 pull_request = pr_util.create_pull_request(approved=True)
580 pull_request_id = pull_request.pull_request_id
580 pull_request_id = pull_request.pull_request_id
581 author = pull_request.user_id
581 author = pull_request.user_id
582 repo = pull_request.target_repo.repo_id
582 repo = pull_request.target_repo.repo_id
583
583
584 self.app.post(
584 self.app.post(
585 route_path(
585 route_path(
586 "pullrequest_comment_create",
586 "pullrequest_comment_create",
587 repo_name=pull_request.target_repo.scm_instance().name,
587 repo_name=pull_request.target_repo.scm_instance().name,
588 pull_request_id=pull_request_id,
588 pull_request_id=pull_request_id,
589 ),
589 ),
590 params={
590 params={
591 "close_pull_request": "1",
591 "close_pull_request": "1",
592 "text": "Closing a PR",
592 "text": "Closing a PR",
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
597
598 journal = (
598 journal = (
599 UserLog.query()
599 UserLog.query()
600 .filter(UserLog.user_id == author)
600 .filter(UserLog.user_id == author)
601 .filter(UserLog.repository_id == repo)
601 .filter(UserLog.repository_id == repo)
602 .order_by(UserLog.user_log_id.asc())
602 .order_by(UserLog.user_log_id.asc())
603 .all()
603 .all()
604 )
604 )
605 assert journal[-1].action == "repo.pull_request.close"
605 assert journal[-1].action == "repo.pull_request.close"
606
606
607 pull_request = PullRequest.get(pull_request_id)
607 pull_request = PullRequest.get(pull_request_id)
608 assert pull_request.is_closed()
608 assert pull_request.is_closed()
609
609
610 status = ChangesetStatusModel().get_status(
610 status = ChangesetStatusModel().get_status(
611 pull_request.source_repo, pull_request=pull_request
611 pull_request.source_repo, pull_request=pull_request
612 )
612 )
613 assert status == ChangesetStatus.STATUS_APPROVED
613 assert status == ChangesetStatus.STATUS_APPROVED
614 comments = (
614 comments = (
615 ChangesetComment()
615 ChangesetComment()
616 .query()
616 .query()
617 .filter(ChangesetComment.pull_request == pull_request)
617 .filter(ChangesetComment.pull_request == pull_request)
618 .order_by(ChangesetComment.comment_id.asc())
618 .order_by(ChangesetComment.comment_id.asc())
619 .all()
619 .all()
620 )
620 )
621 assert comments[-1].text == "Closing a PR"
621 assert comments[-1].text == "Closing a PR"
622
622
623 def test_comment_force_close_pull_request_rejected(
623 def test_comment_force_close_pull_request_rejected(
624 self, pr_util, csrf_token, xhr_header
624 self, pr_util, csrf_token, xhr_header
625 ):
625 ):
626 pull_request = pr_util.create_pull_request()
626 pull_request = pr_util.create_pull_request()
627 pull_request_id = pull_request.pull_request_id
627 pull_request_id = pull_request.pull_request_id
628 PullRequestModel().update_reviewers(
628 PullRequestModel().update_reviewers(
629 pull_request_id,
629 pull_request_id,
630 [
630 [
631 (1, ["reason"], False, "reviewer", []),
631 (1, ["reason"], False, "reviewer", []),
632 (2, ["reason2"], False, "reviewer", []),
632 (2, ["reason2"], False, "reviewer", []),
633 ],
633 ],
634 pull_request.author,
634 pull_request.author,
635 )
635 )
636 author = pull_request.user_id
636 author = pull_request.user_id
637 repo = pull_request.target_repo.repo_id
637 repo = pull_request.target_repo.repo_id
638
638
639 self.app.post(
639 self.app.post(
640 route_path(
640 route_path(
641 "pullrequest_comment_create",
641 "pullrequest_comment_create",
642 repo_name=pull_request.target_repo.scm_instance().name,
642 repo_name=pull_request.target_repo.scm_instance().name,
643 pull_request_id=pull_request_id,
643 pull_request_id=pull_request_id,
644 ),
644 ),
645 params={"close_pull_request": "1", "csrf_token": csrf_token},
645 params={"close_pull_request": "1", "csrf_token": csrf_token},
646 extra_environ=xhr_header,
646 extra_environ=xhr_header,
647 )
647 )
648
648
649 pull_request = PullRequest.get(pull_request_id)
649 pull_request = PullRequest.get(pull_request_id)
650
650
651 journal = (
651 journal = (
652 UserLog.query()
652 UserLog.query()
653 .filter(UserLog.user_id == author, UserLog.repository_id == repo)
653 .filter(UserLog.user_id == author, UserLog.repository_id == repo)
654 .order_by(UserLog.user_log_id.asc())
654 .order_by(UserLog.user_log_id.asc())
655 .all()
655 .all()
656 )
656 )
657 assert journal[-1].action == "repo.pull_request.close"
657 assert journal[-1].action == "repo.pull_request.close"
658
658
659 # check only the latest status, not the review status
659 # check only the latest status, not the review status
660 status = ChangesetStatusModel().get_status(
660 status = ChangesetStatusModel().get_status(
661 pull_request.source_repo, pull_request=pull_request
661 pull_request.source_repo, pull_request=pull_request
662 )
662 )
663 assert status == ChangesetStatus.STATUS_REJECTED
663 assert status == ChangesetStatus.STATUS_REJECTED
664
664
665 def test_comment_and_close_pull_request(self, pr_util, csrf_token, xhr_header):
665 def test_comment_and_close_pull_request(self, pr_util, csrf_token, xhr_header):
666 pull_request = pr_util.create_pull_request()
666 pull_request = pr_util.create_pull_request()
667 pull_request_id = pull_request.pull_request_id
667 pull_request_id = pull_request.pull_request_id
668
668
669 response = self.app.post(
669 response = self.app.post(
670 route_path(
670 route_path(
671 "pullrequest_comment_create",
671 "pullrequest_comment_create",
672 repo_name=pull_request.target_repo.scm_instance().name,
672 repo_name=pull_request.target_repo.scm_instance().name,
673 pull_request_id=pull_request.pull_request_id,
673 pull_request_id=pull_request.pull_request_id,
674 ),
674 ),
675 params={"close_pull_request": "true", "csrf_token": csrf_token},
675 params={"close_pull_request": "true", "csrf_token": csrf_token},
676 extra_environ=xhr_header,
676 extra_environ=xhr_header,
677 )
677 )
678
678
679 assert response.json
679 assert response.json
680
680
681 pull_request = PullRequest.get(pull_request_id)
681 pull_request = PullRequest.get(pull_request_id)
682 assert pull_request.is_closed()
682 assert pull_request.is_closed()
683
683
684 # check only the latest status, not the review status
684 # check only the latest status, not the review status
685 status = ChangesetStatusModel().get_status(
685 status = ChangesetStatusModel().get_status(
686 pull_request.source_repo, pull_request=pull_request
686 pull_request.source_repo, pull_request=pull_request
687 )
687 )
688 assert status == ChangesetStatus.STATUS_REJECTED
688 assert status == ChangesetStatus.STATUS_REJECTED
689
689
690 def test_comment_and_close_pull_request_try_edit_comment(
690 def test_comment_and_close_pull_request_try_edit_comment(
691 self, pr_util, csrf_token, xhr_header
691 self, pr_util, csrf_token, xhr_header
692 ):
692 ):
693 pull_request = pr_util.create_pull_request()
693 pull_request = pr_util.create_pull_request()
694 pull_request_id = pull_request.pull_request_id
694 pull_request_id = pull_request.pull_request_id
695 target_scm = pull_request.target_repo.scm_instance()
695 target_scm = pull_request.target_repo.scm_instance()
696 target_scm_name = target_scm.name
696 target_scm_name = target_scm.name
697
697
698 response = self.app.post(
698 response = self.app.post(
699 route_path(
699 route_path(
700 "pullrequest_comment_create",
700 "pullrequest_comment_create",
701 repo_name=target_scm_name,
701 repo_name=target_scm_name,
702 pull_request_id=pull_request_id,
702 pull_request_id=pull_request_id,
703 ),
703 ),
704 params={
704 params={
705 "close_pull_request": "true",
705 "close_pull_request": "true",
706 "csrf_token": csrf_token,
706 "csrf_token": csrf_token,
707 },
707 },
708 extra_environ=xhr_header,
708 extra_environ=xhr_header,
709 )
709 )
710
710
711 assert response.json
711 assert response.json
712
712
713 pull_request = PullRequest.get(pull_request_id)
713 pull_request = PullRequest.get(pull_request_id)
714 target_scm = pull_request.target_repo.scm_instance()
714 target_scm = pull_request.target_repo.scm_instance()
715 target_scm_name = target_scm.name
715 target_scm_name = target_scm.name
716 assert pull_request.is_closed()
716 assert pull_request.is_closed()
717
717
718 # check only the latest status, not the review status
718 # check only the latest status, not the review status
719 status = ChangesetStatusModel().get_status(
719 status = ChangesetStatusModel().get_status(
720 pull_request.source_repo, pull_request=pull_request
720 pull_request.source_repo, pull_request=pull_request
721 )
721 )
722 assert status == ChangesetStatus.STATUS_REJECTED
722 assert status == ChangesetStatus.STATUS_REJECTED
723
723
724 for comment_id in response.json.keys():
724 for comment_id in response.json.keys():
725 test_text = "test"
725 test_text = "test"
726 response = self.app.post(
726 response = self.app.post(
727 route_path(
727 route_path(
728 "pullrequest_comment_edit",
728 "pullrequest_comment_edit",
729 repo_name=target_scm_name,
729 repo_name=target_scm_name,
730 pull_request_id=pull_request_id,
730 pull_request_id=pull_request_id,
731 comment_id=comment_id,
731 comment_id=comment_id,
732 ),
732 ),
733 extra_environ=xhr_header,
733 extra_environ=xhr_header,
734 params={
734 params={
735 "csrf_token": csrf_token,
735 "csrf_token": csrf_token,
736 "text": test_text,
736 "text": test_text,
737 },
737 },
738 status=403,
738 status=403,
739 )
739 )
740 assert response.status_int == 403
740 assert response.status_int == 403
741
741
742 def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header):
742 def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header):
743 pull_request = pr_util.create_pull_request()
743 pull_request = pr_util.create_pull_request()
744 target_scm = pull_request.target_repo.scm_instance()
744 target_scm = pull_request.target_repo.scm_instance()
745 target_scm_name = target_scm.name
745 target_scm_name = target_scm.name
746
746
747 response = self.app.post(
747 response = self.app.post(
748 route_path(
748 route_path(
749 "pullrequest_comment_create",
749 "pullrequest_comment_create",
750 repo_name=target_scm_name,
750 repo_name=target_scm_name,
751 pull_request_id=pull_request.pull_request_id,
751 pull_request_id=pull_request.pull_request_id,
752 ),
752 ),
753 params={
753 params={
754 "csrf_token": csrf_token,
754 "csrf_token": csrf_token,
755 "text": "init",
755 "text": "init",
756 },
756 },
757 extra_environ=xhr_header,
757 extra_environ=xhr_header,
758 )
758 )
759 assert response.json
759 assert response.json
760
760
761 for comment_id in response.json.keys():
761 for comment_id in response.json.keys():
762 assert comment_id
762 assert comment_id
763 test_text = "test"
763 test_text = "test"
764 self.app.post(
764 self.app.post(
765 route_path(
765 route_path(
766 "pullrequest_comment_edit",
766 "pullrequest_comment_edit",
767 repo_name=target_scm_name,
767 repo_name=target_scm_name,
768 pull_request_id=pull_request.pull_request_id,
768 pull_request_id=pull_request.pull_request_id,
769 comment_id=comment_id,
769 comment_id=comment_id,
770 ),
770 ),
771 extra_environ=xhr_header,
771 extra_environ=xhr_header,
772 params={
772 params={
773 "csrf_token": csrf_token,
773 "csrf_token": csrf_token,
774 "text": test_text,
774 "text": test_text,
775 "version": "0",
775 "version": "0",
776 },
776 },
777 )
777 )
778 text_form_db = (
778 text_form_db = (
779 ChangesetComment.query()
779 ChangesetComment.query()
780 .filter(ChangesetComment.comment_id == comment_id)
780 .filter(ChangesetComment.comment_id == comment_id)
781 .first()
781 .first()
782 .text
782 .text
783 )
783 )
784 assert test_text == text_form_db
784 assert test_text == text_form_db
785
785
786 def test_comment_and_comment_edit_special(self, pr_util, csrf_token, xhr_header):
786 def test_comment_and_comment_edit_special(self, pr_util, csrf_token, xhr_header):
787 pull_request = pr_util.create_pull_request()
787 pull_request = pr_util.create_pull_request()
788 target_scm = pull_request.target_repo.scm_instance()
788 target_scm = pull_request.target_repo.scm_instance()
789 target_scm_name = target_scm.name
789 target_scm_name = target_scm.name
790
790
791 response = self.app.post(
791 response = self.app.post(
792 route_path(
792 route_path(
793 "pullrequest_comment_create",
793 "pullrequest_comment_create",
794 repo_name=target_scm_name,
794 repo_name=target_scm_name,
795 pull_request_id=pull_request.pull_request_id,
795 pull_request_id=pull_request.pull_request_id,
796 ),
796 ),
797 params={
797 params={
798 "csrf_token": csrf_token,
798 "csrf_token": csrf_token,
799 "text": "init",
799 "text": "init",
800 },
800 },
801 extra_environ=xhr_header,
801 extra_environ=xhr_header,
802 )
802 )
803 assert response.json
803 assert response.json
804
804
805 for comment_id in response.json.keys():
805 for comment_id in response.json.keys():
806 test_text = "init"
806 test_text = "init"
807 response = self.app.post(
807 response = self.app.post(
808 route_path(
808 route_path(
809 "pullrequest_comment_edit",
809 "pullrequest_comment_edit",
810 repo_name=target_scm_name,
810 repo_name=target_scm_name,
811 pull_request_id=pull_request.pull_request_id,
811 pull_request_id=pull_request.pull_request_id,
812 comment_id=comment_id,
812 comment_id=comment_id,
813 ),
813 ),
814 extra_environ=xhr_header,
814 extra_environ=xhr_header,
815 params={
815 params={
816 "csrf_token": csrf_token,
816 "csrf_token": csrf_token,
817 "text": test_text,
817 "text": test_text,
818 "version": "0",
818 "version": "0",
819 },
819 },
820 status=404,
820 status=404,
821 )
821 )
822 assert response.status_int == 404
822 assert response.status_int == 404
823
823
824 def test_comment_and_try_edit_already_edited(self, pr_util, csrf_token, xhr_header):
824 def test_comment_and_try_edit_already_edited(self, pr_util, csrf_token, xhr_header):
825 pull_request = pr_util.create_pull_request()
825 pull_request = pr_util.create_pull_request()
826 target_scm = pull_request.target_repo.scm_instance()
826 target_scm = pull_request.target_repo.scm_instance()
827 target_scm_name = target_scm.name
827 target_scm_name = target_scm.name
828
828
829 response = self.app.post(
829 response = self.app.post(
830 route_path(
830 route_path(
831 "pullrequest_comment_create",
831 "pullrequest_comment_create",
832 repo_name=target_scm_name,
832 repo_name=target_scm_name,
833 pull_request_id=pull_request.pull_request_id,
833 pull_request_id=pull_request.pull_request_id,
834 ),
834 ),
835 params={
835 params={
836 "csrf_token": csrf_token,
836 "csrf_token": csrf_token,
837 "text": "init",
837 "text": "init",
838 },
838 },
839 extra_environ=xhr_header,
839 extra_environ=xhr_header,
840 )
840 )
841 assert response.json
841 assert response.json
842 for comment_id in response.json.keys():
842 for comment_id in response.json.keys():
843 test_text = "test"
843 test_text = "test"
844 self.app.post(
844 self.app.post(
845 route_path(
845 route_path(
846 "pullrequest_comment_edit",
846 "pullrequest_comment_edit",
847 repo_name=target_scm_name,
847 repo_name=target_scm_name,
848 pull_request_id=pull_request.pull_request_id,
848 pull_request_id=pull_request.pull_request_id,
849 comment_id=comment_id,
849 comment_id=comment_id,
850 ),
850 ),
851 extra_environ=xhr_header,
851 extra_environ=xhr_header,
852 params={
852 params={
853 "csrf_token": csrf_token,
853 "csrf_token": csrf_token,
854 "text": test_text,
854 "text": test_text,
855 "version": "0",
855 "version": "0",
856 },
856 },
857 )
857 )
858 test_text_v2 = "test_v2"
858 test_text_v2 = "test_v2"
859 response = self.app.post(
859 response = self.app.post(
860 route_path(
860 route_path(
861 "pullrequest_comment_edit",
861 "pullrequest_comment_edit",
862 repo_name=target_scm_name,
862 repo_name=target_scm_name,
863 pull_request_id=pull_request.pull_request_id,
863 pull_request_id=pull_request.pull_request_id,
864 comment_id=comment_id,
864 comment_id=comment_id,
865 ),
865 ),
866 extra_environ=xhr_header,
866 extra_environ=xhr_header,
867 params={
867 params={
868 "csrf_token": csrf_token,
868 "csrf_token": csrf_token,
869 "text": test_text_v2,
869 "text": test_text_v2,
870 "version": "0",
870 "version": "0",
871 },
871 },
872 status=409,
872 status=409,
873 )
873 )
874 assert response.status_int == 409
874 assert response.status_int == 409
875
875
876 text_form_db = (
876 text_form_db = (
877 ChangesetComment.query()
877 ChangesetComment.query()
878 .filter(ChangesetComment.comment_id == comment_id)
878 .filter(ChangesetComment.comment_id == comment_id)
879 .first()
879 .first()
880 .text
880 .text
881 )
881 )
882
882
883 assert test_text == text_form_db
883 assert test_text == text_form_db
884 assert test_text_v2 != text_form_db
884 assert test_text_v2 != text_form_db
885
885
886 def test_comment_and_comment_edit_permissions_forbidden(
886 def test_comment_and_comment_edit_permissions_forbidden(
887 self,
887 self,
888 autologin_regular_user,
888 autologin_regular_user,
889 user_regular,
889 user_regular,
890 user_admin,
890 user_admin,
891 pr_util,
891 pr_util,
892 csrf_token,
892 csrf_token,
893 xhr_header,
893 xhr_header,
894 ):
894 ):
895 pull_request = pr_util.create_pull_request(
895 pull_request = pr_util.create_pull_request(
896 author=user_admin.username, enable_notifications=False
896 author=user_admin.username, enable_notifications=False
897 )
897 )
898 comment = CommentsModel().create(
898 comment = CommentsModel().create(
899 text="test",
899 text="test",
900 repo=pull_request.target_repo.scm_instance().name,
900 repo=pull_request.target_repo.scm_instance().name,
901 user=user_admin,
901 user=user_admin,
902 pull_request=pull_request,
902 pull_request=pull_request,
903 )
903 )
904 response = self.app.post(
904 response = self.app.post(
905 route_path(
905 route_path(
906 "pullrequest_comment_edit",
906 "pullrequest_comment_edit",
907 repo_name=pull_request.target_repo.scm_instance().name,
907 repo_name=pull_request.target_repo.scm_instance().name,
908 pull_request_id=pull_request.pull_request_id,
908 pull_request_id=pull_request.pull_request_id,
909 comment_id=comment.comment_id,
909 comment_id=comment.comment_id,
910 ),
910 ),
911 extra_environ=xhr_header,
911 extra_environ=xhr_header,
912 params={
912 params={
913 "csrf_token": csrf_token,
913 "csrf_token": csrf_token,
914 "text": "test_text",
914 "text": "test_text",
915 },
915 },
916 status=403,
916 status=403,
917 )
917 )
918 assert response.status_int == 403
918 assert response.status_int == 403
919
919
920 def test_create_pull_request(self, backend, csrf_token):
920 def test_create_pull_request(self, backend, csrf_token):
921 commits = [
921 commits = [
922 {"message": "ancestor"},
922 {"message": "ancestor"},
923 {"message": "change"},
923 {"message": "change"},
924 {"message": "change2"},
924 {"message": "change2"},
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"])
927 target = backend.create_repo(heads=["ancestor"])
928 source = backend.create_repo(heads=["change2"])
928 source = backend.create_repo(heads=["change2"])
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["change2"]),
934 ("source_ref", "branch:default:" + commit_ids["change2"]),
935 ("target_repo", target.repo_name),
935 ("target_repo", target.repo_name),
936 ("target_ref", "branch:default:" + commit_ids["ancestor"]),
936 ("target_ref", "branch:default:" + commit_ids["ancestor"]),
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 ("revisions", commit_ids["change2"]),
954 ("revisions", commit_ids["change2"]),
955 ("__end__", "revisions:sequence"),
955 ("__end__", "revisions:sequence"),
956 ("user", ""),
956 ("user", ""),
957 ("csrf_token", csrf_token),
957 ("csrf_token", csrf_token),
958 ],
958 ],
959 status=302,
959 status=302,
960 )
960 )
961
961
962 location = response.headers["Location"]
962 location = response.headers["Location"]
963 pull_request_id = location.rsplit("/", 1)[1]
963 pull_request_id = location.rsplit("/", 1)[1]
964 assert pull_request_id != "new"
964 assert pull_request_id != "new"
965 pull_request = PullRequest.get(int(pull_request_id))
965 pull_request = PullRequest.get(int(pull_request_id))
966
966
967 # check that we have now both revisions
967 # check that we have now both revisions
968 assert pull_request.revisions == [commit_ids["change2"], commit_ids["change"]]
968 assert pull_request.revisions == [commit_ids["change2"], commit_ids["change"]]
969 assert pull_request.source_ref == "branch:default:" + commit_ids["change2"]
969 assert pull_request.source_ref == "branch:default:" + commit_ids["change2"]
970 expected_target_ref = "branch:default:" + commit_ids["ancestor"]
970 expected_target_ref = "branch:default:" + commit_ids["ancestor"]
971 assert pull_request.target_ref == expected_target_ref
971 assert pull_request.target_ref == expected_target_ref
972
972
973 def test_reviewer_notifications(self, backend, csrf_token):
973 def test_reviewer_notifications(self, backend, csrf_token):
974 # We have to use the app.post for this test, so it will create the
974 # We have to use the app.post for this test, so it will create the
975 # notifications properly with the new PR
975 # notifications properly with the new PR
976 commits = [
976 commits = [
977 {
977 {
978 "message": "ancestor",
978 "message": "ancestor",
979 "added": [FileNode(b"file_A", content=b"content_of_ancestor")],
979 "added": [FileNode(b"file_A", content=b"content_of_ancestor")],
980 },
980 },
981 {
981 {
982 "message": "change",
982 "message": "change",
983 "added": [FileNode(b"file_a", content=b"content_of_change")],
983 "added": [FileNode(b"file_a", content=b"content_of_change")],
984 },
984 },
985 {"message": "change-child"},
985 {"message": "change-child"},
986 {
986 {
987 "message": "ancestor-child",
987 "message": "ancestor-child",
988 "parents": ["ancestor"],
988 "parents": ["ancestor"],
989 "branch": "feature",
989 "branch": "feature",
990 "added": [FileNode(b"file_c", content=b"content_of_ancestor_child")],
990 "added": [FileNode(b"file_c", content=b"content_of_ancestor_child")],
991 },
991 },
992 {"message": "ancestor-child-2", "branch": "feature"},
992 {"message": "ancestor-child-2", "branch": "feature"},
993 ]
993 ]
994 commit_ids = backend.create_master_repo(commits)
994 commit_ids = backend.create_master_repo(commits)
995 target = backend.create_repo(heads=["ancestor-child"])
995 target = backend.create_repo(heads=["ancestor-child"])
996 source = backend.create_repo(heads=["change"])
996 source = backend.create_repo(heads=["change"])
997
997
998 response = self.app.post(
998 response = self.app.post(
999 route_path("pullrequest_create", repo_name=source.repo_name),
999 route_path("pullrequest_create", repo_name=source.repo_name),
1000 [
1000 [
1001 ("source_repo", source.repo_name),
1001 ("source_repo", source.repo_name),
1002 ("source_ref", "branch:default:" + commit_ids["change"]),
1002 ("source_ref", "branch:default:" + commit_ids["change"]),
1003 ("target_repo", target.repo_name),
1003 ("target_repo", target.repo_name),
1004 ("target_ref", "branch:default:" + commit_ids["ancestor-child"]),
1004 ("target_ref", "branch:default:" + commit_ids["ancestor-child"]),
1005 ("common_ancestor", commit_ids["ancestor"]),
1005 ("common_ancestor", commit_ids["ancestor"]),
1006 ("pullrequest_title", "Title"),
1006 ("pullrequest_title", "Title"),
1007 ("pullrequest_desc", "Description"),
1007 ("pullrequest_desc", "Description"),
1008 ("description_renderer", "markdown"),
1008 ("description_renderer", "markdown"),
1009 ("__start__", "review_members:sequence"),
1009 ("__start__", "review_members:sequence"),
1010 ("__start__", "reviewer:mapping"),
1010 ("__start__", "reviewer:mapping"),
1011 ("user_id", "2"),
1011 ("user_id", "2"),
1012 ("__start__", "reasons:sequence"),
1012 ("__start__", "reasons:sequence"),
1013 ("reason", "Some reason"),
1013 ("reason", "Some reason"),
1014 ("__end__", "reasons:sequence"),
1014 ("__end__", "reasons:sequence"),
1015 ("__start__", "rules:sequence"),
1015 ("__start__", "rules:sequence"),
1016 ("__end__", "rules:sequence"),
1016 ("__end__", "rules:sequence"),
1017 ("mandatory", "False"),
1017 ("mandatory", "False"),
1018 ("__end__", "reviewer:mapping"),
1018 ("__end__", "reviewer:mapping"),
1019 ("__end__", "review_members:sequence"),
1019 ("__end__", "review_members:sequence"),
1020 ("__start__", "revisions:sequence"),
1020 ("__start__", "revisions:sequence"),
1021 ("revisions", commit_ids["change"]),
1021 ("revisions", commit_ids["change"]),
1022 ("__end__", "revisions:sequence"),
1022 ("__end__", "revisions:sequence"),
1023 ("user", ""),
1023 ("user", ""),
1024 ("csrf_token", csrf_token),
1024 ("csrf_token", csrf_token),
1025 ],
1025 ],
1026 status=302,
1026 status=302,
1027 )
1027 )
1028
1028
1029 location = response.headers["Location"]
1029 location = response.headers["Location"]
1030
1030
1031 pull_request_id = location.rsplit("/", 1)[1]
1031 pull_request_id = location.rsplit("/", 1)[1]
1032 assert pull_request_id != "new"
1032 assert pull_request_id != "new"
1033 pull_request = PullRequest.get(int(pull_request_id))
1033 pull_request = PullRequest.get(int(pull_request_id))
1034
1034
1035 # Check that a notification was made
1035 # Check that a notification was made
1036 notifications = Notification.query().filter(
1036 notifications = Notification.query().filter(
1037 Notification.created_by == pull_request.author.user_id,
1037 Notification.created_by == pull_request.author.user_id,
1038 Notification.type_ == Notification.TYPE_PULL_REQUEST,
1038 Notification.type_ == Notification.TYPE_PULL_REQUEST,
1039 Notification.subject.contains(
1039 Notification.subject.contains(
1040 "requested a pull request review. !%s" % pull_request_id
1040 "requested a pull request review. !%s" % pull_request_id
1041 ),
1041 ),
1042 )
1042 )
1043 assert len(notifications.all()) == 1
1043 assert len(notifications.all()) == 1
1044
1044
1045 # Change reviewers and check that a notification was made
1045 # Change reviewers and check that a notification was made
1046 PullRequestModel().update_reviewers(
1046 PullRequestModel().update_reviewers(
1047 pull_request.pull_request_id,
1047 pull_request.pull_request_id,
1048 [(1, [], False, "reviewer", [])],
1048 [(1, [], False, "reviewer", [])],
1049 pull_request.author,
1049 pull_request.author,
1050 )
1050 )
1051 assert len(notifications.all()) == 2
1051 assert len(notifications.all()) == 2
1052
1052
1053 @pytest.mark.xfail(reason="unable to fix this test after python3 migration")
1053 @pytest.mark.xfail(reason="unable to fix this test after python3 migration")
1054 def test_create_pull_request_stores_ancestor_commit_id(self, backend, csrf_token):
1054 def test_create_pull_request_stores_ancestor_commit_id(self, backend, csrf_token):
1055 commits = [
1055 commits = [
1056 {
1056 {
1057 "message": "ancestor",
1057 "message": "ancestor",
1058 "added": [FileNode(b"file_A", content=b"content_of_ancestor")],
1058 "added": [FileNode(b"file_A", content=b"content_of_ancestor")],
1059 },
1059 },
1060 {
1060 {
1061 "message": "change",
1061 "message": "change",
1062 "added": [FileNode(b"file_a", content=b"content_of_change")],
1062 "added": [FileNode(b"file_a", content=b"content_of_change")],
1063 },
1063 },
1064 {
1064 {
1065 "message": "change-child",
1065 "message": "change-child",
1066 "added": [FileNode(b"file_c", content=b"content_of_change_2")],
1066 "added": [FileNode(b"file_c", content=b"content_of_change_2")],
1067 },
1067 },
1068 {
1068 {
1069 "message": "ancestor-child",
1069 "message": "ancestor-child",
1070 "parents": ["ancestor"],
1070 "parents": ["ancestor"],
1071 "branch": "feature",
1071 "branch": "feature",
1072 "added": [FileNode(b"file_B", content=b"content_of_ancestor_child")],
1072 "added": [FileNode(b"file_B", content=b"content_of_ancestor_child")],
1073 },
1073 },
1074 {"message": "ancestor-child-2", "branch": "feature"},
1074 {"message": "ancestor-child-2", "branch": "feature"},
1075 ]
1075 ]
1076 commit_ids = backend.create_master_repo(commits)
1076 commit_ids = backend.create_master_repo(commits)
1077 target = backend.create_repo(heads=["ancestor-child"])
1077 target = backend.create_repo(heads=["ancestor-child"])
1078 source = backend.create_repo(heads=["change"])
1078 source = backend.create_repo(heads=["change"])
1079
1079
1080 response = self.app.post(
1080 response = self.app.post(
1081 route_path("pullrequest_create", repo_name=source.repo_name),
1081 route_path("pullrequest_create", repo_name=source.repo_name),
1082 [
1082 [
1083 ("source_repo", source.repo_name),
1083 ("source_repo", source.repo_name),
1084 ("source_ref", "branch:default:" + commit_ids["change"]),
1084 ("source_ref", "branch:default:" + commit_ids["change"]),
1085 ("target_repo", target.repo_name),
1085 ("target_repo", target.repo_name),
1086 ("target_ref", "branch:default:" + commit_ids["ancestor-child"]),
1086 ("target_ref", "branch:default:" + commit_ids["ancestor-child"]),
1087 ("common_ancestor", commit_ids["ancestor"]),
1087 ("common_ancestor", commit_ids["ancestor"]),
1088 ("pullrequest_title", "Title"),
1088 ("pullrequest_title", "Title"),
1089 ("pullrequest_desc", "Description"),
1089 ("pullrequest_desc", "Description"),
1090 ("description_renderer", "markdown"),
1090 ("description_renderer", "markdown"),
1091 ("__start__", "review_members:sequence"),
1091 ("__start__", "review_members:sequence"),
1092 ("__start__", "reviewer:mapping"),
1092 ("__start__", "reviewer:mapping"),
1093 ("user_id", "1"),
1093 ("user_id", "1"),
1094 ("__start__", "reasons:sequence"),
1094 ("__start__", "reasons:sequence"),
1095 ("reason", "Some reason"),
1095 ("reason", "Some reason"),
1096 ("__end__", "reasons:sequence"),
1096 ("__end__", "reasons:sequence"),
1097 ("__start__", "rules:sequence"),
1097 ("__start__", "rules:sequence"),
1098 ("__end__", "rules:sequence"),
1098 ("__end__", "rules:sequence"),
1099 ("mandatory", "False"),
1099 ("mandatory", "False"),
1100 ("__end__", "reviewer:mapping"),
1100 ("__end__", "reviewer:mapping"),
1101 ("__end__", "review_members:sequence"),
1101 ("__end__", "review_members:sequence"),
1102 ("__start__", "revisions:sequence"),
1102 ("__start__", "revisions:sequence"),
1103 ("revisions", commit_ids["change"]),
1103 ("revisions", commit_ids["change"]),
1104 ("__end__", "revisions:sequence"),
1104 ("__end__", "revisions:sequence"),
1105 ("user", ""),
1105 ("user", ""),
1106 ("csrf_token", csrf_token),
1106 ("csrf_token", csrf_token),
1107 ],
1107 ],
1108 status=302,
1108 status=302,
1109 )
1109 )
1110
1110
1111 location = response.headers["Location"]
1111 location = response.headers["Location"]
1112
1112
1113 pull_request_id = location.rsplit("/", 1)[1]
1113 pull_request_id = location.rsplit("/", 1)[1]
1114 assert pull_request_id != "new"
1114 assert pull_request_id != "new"
1115 pull_request = PullRequest.get(int(pull_request_id))
1115 pull_request = PullRequest.get(int(pull_request_id))
1116
1116
1117 # target_ref has to point to the ancestor's commit_id in order to
1117 # target_ref has to point to the ancestor's commit_id in order to
1118 # show the correct diff
1118 # show the correct diff
1119 expected_target_ref = "branch:default:" + commit_ids["ancestor"]
1119 expected_target_ref = "branch:default:" + commit_ids["ancestor"]
1120 assert pull_request.target_ref == expected_target_ref
1120 assert pull_request.target_ref == expected_target_ref
1121
1121
1122 # Check generated diff contents
1122 # Check generated diff contents
1123 response = response.follow()
1123 response = response.follow()
1124 response.mustcontain(no=["content_of_ancestor"])
1124 response.mustcontain(no=["content_of_ancestor"])
1125 response.mustcontain(no=["content_of_ancestor-child"])
1125 response.mustcontain(no=["content_of_ancestor-child"])
1126 response.mustcontain("content_of_change")
1126 response.mustcontain("content_of_change")
1127
1127
1128 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
1128 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
1129 # Clear any previous calls to rcextensions
1129 # Clear any previous calls to rcextensions
1130 rhodecode.EXTENSIONS.calls.clear()
1130 rhodecode.EXTENSIONS.calls.clear()
1131
1131
1132 pull_request = pr_util.create_pull_request(approved=True, mergeable=True)
1132 pull_request = pr_util.create_pull_request(approved=True, mergeable=True)
1133 pull_request_id = pull_request.pull_request_id
1133 pull_request_id = pull_request.pull_request_id
1134 repo_name = (pull_request.target_repo.scm_instance().name,)
1134 repo_name = (pull_request.target_repo.scm_instance().name,)
1135
1135
1136 url = route_path(
1136 url = route_path(
1137 "pullrequest_merge",
1137 "pullrequest_merge",
1138 repo_name=str(repo_name[0]),
1138 repo_name=str(repo_name[0]),
1139 pull_request_id=pull_request_id,
1139 pull_request_id=pull_request_id,
1140 )
1140 )
1141 response = self.app.post(url, params={"csrf_token": csrf_token}).follow()
1141 response = self.app.post(url, params={"csrf_token": csrf_token}).follow()
1142
1142
1143 pull_request = PullRequest.get(pull_request_id)
1143 pull_request = PullRequest.get(pull_request_id)
1144
1144
1145 assert response.status_int == 200
1145 assert response.status_int == 200
1146 assert pull_request.is_closed()
1146 assert pull_request.is_closed()
1147 assert_pull_request_status(pull_request, ChangesetStatus.STATUS_APPROVED)
1147 assert_pull_request_status(pull_request, ChangesetStatus.STATUS_APPROVED)
1148
1148
1149 # Check the relevant log entries were added
1149 # Check the relevant log entries were added
1150 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(3)
1150 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(3)
1151 actions = [log.action for log in user_logs]
1151 actions = [log.action for log in user_logs]
1152 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
1152 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
1153 expected_actions = [
1153 expected_actions = [
1154 "repo.pull_request.close",
1154 "repo.pull_request.close",
1155 "repo.pull_request.merge",
1155 "repo.pull_request.merge",
1156 "repo.pull_request.comment.create",
1156 "repo.pull_request.comment.create",
1157 ]
1157 ]
1158 assert actions == expected_actions
1158 assert actions == expected_actions
1159
1159
1160 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(4)
1160 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(4)
1161 actions = [log for log in user_logs]
1161 actions = [log for log in user_logs]
1162 assert actions[-1].action == "user.push"
1162 assert actions[-1].action == "user.push"
1163 assert actions[-1].action_data["commit_ids"] == pr_commit_ids
1163 assert actions[-1].action_data["commit_ids"] == pr_commit_ids
1164
1164
1165 # Check post_push rcextension was really executed
1165 # Check post_push rcextension was really executed
1166 push_calls = rhodecode.EXTENSIONS.calls["_push_hook"]
1166 push_calls = rhodecode.EXTENSIONS.calls["_push_hook"]
1167 assert len(push_calls) == 1
1167 assert len(push_calls) == 1
1168 unused_last_call_args, last_call_kwargs = push_calls[0]
1168 unused_last_call_args, last_call_kwargs = push_calls[0]
1169 assert last_call_kwargs["action"] == "push"
1169 assert last_call_kwargs["action"] == "push"
1170 assert last_call_kwargs["commit_ids"] == pr_commit_ids
1170 assert last_call_kwargs["commit_ids"] == pr_commit_ids
1171
1171
1172 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
1172 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
1173 pull_request = pr_util.create_pull_request(mergeable=False)
1173 pull_request = pr_util.create_pull_request(mergeable=False)
1174 pull_request_id = pull_request.pull_request_id
1174 pull_request_id = pull_request.pull_request_id
1175 pull_request = PullRequest.get(pull_request_id)
1175 pull_request = PullRequest.get(pull_request_id)
1176
1176
1177 response = self.app.post(
1177 response = self.app.post(
1178 route_path(
1178 route_path(
1179 "pullrequest_merge",
1179 "pullrequest_merge",
1180 repo_name=pull_request.target_repo.scm_instance().name,
1180 repo_name=pull_request.target_repo.scm_instance().name,
1181 pull_request_id=pull_request.pull_request_id,
1181 pull_request_id=pull_request.pull_request_id,
1182 ),
1182 ),
1183 params={"csrf_token": csrf_token},
1183 params={"csrf_token": csrf_token},
1184 ).follow()
1184 ).follow()
1185
1185
1186 assert response.status_int == 200
1186 assert response.status_int == 200
1187 response.mustcontain(
1187 response.mustcontain(
1188 "Merge is not currently possible because of below failed checks."
1188 "Merge is not currently possible because of below failed checks."
1189 )
1189 )
1190 response.mustcontain("Server-side pull request merging is disabled.")
1190 response.mustcontain("Server-side pull request merging is disabled.")
1191
1191
1192 @pytest.mark.skip_backends("svn")
1192 @pytest.mark.skip_backends("svn")
1193 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
1193 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
1194 pull_request = pr_util.create_pull_request(mergeable=True)
1194 pull_request = pr_util.create_pull_request(mergeable=True)
1195 pull_request_id = pull_request.pull_request_id
1195 pull_request_id = pull_request.pull_request_id
1196 repo_name = pull_request.target_repo.scm_instance().name
1196 repo_name = pull_request.target_repo.scm_instance().name
1197
1197
1198 response = self.app.post(
1198 response = self.app.post(
1199 route_path(
1199 route_path(
1200 "pullrequest_merge",
1200 "pullrequest_merge",
1201 repo_name=repo_name,
1201 repo_name=repo_name,
1202 pull_request_id=pull_request_id,
1202 pull_request_id=pull_request_id,
1203 ),
1203 ),
1204 params={"csrf_token": csrf_token},
1204 params={"csrf_token": csrf_token},
1205 ).follow()
1205 ).follow()
1206
1206
1207 assert response.status_int == 200
1207 assert response.status_int == 200
1208
1208
1209 response.mustcontain(
1209 response.mustcontain(
1210 "Merge is not currently possible because of below failed checks."
1210 "Merge is not currently possible because of below failed checks."
1211 )
1211 )
1212 response.mustcontain("Pull request reviewer approval is pending.")
1212 response.mustcontain("Pull request reviewer approval is pending.")
1213
1213
1214 def test_merge_pull_request_renders_failure_reason(
1214 def test_merge_pull_request_renders_failure_reason(
1215 self, user_regular, csrf_token, pr_util
1215 self, user_regular, csrf_token, pr_util
1216 ):
1216 ):
1217 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
1217 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
1218 pull_request_id = pull_request.pull_request_id
1218 pull_request_id = pull_request.pull_request_id
1219 repo_name = pull_request.target_repo.scm_instance().name
1219 repo_name = pull_request.target_repo.scm_instance().name
1220
1220
1221 merge_resp = MergeResponse(
1221 merge_resp = MergeResponse(
1222 True,
1222 True,
1223 False,
1223 False,
1224 Reference("commit", "STUB_COMMIT_ID", "STUB_COMMIT_ID"),
1224 Reference("commit", "STUB_COMMIT_ID", "STUB_COMMIT_ID"),
1225 MergeFailureReason.PUSH_FAILED,
1225 MergeFailureReason.PUSH_FAILED,
1226 metadata={"target": "shadow repo", "merge_commit": "xxx"},
1226 metadata={"target": "shadow repo", "merge_commit": "xxx"},
1227 )
1227 )
1228 model_patcher = mock.patch.multiple(
1228 model_patcher = mock.patch.multiple(
1229 PullRequestModel,
1229 PullRequestModel,
1230 merge_repo=mock.Mock(return_value=merge_resp),
1230 merge_repo=mock.Mock(return_value=merge_resp),
1231 merge_status=mock.Mock(return_value=(None, True, "WRONG_MESSAGE")),
1231 merge_status=mock.Mock(return_value=(None, True, "WRONG_MESSAGE")),
1232 )
1232 )
1233
1233
1234 with model_patcher:
1234 with model_patcher:
1235 response = self.app.post(
1235 response = self.app.post(
1236 route_path(
1236 route_path(
1237 "pullrequest_merge",
1237 "pullrequest_merge",
1238 repo_name=repo_name,
1238 repo_name=repo_name,
1239 pull_request_id=pull_request_id,
1239 pull_request_id=pull_request_id,
1240 ),
1240 ),
1241 params={"csrf_token": csrf_token},
1241 params={"csrf_token": csrf_token},
1242 status=302,
1242 status=302,
1243 )
1243 )
1244
1244
1245 merge_resp = MergeResponse(
1245 merge_resp = MergeResponse(
1246 True,
1246 True,
1247 True,
1247 True,
1248 Reference("commit", "STUB_COMMIT_ID", "STUB_COMMIT_ID"),
1248 Reference("commit", "STUB_COMMIT_ID", "STUB_COMMIT_ID"),
1249 MergeFailureReason.PUSH_FAILED,
1249 MergeFailureReason.PUSH_FAILED,
1250 metadata={"target": "shadow repo", "merge_commit": "xxx"},
1250 metadata={"target": "shadow repo", "merge_commit": "xxx"},
1251 )
1251 )
1252 assert_session_flash(response, merge_resp.merge_status_message)
1252 assert_session_flash(response, merge_resp.merge_status_message)
1253
1253
1254 def test_update_source_revision(self, backend, csrf_token):
1254 def test_update_source_revision(self, backend, csrf_token):
1255 commits = [
1255 commits = [
1256 {"message": "ancestor"},
1256 {"message": "ancestor"},
1257 {"message": "change"},
1257 {"message": "change"},
1258 {"message": "change-2"},
1258 {"message": "change-2"},
1259 ]
1259 ]
1260 commit_ids = backend.create_master_repo(commits)
1260 commit_ids = backend.create_master_repo(commits)
1261 target = backend.create_repo(heads=["ancestor"])
1261 target = backend.create_repo(heads=["ancestor"])
1262 source = backend.create_repo(heads=["change"])
1262 source = backend.create_repo(heads=["change"])
1263
1263
1264 # create pr from a in source to A in target
1264 # create pr from a in source to A in target
1265 pull_request = PullRequest()
1265 pull_request = PullRequest()
1266
1266
1267 pull_request.source_repo = source
1267 pull_request.source_repo = source
1268 pull_request.source_ref = "branch:{branch}:{commit_id}".format(
1268 pull_request.source_ref = "branch:{branch}:{commit_id}".format(
1269 branch=backend.default_branch_name, commit_id=commit_ids["change"]
1269 branch=backend.default_branch_name, commit_id=commit_ids["change"]
1270 )
1270 )
1271
1271
1272 pull_request.target_repo = target
1272 pull_request.target_repo = target
1273 pull_request.target_ref = "branch:{branch}:{commit_id}".format(
1273 pull_request.target_ref = "branch:{branch}:{commit_id}".format(
1274 branch=backend.default_branch_name, commit_id=commit_ids["ancestor"]
1274 branch=backend.default_branch_name, commit_id=commit_ids["ancestor"]
1275 )
1275 )
1276
1276
1277 pull_request.revisions = [commit_ids["change"]]
1277 pull_request.revisions = [commit_ids["change"]]
1278 pull_request.title = "Test"
1278 pull_request.title = "Test"
1279 pull_request.description = "Description"
1279 pull_request.description = "Description"
1280 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1280 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1281 pull_request.pull_request_state = PullRequest.STATE_CREATED
1281 pull_request.pull_request_state = PullRequest.STATE_CREATED
1282 Session().add(pull_request)
1282 Session().add(pull_request)
1283 Session().commit()
1283 Session().commit()
1284 pull_request_id = pull_request.pull_request_id
1284 pull_request_id = pull_request.pull_request_id
1285
1285
1286 # source has ancestor - change - change-2
1286 # source has ancestor - change - change-2
1287 backend.pull_heads(source, heads=["change-2"])
1287 backend.pull_heads(source, heads=["change-2"])
1288 target_repo_name = target.repo_name
1288 target_repo_name = target.repo_name
1289
1289
1290 # update PR
1290 # update PR
1291 self.app.post(
1291 self.app.post(
1292 route_path(
1292 route_path(
1293 "pullrequest_update",
1293 "pullrequest_update",
1294 repo_name=target_repo_name,
1294 repo_name=target_repo_name,
1295 pull_request_id=pull_request_id,
1295 pull_request_id=pull_request_id,
1296 ),
1296 ),
1297 params={"update_commits": "true", "csrf_token": csrf_token},
1297 params={"update_commits": "true", "csrf_token": csrf_token},
1298 )
1298 )
1299
1299
1300 response = self.app.get(
1300 response = self.app.get(
1301 route_path(
1301 route_path(
1302 "pullrequest_show",
1302 "pullrequest_show",
1303 repo_name=target_repo_name,
1303 repo_name=target_repo_name,
1304 pull_request_id=pull_request.pull_request_id,
1304 pull_request_id=pull_request.pull_request_id,
1305 )
1305 )
1306 )
1306 )
1307
1307
1308 assert response.status_int == 200
1308 assert response.status_int == 200
1309 response.mustcontain("Pull request updated to")
1309 response.mustcontain("Pull request updated to")
1310 response.mustcontain("with 1 added, 0 removed commits.")
1310 response.mustcontain("with 1 added, 0 removed commits.")
1311
1311
1312 # check that we have now both revisions
1312 # check that we have now both revisions
1313 pull_request = PullRequest.get(pull_request_id)
1313 pull_request = PullRequest.get(pull_request_id)
1314 assert pull_request.revisions == [commit_ids["change-2"], commit_ids["change"]]
1314 assert pull_request.revisions == [commit_ids["change-2"], commit_ids["change"]]
1315
1315
1316 def test_update_target_revision(self, backend, csrf_token):
1316 def test_update_target_revision(self, backend, csrf_token):
1317 """
1317 """
1318 Checks when we add more commits into a target branch, and update PR
1318 Checks when we add more commits into a target branch, and update PR
1319 """
1319 """
1320
1320
1321 commits = [
1321 commits = [
1322 {"message": "commit-a"}, # main branch (our PR target)
1322 {"message": "commit-a"}, # main branch (our PR target)
1323 {"message": "commit-b"}, # Initial source
1323 {"message": "commit-b"}, # Initial source
1324 {"message": "commit-c"},
1324 {"message": "commit-c"},
1325
1325
1326 {"message": "commit-a-prime", "branch": "feature", "parents": ["commit-a"]}, # main branch (source)
1326 {"message": "commit-a-prime", "branch": "feature", "parents": ["commit-a"]}, # main branch (source)
1327 ]
1327 ]
1328
1328
1329 commit_ids = backend.create_master_repo(commits)
1329 commit_ids = backend.create_master_repo(commits)
1330 target = backend.create_repo(heads=["commit-a"])
1330 target = backend.create_repo(heads=["commit-a"])
1331 source = backend.create_repo(heads=["commit-b"])
1331 source = backend.create_repo(heads=["commit-b"])
1332 target_repo_name = target.repo_name
1332 target_repo_name = target.repo_name
1333
1333
1334 # create pr from commit-b to commit-a
1334 # create pr from commit-b to commit-a
1335 pull_request = PullRequest()
1335 pull_request = PullRequest()
1336
1336
1337 pull_request.target_repo = target
1337 pull_request.target_repo = target
1338 pull_request.target_ref = "branch:{branch}:{commit_id}".format(
1338 pull_request.target_ref = "branch:{branch}:{commit_id}".format(
1339 branch=backend.default_branch_name, commit_id=commit_ids["commit-a"]
1339 branch=backend.default_branch_name, commit_id=commit_ids["commit-a"]
1340 )
1340 )
1341
1341
1342 pull_request.source_repo = source
1342 pull_request.source_repo = source
1343 pull_request.source_ref = "branch:{branch}:{commit_id}".format(
1343 pull_request.source_ref = "branch:{branch}:{commit_id}".format(
1344 branch=backend.default_branch_name, commit_id=commit_ids["commit-b"]
1344 branch=backend.default_branch_name, commit_id=commit_ids["commit-b"]
1345 )
1345 )
1346
1346
1347 pull_request.revisions = [commit_ids["commit-b"]]
1347 pull_request.revisions = [commit_ids["commit-b"]]
1348 pull_request.title = "Test"
1348 pull_request.title = "Test"
1349 pull_request.description = "Description"
1349 pull_request.description = "Description"
1350 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1350 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1351 pull_request.pull_request_state = PullRequest.STATE_CREATED
1351 pull_request.pull_request_state = PullRequest.STATE_CREATED
1352
1352
1353 Session().add(pull_request)
1353 Session().add(pull_request)
1354 Session().commit()
1354 Session().commit()
1355 pull_request_id = pull_request.pull_request_id
1355 pull_request_id = pull_request.pull_request_id
1356
1356
1357 # target - add one commit on top commit-a -> commit-b
1357 # target - add one commit on top commit-a -> commit-b
1358 backend.pull_heads(target, heads=["commit-b"])
1358 backend.pull_heads(target, heads=["commit-b"])
1359
1359
1360 # source has ancestor - ancestor-new - change-rebased
1360 # source has ancestor - ancestor-new - change-rebased
1361 backend.pull_heads(source, heads=["commit-c"])
1361 backend.pull_heads(source, heads=["commit-c"])
1362
1362
1363 # update PR
1363 # update PR
1364 url = route_path(
1364 url = route_path(
1365 "pullrequest_update",
1365 "pullrequest_update",
1366 repo_name=target_repo_name,
1366 repo_name=target_repo_name,
1367 pull_request_id=pull_request_id,
1367 pull_request_id=pull_request_id,
1368 )
1368 )
1369 self.app.post(
1369 self.app.post(
1370 url, params={"update_commits": "true", "csrf_token": csrf_token}, status=200
1370 url, params={"update_commits": "true", "csrf_token": csrf_token}, status=200
1371 )
1371 )
1372
1372
1373 # check that we have now both revisions
1373 # check that we have now both revisions
1374 pull_request = PullRequest.get(pull_request_id)
1374 pull_request = PullRequest.get(pull_request_id)
1375 assert pull_request.revisions == [commit_ids["commit-c"]]
1375 assert pull_request.revisions == [commit_ids["commit-c"]]
1376 assert pull_request.target_ref == "branch:{branch}:{commit_id}".format(
1376 assert pull_request.target_ref == "branch:{branch}:{commit_id}".format(
1377 branch=backend.default_branch_name, commit_id=commit_ids["commit-b"]
1377 branch=backend.default_branch_name, commit_id=commit_ids["commit-b"]
1378 )
1378 )
1379
1379
1380 response = self.app.get(
1380 response = self.app.get(
1381 route_path(
1381 route_path(
1382 "pullrequest_show",
1382 "pullrequest_show",
1383 repo_name=target_repo_name,
1383 repo_name=target_repo_name,
1384 pull_request_id=pull_request.pull_request_id,
1384 pull_request_id=pull_request.pull_request_id,
1385 )
1385 )
1386 )
1386 )
1387 assert response.status_int == 200
1387 assert response.status_int == 200
1388 response.mustcontain("Pull request updated to")
1388 response.mustcontain("Pull request updated to")
1389 response.mustcontain("with 1 added, 1 removed commits.")
1389 response.mustcontain("with 1 added, 1 removed commits.")
1390
1390
1391 def test_update_target_revision_with_removal_of_1_commit_git(
1391 def test_update_target_revision_with_removal_of_1_commit_git(
1392 self, backend_git, csrf_token
1392 self, backend_git, csrf_token
1393 ):
1393 ):
1394 backend = backend_git
1394 backend = backend_git
1395 commits = [
1395 commits = [
1396 {"message": "master-commit-1"},
1396 {"message": "master-commit-1"},
1397 {"message": "master-commit-2-change-1"},
1397 {"message": "master-commit-2-change-1"},
1398 {"message": "master-commit-3-change-2"},
1398 {"message": "master-commit-3-change-2"},
1399 {
1399 {
1400 "message": "feat-commit-1",
1400 "message": "feat-commit-1",
1401 "parents": ["master-commit-1"],
1401 "parents": ["master-commit-1"],
1402 "branch": "feature",
1402 "branch": "feature",
1403 },
1403 },
1404 {"message": "feat-commit-2", "branch": "feature"},
1404 {"message": "feat-commit-2", "branch": "feature"},
1405 ]
1405 ]
1406 commit_ids = backend.create_master_repo(commits)
1406 commit_ids = backend.create_master_repo(commits)
1407 target = backend.create_repo(heads=["master-commit-3-change-2"])
1407 target = backend.create_repo(heads=["master-commit-3-change-2"])
1408 source = backend.create_repo(heads=["feat-commit-2"])
1408 source = backend.create_repo(heads=["feat-commit-2"])
1409
1409
1410 # create pr from a in source to A in target
1410 # create pr from a in source to A in target
1411 pull_request = PullRequest()
1411 pull_request = PullRequest()
1412 pull_request.source_repo = source
1412 pull_request.source_repo = source
1413
1413
1414 pull_request.source_ref = "branch:{branch}:{commit_id}".format(
1414 pull_request.source_ref = "branch:{branch}:{commit_id}".format(
1415 branch=backend.default_branch_name,
1415 branch=backend.default_branch_name,
1416 commit_id=commit_ids["master-commit-3-change-2"],
1416 commit_id=commit_ids["master-commit-3-change-2"],
1417 )
1417 )
1418
1418
1419 pull_request.target_repo = target
1419 pull_request.target_repo = target
1420 pull_request.target_ref = "branch:{branch}:{commit_id}".format(
1420 pull_request.target_ref = "branch:{branch}:{commit_id}".format(
1421 branch=backend.default_branch_name, commit_id=commit_ids["feat-commit-2"]
1421 branch=backend.default_branch_name, commit_id=commit_ids["feat-commit-2"]
1422 )
1422 )
1423
1423
1424 pull_request.revisions = [
1424 pull_request.revisions = [
1425 commit_ids["feat-commit-1"],
1425 commit_ids["feat-commit-1"],
1426 commit_ids["feat-commit-2"],
1426 commit_ids["feat-commit-2"],
1427 ]
1427 ]
1428 pull_request.title = "Test"
1428 pull_request.title = "Test"
1429 pull_request.description = "Description"
1429 pull_request.description = "Description"
1430 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1430 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1431 pull_request.pull_request_state = PullRequest.STATE_CREATED
1431 pull_request.pull_request_state = PullRequest.STATE_CREATED
1432 Session().add(pull_request)
1432 Session().add(pull_request)
1433 Session().commit()
1433 Session().commit()
1434 pull_request_id = pull_request.pull_request_id
1434 pull_request_id = pull_request.pull_request_id
1435
1435
1436 # PR is created, now we simulate a force-push into target,
1436 # PR is created, now we simulate a force-push into target,
1437 # that drops a 2 last commits
1437 # that drops a 2 last commits
1438 vcsrepo = target.scm_instance()
1438 vcsrepo = target.scm_instance()
1439 vcsrepo.config.clear_section("hooks")
1439 vcsrepo.config.clear_section("hooks")
1440 vcsrepo.run_git_command(["reset", "--soft", "HEAD~2"])
1440 vcsrepo.run_git_command(["reset", "--soft", "HEAD~2"])
1441 target_repo_name = target.repo_name
1441 target_repo_name = target.repo_name
1442
1442
1443 # update PR
1443 # update PR
1444 url = route_path(
1444 url = route_path(
1445 "pullrequest_update",
1445 "pullrequest_update",
1446 repo_name=target_repo_name,
1446 repo_name=target_repo_name,
1447 pull_request_id=pull_request_id,
1447 pull_request_id=pull_request_id,
1448 )
1448 )
1449 self.app.post(
1449 self.app.post(
1450 url, params={"update_commits": "true", "csrf_token": csrf_token}, status=200
1450 url, params={"update_commits": "true", "csrf_token": csrf_token}, status=200
1451 )
1451 )
1452
1452
1453 response = self.app.get(
1453 response = self.app.get(
1454 route_path("pullrequest_new", repo_name=target_repo_name)
1454 route_path("pullrequest_new", repo_name=target_repo_name)
1455 )
1455 )
1456 assert response.status_int == 200
1456 assert response.status_int == 200
1457 response.mustcontain("Pull request updated to")
1457 response.mustcontain("Pull request updated to")
1458 response.mustcontain("with 0 added, 0 removed commits.")
1458 response.mustcontain("with 0 added, 0 removed commits.")
1459
1459
1460 @pytest.mark.xfail(reason="unable to fix after pygit update")
1460 def test_update_pr_ancestor_reference(self, csrf_token, pr_util: PRTestUtility):
1461 def test_update_pr_ancestor_reference(self, csrf_token, pr_util: PRTestUtility):
1461 commits = [
1462 commits = [
1462 {"message": "ancestor"},
1463 {"message": "ancestor"},
1463 {"message": "change"},
1464 {"message": "change"},
1464 {"message": "change-2"},
1465 {"message": "change-2"},
1465
1466
1466 {"message": "ancestor-new", "parents": ["ancestor"], "branch": "feature"},
1467 {"message": "ancestor-new", "parents": ["ancestor"], "branch": "feature"},
1467 {"message": "change-rebased", "branch": "feature"},
1468 {"message": "change-rebased", "branch": "feature"},
1468 ]
1469 ]
1469
1470
1470 pull_request = pr_util.create_pull_request(
1471 pull_request = pr_util.create_pull_request(
1471 commits,
1472 commits,
1472 target_head="ancestor",
1473 target_head="ancestor",
1473 source_head="change",
1474 source_head="change",
1474 revisions=["change"],
1475 revisions=["change"],
1475 )
1476 )
1476 pull_request_id = pull_request.pull_request_id
1477 pull_request_id = pull_request.pull_request_id
1477 target_repo_name = pr_util.target_repository.repo_name
1478 target_repo_name = pr_util.target_repository.repo_name
1478 commit_ids = pr_util.commit_ids
1479 commit_ids = pr_util.commit_ids
1479
1480
1480 assert pull_request.revisions == [commit_ids["change"]]
1481 assert pull_request.revisions == [commit_ids["change"]]
1481 assert list(pull_request.target_repo.scm_instance(cache=False).branches.keys()) == [pr_util.backend.default_branch_name]
1482 assert list(pull_request.target_repo.scm_instance(cache=False).branches.keys()) == [pr_util.backend.default_branch_name]
1482 assert list(pull_request.source_repo.scm_instance(cache=False).branches.keys()) == [pr_util.backend.default_branch_name]
1483 assert list(pull_request.source_repo.scm_instance(cache=False).branches.keys()) == [pr_util.backend.default_branch_name]
1483
1484
1484 branch = "feature"
1485 branch = "feature"
1485 pr_util.update_target_repository(head="ancestor-new", do_fetch=True)
1486 pr_util.update_target_repository(head="ancestor-new", do_fetch=True)
1486 pr_util.set_pr_target_ref(ref_type="branch", ref_name=branch, ref_commit_id=commit_ids["ancestor-new"])
1487 pr_util.set_pr_target_ref(ref_type="branch", ref_name=branch, ref_commit_id=commit_ids["ancestor-new"])
1487
1488
1488 pr_util.update_source_repository(head="change-rebased", do_fetch=True)
1489 pr_util.update_source_repository(head="change-rebased", do_fetch=True)
1489 pr_util.set_pr_source_ref(ref_type="branch", ref_name=branch, ref_commit_id=commit_ids["change-rebased"])
1490 pr_util.set_pr_source_ref(ref_type="branch", ref_name=branch, ref_commit_id=commit_ids["change-rebased"])
1490
1491
1491 assert list(pull_request.target_repo.scm_instance(cache=False).branches.keys()) == [pr_util.backend.default_branch_name, branch]
1492 assert list(pull_request.target_repo.scm_instance(cache=False).branches.keys()) == [pr_util.backend.default_branch_name, branch]
1492 assert list(pull_request.source_repo.scm_instance(cache=False).branches.keys()) == [pr_util.backend.default_branch_name, branch]
1493 assert list(pull_request.source_repo.scm_instance(cache=False).branches.keys()) == [pr_util.backend.default_branch_name, branch]
1493
1494
1494 Session().add(pr_util.pull_request)
1495 Session().add(pr_util.pull_request)
1495 Session().commit()
1496 Session().commit()
1496
1497
1497 self.app.post(
1498 self.app.post(
1498 route_path(
1499 route_path(
1499 "pullrequest_update",
1500 "pullrequest_update",
1500 repo_name=target_repo_name,
1501 repo_name=target_repo_name,
1501 pull_request_id=pull_request_id,
1502 pull_request_id=pull_request_id,
1502 ),
1503 ),
1503 params={"update_commits": "true", "csrf_token": csrf_token, "force_refresh": True},
1504 params={"update_commits": "true", "csrf_token": csrf_token, "force_refresh": True},
1504 status=200,
1505 status=200,
1505 )
1506 )
1506
1507
1507
1508
1508 # response = self.app.get(
1509 # response = self.app.get(
1509 # route_path(
1510 # route_path(
1510 # "pullrequest_show", repo_name=target_repo_name, pull_request_id=pull_request_id,
1511 # "pullrequest_show", repo_name=target_repo_name, pull_request_id=pull_request_id,
1511 # params={"force_refresh": True}
1512 # params={"force_refresh": True}
1512 # ),
1513 # ),
1513 # )
1514 # )
1514 #
1515 #
1515 # response.mustcontain("Pull request updated to")
1516 # response.mustcontain("Pull request updated to")
1516 # response.mustcontain("with 1 added, 0 removed commits.")
1517 # response.mustcontain("with 1 added, 0 removed commits.")
1517
1518
1518 pull_request = PullRequest.get(pull_request_id)
1519 pull_request = PullRequest.get(pull_request_id)
1519
1520
1520 assert pull_request.target_ref == "branch:{branch}:{commit_id}".format(
1521 assert pull_request.target_ref == "branch:{branch}:{commit_id}".format(
1521 branch="feature", commit_id=commit_ids["ancestor-new"])
1522 branch="feature", commit_id=commit_ids["ancestor-new"])
1522
1523
1523 assert pull_request.revisions == [commit_ids["change-rebased"]]
1524 assert pull_request.revisions == [commit_ids["change-rebased"]]
1524
1525
1525
1526
1526 def test_remove_pull_request_branch(self, backend_git, csrf_token):
1527 def test_remove_pull_request_branch(self, backend_git, csrf_token):
1527 branch_name = "development"
1528 branch_name = "development"
1528 commits = [
1529 commits = [
1529 {"message": "initial-commit"},
1530 {"message": "initial-commit"},
1530 {"message": "old-feature"},
1531 {"message": "old-feature"},
1531 {"message": "new-feature", "branch": branch_name},
1532 {"message": "new-feature", "branch": branch_name},
1532 ]
1533 ]
1533 repo = backend_git.create_repo(commits)
1534 repo = backend_git.create_repo(commits)
1534 repo_name = repo.repo_name
1535 repo_name = repo.repo_name
1535 commit_ids = backend_git.commit_ids
1536 commit_ids = backend_git.commit_ids
1536
1537
1537 pull_request = PullRequest()
1538 pull_request = PullRequest()
1538 pull_request.source_repo = repo
1539 pull_request.source_repo = repo
1539 pull_request.target_repo = repo
1540 pull_request.target_repo = repo
1540 pull_request.source_ref = "branch:{branch}:{commit_id}".format(
1541 pull_request.source_ref = "branch:{branch}:{commit_id}".format(
1541 branch=branch_name, commit_id=commit_ids["new-feature"]
1542 branch=branch_name, commit_id=commit_ids["new-feature"]
1542 )
1543 )
1543 pull_request.target_ref = "branch:{branch}:{commit_id}".format(
1544 pull_request.target_ref = "branch:{branch}:{commit_id}".format(
1544 branch=backend_git.default_branch_name, commit_id=commit_ids["old-feature"]
1545 branch=backend_git.default_branch_name, commit_id=commit_ids["old-feature"]
1545 )
1546 )
1546 pull_request.revisions = [commit_ids["new-feature"]]
1547 pull_request.revisions = [commit_ids["new-feature"]]
1547 pull_request.title = "Test"
1548 pull_request.title = "Test"
1548 pull_request.description = "Description"
1549 pull_request.description = "Description"
1549 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1550 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1550 pull_request.pull_request_state = PullRequest.STATE_CREATED
1551 pull_request.pull_request_state = PullRequest.STATE_CREATED
1551 Session().add(pull_request)
1552 Session().add(pull_request)
1552 Session().commit()
1553 Session().commit()
1553
1554
1554 pull_request_id = pull_request.pull_request_id
1555 pull_request_id = pull_request.pull_request_id
1555
1556
1556 vcs = repo.scm_instance()
1557 vcs = repo.scm_instance()
1557 vcs.remove_ref("refs/heads/{}".format(branch_name))
1558 vcs.remove_ref("refs/heads/{}".format(branch_name))
1558 # NOTE(marcink): run GC to ensure the commits are gone
1559 # NOTE(marcink): run GC to ensure the commits are gone
1559 vcs.run_gc()
1560 vcs.run_gc()
1560
1561
1561 response = self.app.get(
1562 response = self.app.get(
1562 route_path(
1563 route_path(
1563 "pullrequest_show", repo_name=repo_name, pull_request_id=pull_request_id
1564 "pullrequest_show", repo_name=repo_name, pull_request_id=pull_request_id
1564 )
1565 )
1565 )
1566 )
1566
1567
1567 assert response.status_int == 200
1568 assert response.status_int == 200
1568
1569
1569 response.assert_response().element_contains(
1570 response.assert_response().element_contains(
1570 "#changeset_compare_view_content .alert strong", "Missing commits"
1571 "#changeset_compare_view_content .alert strong", "Missing commits"
1571 )
1572 )
1572 response.assert_response().element_contains(
1573 response.assert_response().element_contains(
1573 "#changeset_compare_view_content .alert",
1574 "#changeset_compare_view_content .alert",
1574 "This pull request cannot be displayed, because one or more"
1575 "This pull request cannot be displayed, because one or more"
1575 " commits no longer exist in the source repository.",
1576 " commits no longer exist in the source repository.",
1576 )
1577 )
1577
1578
1578 def test_strip_commits_from_pull_request(self, backend, pr_util):
1579 def test_strip_commits_from_pull_request(self, backend, pr_util):
1579 commits = [
1580 commits = [
1580 {"message": "initial-commit"},
1581 {"message": "initial-commit"},
1581 {"message": "old-feature"},
1582 {"message": "old-feature"},
1582 {"message": "new-feature"},
1583 {"message": "new-feature"},
1583 ]
1584 ]
1584 pull_request = pr_util.create_pull_request(
1585 pull_request = pr_util.create_pull_request(
1585 commits,
1586 commits,
1586 target_head="initial-commit",
1587 target_head="initial-commit",
1587 source_head="new-feature",
1588 source_head="new-feature",
1588 revisions=["new-feature"],
1589 revisions=["new-feature"],
1589 )
1590 )
1590
1591
1591 vcs = pr_util.source_repository.scm_instance()
1592 vcs = pr_util.source_repository.scm_instance()
1592 if backend.alias == "git":
1593 if backend.alias == "git":
1593 vcs.strip(pr_util.commit_ids["new-feature"], branch_name=pr_util.backend.default_branch_name)
1594 vcs.strip(pr_util.commit_ids["new-feature"], branch_name=pr_util.backend.default_branch_name)
1594 else:
1595 else:
1595 vcs.strip(pr_util.commit_ids["new-feature"])
1596 vcs.strip(pr_util.commit_ids["new-feature"])
1596
1597
1597 response = self.app.get(
1598 response = self.app.get(
1598 route_path(
1599 route_path(
1599 "pullrequest_show",
1600 "pullrequest_show",
1600 repo_name=pr_util.target_repository.repo_name,
1601 repo_name=pr_util.target_repository.repo_name,
1601 pull_request_id=pull_request.pull_request_id,
1602 pull_request_id=pull_request.pull_request_id,
1602 )
1603 )
1603 )
1604 )
1604
1605
1605 assert response.status_int == 200
1606 assert response.status_int == 200
1606
1607
1607 response.assert_response().element_contains(
1608 response.assert_response().element_contains(
1608 "#changeset_compare_view_content .alert strong", "Missing commits"
1609 "#changeset_compare_view_content .alert strong", "Missing commits"
1609 )
1610 )
1610 response.assert_response().element_contains(
1611 response.assert_response().element_contains(
1611 "#changeset_compare_view_content .alert",
1612 "#changeset_compare_view_content .alert",
1612 "This pull request cannot be displayed, because one or more"
1613 "This pull request cannot be displayed, because one or more"
1613 " commits no longer exist in the source repository.",
1614 " commits no longer exist in the source repository.",
1614 )
1615 )
1615 response.assert_response().element_contains("#update_commits", "Update commits")
1616 response.assert_response().element_contains("#update_commits", "Update commits")
1616
1617
1617 def test_strip_commits_and_update(self, backend, pr_util, csrf_token):
1618 def test_strip_commits_and_update(self, backend, pr_util, csrf_token):
1618 commits = [
1619 commits = [
1619 {"message": "initial-commit"},
1620 {"message": "initial-commit"},
1620 {"message": "old-feature"},
1621 {"message": "old-feature"},
1621 {"message": "new-feature", "parents": ["old-feature"]},
1622 {"message": "new-feature", "parents": ["old-feature"]},
1622 ]
1623 ]
1623 pull_request = pr_util.create_pull_request(
1624 pull_request = pr_util.create_pull_request(
1624 commits,
1625 commits,
1625 target_head="old-feature",
1626 target_head="old-feature",
1626 source_head="new-feature",
1627 source_head="new-feature",
1627 revisions=["new-feature"],
1628 revisions=["new-feature"],
1628 mergeable=True,
1629 mergeable=True,
1629 )
1630 )
1630 pr_id = pull_request.pull_request_id
1631 pr_id = pull_request.pull_request_id
1631 target_repo_name = pull_request.target_repo.repo_name
1632 target_repo_name = pull_request.target_repo.repo_name
1632
1633
1633 vcs = pr_util.source_repository.scm_instance()
1634 vcs = pr_util.source_repository.scm_instance()
1634 if backend.alias == "git":
1635 if backend.alias == "git":
1635 vcs.strip(pr_util.commit_ids["new-feature"], branch_name="master")
1636 vcs.strip(pr_util.commit_ids["new-feature"], branch_name="master")
1636 else:
1637 else:
1637 vcs.strip(pr_util.commit_ids["new-feature"])
1638 vcs.strip(pr_util.commit_ids["new-feature"])
1638
1639
1639 url = route_path(
1640 url = route_path(
1640 "pullrequest_update", repo_name=target_repo_name, pull_request_id=pr_id
1641 "pullrequest_update", repo_name=target_repo_name, pull_request_id=pr_id
1641 )
1642 )
1642 response = self.app.post(
1643 response = self.app.post(
1643 url, params={"update_commits": "true", "csrf_token": csrf_token}
1644 url, params={"update_commits": "true", "csrf_token": csrf_token}
1644 )
1645 )
1645
1646
1646 assert response.status_int == 200
1647 assert response.status_int == 200
1647 assert json.loads(response.body) == json.loads(
1648 assert json.loads(response.body) == json.loads(
1648 '{"response": true, "redirect_url": null}'
1649 '{"response": true, "redirect_url": null}'
1649 )
1650 )
1650
1651
1651 # Make sure that after update, it won't raise 500 errors
1652 # Make sure that after update, it won't raise 500 errors
1652 response = self.app.get(
1653 response = self.app.get(
1653 route_path(
1654 route_path(
1654 "pullrequest_show", repo_name=target_repo_name, pull_request_id=pr_id
1655 "pullrequest_show", repo_name=target_repo_name, pull_request_id=pr_id
1655 )
1656 )
1656 )
1657 )
1657
1658
1658 assert response.status_int == 200
1659 assert response.status_int == 200
1659 response.assert_response().element_contains(
1660 response.assert_response().element_contains(
1660 "#changeset_compare_view_content .alert strong", "Missing commits"
1661 "#changeset_compare_view_content .alert strong", "Missing commits"
1661 )
1662 )
1662
1663
1663 def test_branch_is_a_link(self, pr_util):
1664 def test_branch_is_a_link(self, pr_util):
1664 pull_request = pr_util.create_pull_request()
1665 pull_request = pr_util.create_pull_request()
1665 pull_request.source_ref = "branch:origin:1234567890abcdef"
1666 pull_request.source_ref = "branch:origin:1234567890abcdef"
1666 pull_request.target_ref = "branch:target:abcdef1234567890"
1667 pull_request.target_ref = "branch:target:abcdef1234567890"
1667 Session().add(pull_request)
1668 Session().add(pull_request)
1668 Session().commit()
1669 Session().commit()
1669
1670
1670 response = self.app.get(
1671 response = self.app.get(
1671 route_path(
1672 route_path(
1672 "pullrequest_show",
1673 "pullrequest_show",
1673 repo_name=pull_request.target_repo.scm_instance().name,
1674 repo_name=pull_request.target_repo.scm_instance().name,
1674 pull_request_id=pull_request.pull_request_id,
1675 pull_request_id=pull_request.pull_request_id,
1675 )
1676 )
1676 )
1677 )
1677 assert response.status_int == 200
1678 assert response.status_int == 200
1678
1679
1679 source = response.assert_response().get_element(".pr-source-info")
1680 source = response.assert_response().get_element(".pr-source-info")
1680 source_parent = source.getparent()
1681 source_parent = source.getparent()
1681 assert len(source_parent) == 1
1682 assert len(source_parent) == 1
1682
1683
1683 target = response.assert_response().get_element(".pr-target-info")
1684 target = response.assert_response().get_element(".pr-target-info")
1684 target_parent = target.getparent()
1685 target_parent = target.getparent()
1685 assert len(target_parent) == 1
1686 assert len(target_parent) == 1
1686
1687
1687 expected_origin_link = route_path(
1688 expected_origin_link = route_path(
1688 "repo_commits",
1689 "repo_commits",
1689 repo_name=pull_request.source_repo.scm_instance().name,
1690 repo_name=pull_request.source_repo.scm_instance().name,
1690 params=dict(branch="origin"),
1691 params=dict(branch="origin"),
1691 )
1692 )
1692 expected_target_link = route_path(
1693 expected_target_link = route_path(
1693 "repo_commits",
1694 "repo_commits",
1694 repo_name=pull_request.target_repo.scm_instance().name,
1695 repo_name=pull_request.target_repo.scm_instance().name,
1695 params=dict(branch="target"),
1696 params=dict(branch="target"),
1696 )
1697 )
1697 assert source_parent.attrib["href"] == expected_origin_link
1698 assert source_parent.attrib["href"] == expected_origin_link
1698 assert target_parent.attrib["href"] == expected_target_link
1699 assert target_parent.attrib["href"] == expected_target_link
1699
1700
1700 def test_bookmark_is_not_a_link(self, pr_util):
1701 def test_bookmark_is_not_a_link(self, pr_util):
1701 pull_request = pr_util.create_pull_request()
1702 pull_request = pr_util.create_pull_request()
1702 pull_request.source_ref = "bookmark:origin:1234567890abcdef"
1703 pull_request.source_ref = "bookmark:origin:1234567890abcdef"
1703 pull_request.target_ref = "bookmark:target:abcdef1234567890"
1704 pull_request.target_ref = "bookmark:target:abcdef1234567890"
1704 Session().add(pull_request)
1705 Session().add(pull_request)
1705 Session().commit()
1706 Session().commit()
1706
1707
1707 response = self.app.get(
1708 response = self.app.get(
1708 route_path(
1709 route_path(
1709 "pullrequest_show",
1710 "pullrequest_show",
1710 repo_name=pull_request.target_repo.scm_instance().name,
1711 repo_name=pull_request.target_repo.scm_instance().name,
1711 pull_request_id=pull_request.pull_request_id,
1712 pull_request_id=pull_request.pull_request_id,
1712 )
1713 )
1713 )
1714 )
1714 assert response.status_int == 200
1715 assert response.status_int == 200
1715
1716
1716 source = response.assert_response().get_element(".pr-source-info")
1717 source = response.assert_response().get_element(".pr-source-info")
1717 assert source.text.strip() == "bookmark:origin"
1718 assert source.text.strip() == "bookmark:origin"
1718 assert source.getparent().attrib.get("href") is None
1719 assert source.getparent().attrib.get("href") is None
1719
1720
1720 target = response.assert_response().get_element(".pr-target-info")
1721 target = response.assert_response().get_element(".pr-target-info")
1721 assert target.text.strip() == "bookmark:target"
1722 assert target.text.strip() == "bookmark:target"
1722 assert target.getparent().attrib.get("href") is None
1723 assert target.getparent().attrib.get("href") is None
1723
1724
1724 def test_tag_is_not_a_link(self, pr_util):
1725 def test_tag_is_not_a_link(self, pr_util):
1725 pull_request = pr_util.create_pull_request()
1726 pull_request = pr_util.create_pull_request()
1726 pull_request.source_ref = "tag:origin:1234567890abcdef"
1727 pull_request.source_ref = "tag:origin:1234567890abcdef"
1727 pull_request.target_ref = "tag:target:abcdef1234567890"
1728 pull_request.target_ref = "tag:target:abcdef1234567890"
1728 Session().add(pull_request)
1729 Session().add(pull_request)
1729 Session().commit()
1730 Session().commit()
1730
1731
1731 response = self.app.get(
1732 response = self.app.get(
1732 route_path(
1733 route_path(
1733 "pullrequest_show",
1734 "pullrequest_show",
1734 repo_name=pull_request.target_repo.scm_instance().name,
1735 repo_name=pull_request.target_repo.scm_instance().name,
1735 pull_request_id=pull_request.pull_request_id,
1736 pull_request_id=pull_request.pull_request_id,
1736 )
1737 )
1737 )
1738 )
1738 assert response.status_int == 200
1739 assert response.status_int == 200
1739
1740
1740 source = response.assert_response().get_element(".pr-source-info")
1741 source = response.assert_response().get_element(".pr-source-info")
1741 assert source.text.strip() == "tag:origin"
1742 assert source.text.strip() == "tag:origin"
1742 assert source.getparent().attrib.get("href") is None
1743 assert source.getparent().attrib.get("href") is None
1743
1744
1744 target = response.assert_response().get_element(".pr-target-info")
1745 target = response.assert_response().get_element(".pr-target-info")
1745 assert target.text.strip() == "tag:target"
1746 assert target.text.strip() == "tag:target"
1746 assert target.getparent().attrib.get("href") is None
1747 assert target.getparent().attrib.get("href") is None
1747
1748
1748 @pytest.mark.parametrize("mergeable", [True, False])
1749 @pytest.mark.parametrize("mergeable", [True, False])
1749 def test_shadow_repository_link(self, mergeable, pr_util, http_host_only_stub):
1750 def test_shadow_repository_link(self, mergeable, pr_util, http_host_only_stub):
1750 """
1751 """
1751 Check that the pull request summary page displays a link to the shadow
1752 Check that the pull request summary page displays a link to the shadow
1752 repository if the pull request is mergeable. If it is not mergeable
1753 repository if the pull request is mergeable. If it is not mergeable
1753 the link should not be displayed.
1754 the link should not be displayed.
1754 """
1755 """
1755 pull_request = pr_util.create_pull_request(
1756 pull_request = pr_util.create_pull_request(
1756 mergeable=mergeable, enable_notifications=False
1757 mergeable=mergeable, enable_notifications=False
1757 )
1758 )
1758 target_repo = pull_request.target_repo.scm_instance()
1759 target_repo = pull_request.target_repo.scm_instance()
1759 pr_id = pull_request.pull_request_id
1760 pr_id = pull_request.pull_request_id
1760 shadow_url = "{host}/{repo}/pull-request/{pr_id}/repository".format(
1761 shadow_url = "{host}/{repo}/pull-request/{pr_id}/repository".format(
1761 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id
1762 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id
1762 )
1763 )
1763
1764
1764 response = self.app.get(
1765 response = self.app.get(
1765 route_path(
1766 route_path(
1766 "pullrequest_show", repo_name=target_repo.name, pull_request_id=pr_id
1767 "pullrequest_show", repo_name=target_repo.name, pull_request_id=pr_id
1767 )
1768 )
1768 )
1769 )
1769
1770
1770 if mergeable:
1771 if mergeable:
1771 response.assert_response().element_value_contains(
1772 response.assert_response().element_value_contains(
1772 "input.pr-mergeinfo", shadow_url
1773 "input.pr-mergeinfo", shadow_url
1773 )
1774 )
1774 response.assert_response().element_value_contains(
1775 response.assert_response().element_value_contains(
1775 "input.pr-mergeinfo ", "pr-merge"
1776 "input.pr-mergeinfo ", "pr-merge"
1776 )
1777 )
1777 else:
1778 else:
1778 response.assert_response().no_element_exists(".pr-mergeinfo")
1779 response.assert_response().no_element_exists(".pr-mergeinfo")
1779
1780
1780
1781
1781 @pytest.mark.usefixtures("app")
1782 @pytest.mark.usefixtures("app")
1782 @pytest.mark.backends("git", "hg")
1783 @pytest.mark.backends("git", "hg")
1783 class TestPullrequestsControllerDelete(object):
1784 class TestPullrequestsControllerDelete(object):
1784 def test_pull_request_delete_button_permissions_admin(
1785 def test_pull_request_delete_button_permissions_admin(
1785 self, autologin_user, user_admin, pr_util
1786 self, autologin_user, user_admin, pr_util
1786 ):
1787 ):
1787 pull_request = pr_util.create_pull_request(
1788 pull_request = pr_util.create_pull_request(
1788 author=user_admin.username, enable_notifications=False
1789 author=user_admin.username, enable_notifications=False
1789 )
1790 )
1790
1791
1791 response = self.app.get(
1792 response = self.app.get(
1792 route_path(
1793 route_path(
1793 "pullrequest_show",
1794 "pullrequest_show",
1794 repo_name=pull_request.target_repo.scm_instance().name,
1795 repo_name=pull_request.target_repo.scm_instance().name,
1795 pull_request_id=pull_request.pull_request_id,
1796 pull_request_id=pull_request.pull_request_id,
1796 )
1797 )
1797 )
1798 )
1798
1799
1799 response.mustcontain('id="delete_pullrequest"')
1800 response.mustcontain('id="delete_pullrequest"')
1800 response.mustcontain("Confirm to delete this pull request")
1801 response.mustcontain("Confirm to delete this pull request")
1801
1802
1802 def test_pull_request_delete_button_permissions_owner(
1803 def test_pull_request_delete_button_permissions_owner(
1803 self, autologin_regular_user, user_regular, pr_util
1804 self, autologin_regular_user, user_regular, pr_util
1804 ):
1805 ):
1805 pull_request = pr_util.create_pull_request(
1806 pull_request = pr_util.create_pull_request(
1806 author=user_regular.username, enable_notifications=False
1807 author=user_regular.username, enable_notifications=False
1807 )
1808 )
1808
1809
1809 response = self.app.get(
1810 response = self.app.get(
1810 route_path(
1811 route_path(
1811 "pullrequest_show",
1812 "pullrequest_show",
1812 repo_name=pull_request.target_repo.scm_instance().name,
1813 repo_name=pull_request.target_repo.scm_instance().name,
1813 pull_request_id=pull_request.pull_request_id,
1814 pull_request_id=pull_request.pull_request_id,
1814 )
1815 )
1815 )
1816 )
1816
1817
1817 response.mustcontain('id="delete_pullrequest"')
1818 response.mustcontain('id="delete_pullrequest"')
1818 response.mustcontain("Confirm to delete this pull request")
1819 response.mustcontain("Confirm to delete this pull request")
1819
1820
1820 def test_pull_request_delete_button_permissions_forbidden(
1821 def test_pull_request_delete_button_permissions_forbidden(
1821 self, autologin_regular_user, user_regular, user_admin, pr_util
1822 self, autologin_regular_user, user_regular, user_admin, pr_util
1822 ):
1823 ):
1823 pull_request = pr_util.create_pull_request(
1824 pull_request = pr_util.create_pull_request(
1824 author=user_admin.username, enable_notifications=False
1825 author=user_admin.username, enable_notifications=False
1825 )
1826 )
1826
1827
1827 response = self.app.get(
1828 response = self.app.get(
1828 route_path(
1829 route_path(
1829 "pullrequest_show",
1830 "pullrequest_show",
1830 repo_name=pull_request.target_repo.scm_instance().name,
1831 repo_name=pull_request.target_repo.scm_instance().name,
1831 pull_request_id=pull_request.pull_request_id,
1832 pull_request_id=pull_request.pull_request_id,
1832 )
1833 )
1833 )
1834 )
1834 response.mustcontain(no=['id="delete_pullrequest"'])
1835 response.mustcontain(no=['id="delete_pullrequest"'])
1835 response.mustcontain(no=["Confirm to delete this pull request"])
1836 response.mustcontain(no=["Confirm to delete this pull request"])
1836
1837
1837 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1838 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1838 self, autologin_regular_user, user_regular, user_admin, pr_util, user_util
1839 self, autologin_regular_user, user_regular, user_admin, pr_util, user_util
1839 ):
1840 ):
1840 pull_request = pr_util.create_pull_request(
1841 pull_request = pr_util.create_pull_request(
1841 author=user_admin.username, enable_notifications=False
1842 author=user_admin.username, enable_notifications=False
1842 )
1843 )
1843
1844
1844 user_util.grant_user_permission_to_repo(
1845 user_util.grant_user_permission_to_repo(
1845 pull_request.target_repo, user_regular, "repository.write"
1846 pull_request.target_repo, user_regular, "repository.write"
1846 )
1847 )
1847
1848
1848 response = self.app.get(
1849 response = self.app.get(
1849 route_path(
1850 route_path(
1850 "pullrequest_show",
1851 "pullrequest_show",
1851 repo_name=pull_request.target_repo.scm_instance().name,
1852 repo_name=pull_request.target_repo.scm_instance().name,
1852 pull_request_id=pull_request.pull_request_id,
1853 pull_request_id=pull_request.pull_request_id,
1853 )
1854 )
1854 )
1855 )
1855
1856
1856 response.mustcontain('id="open_edit_pullrequest"')
1857 response.mustcontain('id="open_edit_pullrequest"')
1857 response.mustcontain('id="delete_pullrequest"')
1858 response.mustcontain('id="delete_pullrequest"')
1858 response.mustcontain(no=["Confirm to delete this pull request"])
1859 response.mustcontain(no=["Confirm to delete this pull request"])
1859
1860
1860 def test_delete_comment_returns_404_if_comment_does_not_exist(
1861 def test_delete_comment_returns_404_if_comment_does_not_exist(
1861 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header
1862 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header
1862 ):
1863 ):
1863 pull_request = pr_util.create_pull_request(
1864 pull_request = pr_util.create_pull_request(
1864 author=user_admin.username, enable_notifications=False
1865 author=user_admin.username, enable_notifications=False
1865 )
1866 )
1866
1867
1867 self.app.post(
1868 self.app.post(
1868 route_path(
1869 route_path(
1869 "pullrequest_comment_delete",
1870 "pullrequest_comment_delete",
1870 repo_name=pull_request.target_repo.scm_instance().name,
1871 repo_name=pull_request.target_repo.scm_instance().name,
1871 pull_request_id=pull_request.pull_request_id,
1872 pull_request_id=pull_request.pull_request_id,
1872 comment_id=1024404,
1873 comment_id=1024404,
1873 ),
1874 ),
1874 extra_environ=xhr_header,
1875 extra_environ=xhr_header,
1875 params={"csrf_token": csrf_token},
1876 params={"csrf_token": csrf_token},
1876 status=404,
1877 status=404,
1877 )
1878 )
1878
1879
1879 def test_delete_comment(
1880 def test_delete_comment(
1880 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header
1881 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header
1881 ):
1882 ):
1882 pull_request = pr_util.create_pull_request(
1883 pull_request = pr_util.create_pull_request(
1883 author=user_admin.username, enable_notifications=False
1884 author=user_admin.username, enable_notifications=False
1884 )
1885 )
1885 comment = pr_util.create_comment()
1886 comment = pr_util.create_comment()
1886 comment_id = comment.comment_id
1887 comment_id = comment.comment_id
1887
1888
1888 response = self.app.post(
1889 response = self.app.post(
1889 route_path(
1890 route_path(
1890 "pullrequest_comment_delete",
1891 "pullrequest_comment_delete",
1891 repo_name=pull_request.target_repo.scm_instance().name,
1892 repo_name=pull_request.target_repo.scm_instance().name,
1892 pull_request_id=pull_request.pull_request_id,
1893 pull_request_id=pull_request.pull_request_id,
1893 comment_id=comment_id,
1894 comment_id=comment_id,
1894 ),
1895 ),
1895 extra_environ=xhr_header,
1896 extra_environ=xhr_header,
1896 params={"csrf_token": csrf_token},
1897 params={"csrf_token": csrf_token},
1897 status=200,
1898 status=200,
1898 )
1899 )
1899 assert response.text == "true"
1900 assert response.text == "true"
1900
1901
1901 @pytest.mark.parametrize(
1902 @pytest.mark.parametrize(
1902 "url_type",
1903 "url_type",
1903 [
1904 [
1904 "pullrequest_new",
1905 "pullrequest_new",
1905 "pullrequest_create",
1906 "pullrequest_create",
1906 "pullrequest_update",
1907 "pullrequest_update",
1907 "pullrequest_merge",
1908 "pullrequest_merge",
1908 ],
1909 ],
1909 )
1910 )
1910 def test_pull_request_is_forbidden_on_archived_repo(
1911 def test_pull_request_is_forbidden_on_archived_repo(
1911 self, autologin_user, backend, xhr_header, user_util, url_type
1912 self, autologin_user, backend, xhr_header, user_util, url_type
1912 ):
1913 ):
1913 # create a temporary repo
1914 # create a temporary repo
1914 source = user_util.create_repo(repo_type=backend.alias)
1915 source = user_util.create_repo(repo_type=backend.alias)
1915 repo_name = source.repo_name
1916 repo_name = source.repo_name
1916 repo = Repository.get_by_repo_name(repo_name)
1917 repo = Repository.get_by_repo_name(repo_name)
1917 repo.archived = True
1918 repo.archived = True
1918 Session().commit()
1919 Session().commit()
1919
1920
1920 response = self.app.get(
1921 response = self.app.get(
1921 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302
1922 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302
1922 )
1923 )
1923
1924
1924 msg = "Action not supported for archived repository."
1925 msg = "Action not supported for archived repository."
1925 assert_session_flash(response, msg)
1926 assert_session_flash(response, msg)
1926
1927
1927
1928
1928 def assert_pull_request_status(pull_request, expected_status):
1929 def assert_pull_request_status(pull_request, expected_status):
1929 status = ChangesetStatusModel().calculated_review_status(pull_request=pull_request)
1930 status = ChangesetStatusModel().calculated_review_status(pull_request=pull_request)
1930 assert status == expected_status
1931 assert status == expected_status
1931
1932
1932
1933
1933 @pytest.mark.parametrize("route", ["pullrequest_new", "pullrequest_create"])
1934 @pytest.mark.parametrize("route", ["pullrequest_new", "pullrequest_create"])
1934 @pytest.mark.usefixtures("autologin_user")
1935 @pytest.mark.usefixtures("autologin_user")
1935 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1936 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1936 app.get(route_path(route, repo_name=backend_svn.repo_name), status=404)
1937 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