##// END OF EJS Templates
db: use more consistent sorting using real objects, strings are deprecated in laters sqlalchemy query syntax.
marcink -
r3949:2da6b4aa default
parent child Browse files
Show More
@@ -1,113 +1,113 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import UserLog
23 from rhodecode.model.db import UserLog
24 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestClosePullRequest(object):
31 class TestClosePullRequest(object):
32
32
33 @pytest.mark.backends("git", "hg")
33 @pytest.mark.backends("git", "hg")
34 def test_api_close_pull_request(self, pr_util):
34 def test_api_close_pull_request(self, pr_util):
35 pull_request = pr_util.create_pull_request()
35 pull_request = pr_util.create_pull_request()
36 pull_request_id = pull_request.pull_request_id
36 pull_request_id = pull_request.pull_request_id
37 author = pull_request.user_id
37 author = pull_request.user_id
38 repo = pull_request.target_repo.repo_id
38 repo = pull_request.target_repo.repo_id
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'close_pull_request',
40 self.apikey, 'close_pull_request',
41 repoid=pull_request.target_repo.repo_name,
41 repoid=pull_request.target_repo.repo_name,
42 pullrequestid=pull_request.pull_request_id)
42 pullrequestid=pull_request.pull_request_id)
43 response = api_call(self.app, params)
43 response = api_call(self.app, params)
44 expected = {
44 expected = {
45 'pull_request_id': pull_request_id,
45 'pull_request_id': pull_request_id,
46 'close_status': 'Rejected',
46 'close_status': 'Rejected',
47 'closed': True,
47 'closed': True,
48 }
48 }
49 assert_ok(id_, expected, response.body)
49 assert_ok(id_, expected, response.body)
50 journal = UserLog.query()\
50 journal = UserLog.query()\
51 .filter(UserLog.user_id == author) \
51 .filter(UserLog.user_id == author) \
52 .order_by('user_log_id') \
52 .order_by(UserLog.user_log_id.asc()) \
53 .filter(UserLog.repository_id == repo)\
53 .filter(UserLog.repository_id == repo)\
54 .all()
54 .all()
55 assert journal[-1].action == 'repo.pull_request.close'
55 assert journal[-1].action == 'repo.pull_request.close'
56
56
57 @pytest.mark.backends("git", "hg")
57 @pytest.mark.backends("git", "hg")
58 def test_api_close_pull_request_already_closed_error(self, pr_util):
58 def test_api_close_pull_request_already_closed_error(self, pr_util):
59 pull_request = pr_util.create_pull_request()
59 pull_request = pr_util.create_pull_request()
60 pull_request_id = pull_request.pull_request_id
60 pull_request_id = pull_request.pull_request_id
61 pull_request_repo = pull_request.target_repo.repo_name
61 pull_request_repo = pull_request.target_repo.repo_name
62 PullRequestModel().close_pull_request(
62 PullRequestModel().close_pull_request(
63 pull_request, pull_request.author)
63 pull_request, pull_request.author)
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey, 'close_pull_request',
65 self.apikey, 'close_pull_request',
66 repoid=pull_request_repo, pullrequestid=pull_request_id)
66 repoid=pull_request_repo, pullrequestid=pull_request_id)
67 response = api_call(self.app, params)
67 response = api_call(self.app, params)
68
68
69 expected = 'pull request `%s` is already closed' % pull_request_id
69 expected = 'pull request `%s` is already closed' % pull_request_id
70 assert_error(id_, expected, given=response.body)
70 assert_error(id_, expected, given=response.body)
71
71
72 @pytest.mark.backends("git", "hg")
72 @pytest.mark.backends("git", "hg")
73 def test_api_close_pull_request_repo_error(self, pr_util):
73 def test_api_close_pull_request_repo_error(self, pr_util):
74 pull_request = pr_util.create_pull_request()
74 pull_request = pr_util.create_pull_request()
75 id_, params = build_data(
75 id_, params = build_data(
76 self.apikey, 'close_pull_request',
76 self.apikey, 'close_pull_request',
77 repoid=666, pullrequestid=pull_request.pull_request_id)
77 repoid=666, pullrequestid=pull_request.pull_request_id)
78 response = api_call(self.app, params)
78 response = api_call(self.app, params)
79
79
80 expected = 'repository `666` does not exist'
80 expected = 'repository `666` does not exist'
81 assert_error(id_, expected, given=response.body)
81 assert_error(id_, expected, given=response.body)
82
82
83 @pytest.mark.backends("git", "hg")
83 @pytest.mark.backends("git", "hg")
84 def test_api_close_pull_request_non_admin_with_userid_error(self,
84 def test_api_close_pull_request_non_admin_with_userid_error(self,
85 pr_util):
85 pr_util):
86 pull_request = pr_util.create_pull_request()
86 pull_request = pr_util.create_pull_request()
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey_regular, 'close_pull_request',
88 self.apikey_regular, 'close_pull_request',
89 repoid=pull_request.target_repo.repo_name,
89 repoid=pull_request.target_repo.repo_name,
90 pullrequestid=pull_request.pull_request_id,
90 pullrequestid=pull_request.pull_request_id,
91 userid=TEST_USER_ADMIN_LOGIN)
91 userid=TEST_USER_ADMIN_LOGIN)
92 response = api_call(self.app, params)
92 response = api_call(self.app, params)
93
93
94 expected = 'userid is not the same as your user'
94 expected = 'userid is not the same as your user'
95 assert_error(id_, expected, given=response.body)
95 assert_error(id_, expected, given=response.body)
96
96
97 @pytest.mark.backends("git", "hg")
97 @pytest.mark.backends("git", "hg")
98 def test_api_close_pull_request_no_perms_to_close(
98 def test_api_close_pull_request_no_perms_to_close(
99 self, user_util, pr_util):
99 self, user_util, pr_util):
100 user = user_util.create_user()
100 user = user_util.create_user()
101 pull_request = pr_util.create_pull_request()
101 pull_request = pr_util.create_pull_request()
102
102
103 id_, params = build_data(
103 id_, params = build_data(
104 user.api_key, 'close_pull_request',
104 user.api_key, 'close_pull_request',
105 repoid=pull_request.target_repo.repo_name,
105 repoid=pull_request.target_repo.repo_name,
106 pullrequestid=pull_request.pull_request_id,)
106 pullrequestid=pull_request.pull_request_id,)
107 response = api_call(self.app, params)
107 response = api_call(self.app, params)
108
108
109 expected = ('pull request `%s` close failed, '
109 expected = ('pull request `%s` close failed, '
110 'no permission to close.') % pull_request.pull_request_id
110 'no permission to close.') % pull_request.pull_request_id
111
111
112 response_json = response.json['error']
112 response_json = response.json['error']
113 assert response_json == expected
113 assert response_json == expected
@@ -1,209 +1,209 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.comment import CommentsModel
23 from rhodecode.model.comment import CommentsModel
24 from rhodecode.model.db import UserLog
24 from rhodecode.model.db import UserLog
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok)
28 build_data, api_call, assert_error, assert_ok)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestCommentPullRequest(object):
32 class TestCommentPullRequest(object):
33 finalizers = []
33 finalizers = []
34
34
35 def teardown_method(self, method):
35 def teardown_method(self, method):
36 if self.finalizers:
36 if self.finalizers:
37 for finalizer in self.finalizers:
37 for finalizer in self.finalizers:
38 finalizer()
38 finalizer()
39 self.finalizers = []
39 self.finalizers = []
40
40
41 @pytest.mark.backends("git", "hg")
41 @pytest.mark.backends("git", "hg")
42 def test_api_comment_pull_request(self, pr_util, no_notifications):
42 def test_api_comment_pull_request(self, pr_util, no_notifications):
43 pull_request = pr_util.create_pull_request()
43 pull_request = pr_util.create_pull_request()
44 pull_request_id = pull_request.pull_request_id
44 pull_request_id = pull_request.pull_request_id
45 author = pull_request.user_id
45 author = pull_request.user_id
46 repo = pull_request.target_repo.repo_id
46 repo = pull_request.target_repo.repo_id
47 id_, params = build_data(
47 id_, params = build_data(
48 self.apikey, 'comment_pull_request',
48 self.apikey, 'comment_pull_request',
49 repoid=pull_request.target_repo.repo_name,
49 repoid=pull_request.target_repo.repo_name,
50 pullrequestid=pull_request.pull_request_id,
50 pullrequestid=pull_request.pull_request_id,
51 message='test message')
51 message='test message')
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
54
54
55 comments = CommentsModel().get_comments(
55 comments = CommentsModel().get_comments(
56 pull_request.target_repo.repo_id, pull_request=pull_request)
56 pull_request.target_repo.repo_id, pull_request=pull_request)
57
57
58 expected = {
58 expected = {
59 'pull_request_id': pull_request.pull_request_id,
59 'pull_request_id': pull_request.pull_request_id,
60 'comment_id': comments[-1].comment_id,
60 'comment_id': comments[-1].comment_id,
61 'status': {'given': None, 'was_changed': None}
61 'status': {'given': None, 'was_changed': None}
62 }
62 }
63 assert_ok(id_, expected, response.body)
63 assert_ok(id_, expected, response.body)
64
64
65 journal = UserLog.query()\
65 journal = UserLog.query()\
66 .filter(UserLog.user_id == author)\
66 .filter(UserLog.user_id == author)\
67 .filter(UserLog.repository_id == repo) \
67 .filter(UserLog.repository_id == repo) \
68 .order_by('user_log_id') \
68 .order_by(UserLog.user_log_id.asc()) \
69 .all()
69 .all()
70 assert journal[-1].action == 'repo.pull_request.comment.create'
70 assert journal[-1].action == 'repo.pull_request.comment.create'
71
71
72 @pytest.mark.backends("git", "hg")
72 @pytest.mark.backends("git", "hg")
73 def test_api_comment_pull_request_change_status(
73 def test_api_comment_pull_request_change_status(
74 self, pr_util, no_notifications):
74 self, pr_util, no_notifications):
75 pull_request = pr_util.create_pull_request()
75 pull_request = pr_util.create_pull_request()
76 pull_request_id = pull_request.pull_request_id
76 pull_request_id = pull_request.pull_request_id
77 id_, params = build_data(
77 id_, params = build_data(
78 self.apikey, 'comment_pull_request',
78 self.apikey, 'comment_pull_request',
79 repoid=pull_request.target_repo.repo_name,
79 repoid=pull_request.target_repo.repo_name,
80 pullrequestid=pull_request.pull_request_id,
80 pullrequestid=pull_request.pull_request_id,
81 status='rejected')
81 status='rejected')
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 pull_request = PullRequestModel().get(pull_request_id)
83 pull_request = PullRequestModel().get(pull_request_id)
84
84
85 comments = CommentsModel().get_comments(
85 comments = CommentsModel().get_comments(
86 pull_request.target_repo.repo_id, pull_request=pull_request)
86 pull_request.target_repo.repo_id, pull_request=pull_request)
87 expected = {
87 expected = {
88 'pull_request_id': pull_request.pull_request_id,
88 'pull_request_id': pull_request.pull_request_id,
89 'comment_id': comments[-1].comment_id,
89 'comment_id': comments[-1].comment_id,
90 'status': {'given': 'rejected', 'was_changed': True}
90 'status': {'given': 'rejected', 'was_changed': True}
91 }
91 }
92 assert_ok(id_, expected, response.body)
92 assert_ok(id_, expected, response.body)
93
93
94 @pytest.mark.backends("git", "hg")
94 @pytest.mark.backends("git", "hg")
95 def test_api_comment_pull_request_change_status_with_specific_commit_id(
95 def test_api_comment_pull_request_change_status_with_specific_commit_id(
96 self, pr_util, no_notifications):
96 self, pr_util, no_notifications):
97 pull_request = pr_util.create_pull_request()
97 pull_request = pr_util.create_pull_request()
98 pull_request_id = pull_request.pull_request_id
98 pull_request_id = pull_request.pull_request_id
99 latest_commit_id = 'test_commit'
99 latest_commit_id = 'test_commit'
100 # inject additional revision, to fail test the status change on
100 # inject additional revision, to fail test the status change on
101 # non-latest commit
101 # non-latest commit
102 pull_request.revisions = pull_request.revisions + ['test_commit']
102 pull_request.revisions = pull_request.revisions + ['test_commit']
103
103
104 id_, params = build_data(
104 id_, params = build_data(
105 self.apikey, 'comment_pull_request',
105 self.apikey, 'comment_pull_request',
106 repoid=pull_request.target_repo.repo_name,
106 repoid=pull_request.target_repo.repo_name,
107 pullrequestid=pull_request.pull_request_id,
107 pullrequestid=pull_request.pull_request_id,
108 status='approved', commit_id=latest_commit_id)
108 status='approved', commit_id=latest_commit_id)
109 response = api_call(self.app, params)
109 response = api_call(self.app, params)
110 pull_request = PullRequestModel().get(pull_request_id)
110 pull_request = PullRequestModel().get(pull_request_id)
111
111
112 expected = {
112 expected = {
113 'pull_request_id': pull_request.pull_request_id,
113 'pull_request_id': pull_request.pull_request_id,
114 'comment_id': None,
114 'comment_id': None,
115 'status': {'given': 'approved', 'was_changed': False}
115 'status': {'given': 'approved', 'was_changed': False}
116 }
116 }
117 assert_ok(id_, expected, response.body)
117 assert_ok(id_, expected, response.body)
118
118
119 @pytest.mark.backends("git", "hg")
119 @pytest.mark.backends("git", "hg")
120 def test_api_comment_pull_request_change_status_with_specific_commit_id(
120 def test_api_comment_pull_request_change_status_with_specific_commit_id(
121 self, pr_util, no_notifications):
121 self, pr_util, no_notifications):
122 pull_request = pr_util.create_pull_request()
122 pull_request = pr_util.create_pull_request()
123 pull_request_id = pull_request.pull_request_id
123 pull_request_id = pull_request.pull_request_id
124 latest_commit_id = pull_request.revisions[0]
124 latest_commit_id = pull_request.revisions[0]
125
125
126 id_, params = build_data(
126 id_, params = build_data(
127 self.apikey, 'comment_pull_request',
127 self.apikey, 'comment_pull_request',
128 repoid=pull_request.target_repo.repo_name,
128 repoid=pull_request.target_repo.repo_name,
129 pullrequestid=pull_request.pull_request_id,
129 pullrequestid=pull_request.pull_request_id,
130 status='approved', commit_id=latest_commit_id)
130 status='approved', commit_id=latest_commit_id)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132 pull_request = PullRequestModel().get(pull_request_id)
132 pull_request = PullRequestModel().get(pull_request_id)
133
133
134 comments = CommentsModel().get_comments(
134 comments = CommentsModel().get_comments(
135 pull_request.target_repo.repo_id, pull_request=pull_request)
135 pull_request.target_repo.repo_id, pull_request=pull_request)
136 expected = {
136 expected = {
137 'pull_request_id': pull_request.pull_request_id,
137 'pull_request_id': pull_request.pull_request_id,
138 'comment_id': comments[-1].comment_id,
138 'comment_id': comments[-1].comment_id,
139 'status': {'given': 'approved', 'was_changed': True}
139 'status': {'given': 'approved', 'was_changed': True}
140 }
140 }
141 assert_ok(id_, expected, response.body)
141 assert_ok(id_, expected, response.body)
142
142
143 @pytest.mark.backends("git", "hg")
143 @pytest.mark.backends("git", "hg")
144 def test_api_comment_pull_request_missing_params_error(self, pr_util):
144 def test_api_comment_pull_request_missing_params_error(self, pr_util):
145 pull_request = pr_util.create_pull_request()
145 pull_request = pr_util.create_pull_request()
146 pull_request_id = pull_request.pull_request_id
146 pull_request_id = pull_request.pull_request_id
147 pull_request_repo = pull_request.target_repo.repo_name
147 pull_request_repo = pull_request.target_repo.repo_name
148 id_, params = build_data(
148 id_, params = build_data(
149 self.apikey, 'comment_pull_request',
149 self.apikey, 'comment_pull_request',
150 repoid=pull_request_repo,
150 repoid=pull_request_repo,
151 pullrequestid=pull_request_id)
151 pullrequestid=pull_request_id)
152 response = api_call(self.app, params)
152 response = api_call(self.app, params)
153
153
154 expected = 'Both message and status parameters are missing. At least one is required.'
154 expected = 'Both message and status parameters are missing. At least one is required.'
155 assert_error(id_, expected, given=response.body)
155 assert_error(id_, expected, given=response.body)
156
156
157 @pytest.mark.backends("git", "hg")
157 @pytest.mark.backends("git", "hg")
158 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
158 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
159 pull_request = pr_util.create_pull_request()
159 pull_request = pr_util.create_pull_request()
160 pull_request_id = pull_request.pull_request_id
160 pull_request_id = pull_request.pull_request_id
161 pull_request_repo = pull_request.target_repo.repo_name
161 pull_request_repo = pull_request.target_repo.repo_name
162 id_, params = build_data(
162 id_, params = build_data(
163 self.apikey, 'comment_pull_request',
163 self.apikey, 'comment_pull_request',
164 repoid=pull_request_repo,
164 repoid=pull_request_repo,
165 pullrequestid=pull_request_id,
165 pullrequestid=pull_request_id,
166 status='42')
166 status='42')
167 response = api_call(self.app, params)
167 response = api_call(self.app, params)
168
168
169 expected = 'Unknown comment status: `42`'
169 expected = 'Unknown comment status: `42`'
170 assert_error(id_, expected, given=response.body)
170 assert_error(id_, expected, given=response.body)
171
171
172 @pytest.mark.backends("git", "hg")
172 @pytest.mark.backends("git", "hg")
173 def test_api_comment_pull_request_repo_error(self, pr_util):
173 def test_api_comment_pull_request_repo_error(self, pr_util):
174 pull_request = pr_util.create_pull_request()
174 pull_request = pr_util.create_pull_request()
175 id_, params = build_data(
175 id_, params = build_data(
176 self.apikey, 'comment_pull_request',
176 self.apikey, 'comment_pull_request',
177 repoid=666, pullrequestid=pull_request.pull_request_id)
177 repoid=666, pullrequestid=pull_request.pull_request_id)
178 response = api_call(self.app, params)
178 response = api_call(self.app, params)
179
179
180 expected = 'repository `666` does not exist'
180 expected = 'repository `666` does not exist'
181 assert_error(id_, expected, given=response.body)
181 assert_error(id_, expected, given=response.body)
182
182
183 @pytest.mark.backends("git", "hg")
183 @pytest.mark.backends("git", "hg")
184 def test_api_comment_pull_request_non_admin_with_userid_error(
184 def test_api_comment_pull_request_non_admin_with_userid_error(
185 self, pr_util):
185 self, pr_util):
186 pull_request = pr_util.create_pull_request()
186 pull_request = pr_util.create_pull_request()
187 id_, params = build_data(
187 id_, params = build_data(
188 self.apikey_regular, 'comment_pull_request',
188 self.apikey_regular, 'comment_pull_request',
189 repoid=pull_request.target_repo.repo_name,
189 repoid=pull_request.target_repo.repo_name,
190 pullrequestid=pull_request.pull_request_id,
190 pullrequestid=pull_request.pull_request_id,
191 userid=TEST_USER_ADMIN_LOGIN)
191 userid=TEST_USER_ADMIN_LOGIN)
192 response = api_call(self.app, params)
192 response = api_call(self.app, params)
193
193
194 expected = 'userid is not the same as your user'
194 expected = 'userid is not the same as your user'
195 assert_error(id_, expected, given=response.body)
195 assert_error(id_, expected, given=response.body)
196
196
197 @pytest.mark.backends("git", "hg")
197 @pytest.mark.backends("git", "hg")
198 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
198 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
199 pull_request = pr_util.create_pull_request()
199 pull_request = pr_util.create_pull_request()
200 id_, params = build_data(
200 id_, params = build_data(
201 self.apikey_regular, 'comment_pull_request',
201 self.apikey_regular, 'comment_pull_request',
202 repoid=pull_request.target_repo.repo_name,
202 repoid=pull_request.target_repo.repo_name,
203 status='approved',
203 status='approved',
204 pullrequestid=pull_request.pull_request_id,
204 pullrequestid=pull_request.pull_request_id,
205 commit_id='XXX')
205 commit_id='XXX')
206 response = api_call(self.app, params)
206 response = api_call(self.app, params)
207
207
208 expected = 'Invalid commit_id `XXX` for this pull request.'
208 expected = 'Invalid commit_id `XXX` for this pull request.'
209 assert_error(id_, expected, given=response.body)
209 assert_error(id_, expected, given=response.body)
@@ -1,259 +1,259 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import UserLog, PullRequest
23 from rhodecode.model.db import UserLog, PullRequest
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestMergePullRequest(object):
31 class TestMergePullRequest(object):
32
32
33 @pytest.mark.backends("git", "hg")
33 @pytest.mark.backends("git", "hg")
34 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
34 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
35 pull_request = pr_util.create_pull_request(mergeable=True)
35 pull_request = pr_util.create_pull_request(mergeable=True)
36 pull_request_id = pull_request.pull_request_id
36 pull_request_id = pull_request.pull_request_id
37 pull_request_repo = pull_request.target_repo.repo_name
37 pull_request_repo = pull_request.target_repo.repo_name
38
38
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'merge_pull_request',
40 self.apikey, 'merge_pull_request',
41 repoid=pull_request_repo,
41 repoid=pull_request_repo,
42 pullrequestid=pull_request_id)
42 pullrequestid=pull_request_id)
43
43
44 response = api_call(self.app, params)
44 response = api_call(self.app, params)
45
45
46 # The above api call detaches the pull request DB object from the
46 # The above api call detaches the pull request DB object from the
47 # session because of an unconditional transaction rollback in our
47 # session because of an unconditional transaction rollback in our
48 # middleware. Therefore we need to add it back here if we want to use it.
48 # middleware. Therefore we need to add it back here if we want to use it.
49 Session().add(pull_request)
49 Session().add(pull_request)
50
50
51 expected = 'merge not possible for following reasons: ' \
51 expected = 'merge not possible for following reasons: ' \
52 'Pull request reviewer approval is pending.'
52 'Pull request reviewer approval is pending.'
53 assert_error(id_, expected, given=response.body)
53 assert_error(id_, expected, given=response.body)
54
54
55 @pytest.mark.backends("git", "hg")
55 @pytest.mark.backends("git", "hg")
56 def test_api_merge_pull_request_merge_failed_disallowed_state(
56 def test_api_merge_pull_request_merge_failed_disallowed_state(
57 self, pr_util, no_notifications):
57 self, pr_util, no_notifications):
58 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
58 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
59 pull_request_id = pull_request.pull_request_id
59 pull_request_id = pull_request.pull_request_id
60 pull_request_repo = pull_request.target_repo.repo_name
60 pull_request_repo = pull_request.target_repo.repo_name
61
61
62 pr = PullRequest.get(pull_request_id)
62 pr = PullRequest.get(pull_request_id)
63 pr.pull_request_state = pull_request.STATE_UPDATING
63 pr.pull_request_state = pull_request.STATE_UPDATING
64 Session().add(pr)
64 Session().add(pr)
65 Session().commit()
65 Session().commit()
66
66
67 id_, params = build_data(
67 id_, params = build_data(
68 self.apikey, 'merge_pull_request',
68 self.apikey, 'merge_pull_request',
69 repoid=pull_request_repo,
69 repoid=pull_request_repo,
70 pullrequestid=pull_request_id)
70 pullrequestid=pull_request_id)
71
71
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73 expected = 'Operation forbidden because pull request is in state {}, '\
73 expected = 'Operation forbidden because pull request is in state {}, '\
74 'only state {} is allowed.'.format(PullRequest.STATE_UPDATING,
74 'only state {} is allowed.'.format(PullRequest.STATE_UPDATING,
75 PullRequest.STATE_CREATED)
75 PullRequest.STATE_CREATED)
76 assert_error(id_, expected, given=response.body)
76 assert_error(id_, expected, given=response.body)
77
77
78 @pytest.mark.backends("git", "hg")
78 @pytest.mark.backends("git", "hg")
79 def test_api_merge_pull_request(self, pr_util, no_notifications):
79 def test_api_merge_pull_request(self, pr_util, no_notifications):
80 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
80 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
81 author = pull_request.user_id
81 author = pull_request.user_id
82 repo = pull_request.target_repo.repo_id
82 repo = pull_request.target_repo.repo_id
83 pull_request_id = pull_request.pull_request_id
83 pull_request_id = pull_request.pull_request_id
84 pull_request_repo = pull_request.target_repo.repo_name
84 pull_request_repo = pull_request.target_repo.repo_name
85
85
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey, 'comment_pull_request',
87 self.apikey, 'comment_pull_request',
88 repoid=pull_request_repo,
88 repoid=pull_request_repo,
89 pullrequestid=pull_request_id,
89 pullrequestid=pull_request_id,
90 status='approved')
90 status='approved')
91
91
92 response = api_call(self.app, params)
92 response = api_call(self.app, params)
93 expected = {
93 expected = {
94 'comment_id': response.json.get('result', {}).get('comment_id'),
94 'comment_id': response.json.get('result', {}).get('comment_id'),
95 'pull_request_id': pull_request_id,
95 'pull_request_id': pull_request_id,
96 'status': {'given': 'approved', 'was_changed': True}
96 'status': {'given': 'approved', 'was_changed': True}
97 }
97 }
98 assert_ok(id_, expected, given=response.body)
98 assert_ok(id_, expected, given=response.body)
99
99
100 id_, params = build_data(
100 id_, params = build_data(
101 self.apikey, 'merge_pull_request',
101 self.apikey, 'merge_pull_request',
102 repoid=pull_request_repo,
102 repoid=pull_request_repo,
103 pullrequestid=pull_request_id)
103 pullrequestid=pull_request_id)
104
104
105 response = api_call(self.app, params)
105 response = api_call(self.app, params)
106
106
107 pull_request = PullRequest.get(pull_request_id)
107 pull_request = PullRequest.get(pull_request_id)
108
108
109 expected = {
109 expected = {
110 'executed': True,
110 'executed': True,
111 'failure_reason': 0,
111 'failure_reason': 0,
112 'merge_status_message': 'This pull request can be automatically merged.',
112 'merge_status_message': 'This pull request can be automatically merged.',
113 'possible': True,
113 'possible': True,
114 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
114 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
115 'merge_ref': pull_request.shadow_merge_ref._asdict()
115 'merge_ref': pull_request.shadow_merge_ref._asdict()
116 }
116 }
117
117
118 assert_ok(id_, expected, response.body)
118 assert_ok(id_, expected, response.body)
119
119
120 journal = UserLog.query()\
120 journal = UserLog.query()\
121 .filter(UserLog.user_id == author)\
121 .filter(UserLog.user_id == author)\
122 .filter(UserLog.repository_id == repo) \
122 .filter(UserLog.repository_id == repo) \
123 .order_by('user_log_id') \
123 .order_by(UserLog.user_log_id.asc()) \
124 .all()
124 .all()
125 assert journal[-2].action == 'repo.pull_request.merge'
125 assert journal[-2].action == 'repo.pull_request.merge'
126 assert journal[-1].action == 'repo.pull_request.close'
126 assert journal[-1].action == 'repo.pull_request.close'
127
127
128 id_, params = build_data(
128 id_, params = build_data(
129 self.apikey, 'merge_pull_request',
129 self.apikey, 'merge_pull_request',
130 repoid=pull_request_repo, pullrequestid=pull_request_id)
130 repoid=pull_request_repo, pullrequestid=pull_request_id)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132
132
133 expected = 'merge not possible for following reasons: This pull request is closed.'
133 expected = 'merge not possible for following reasons: This pull request is closed.'
134 assert_error(id_, expected, given=response.body)
134 assert_error(id_, expected, given=response.body)
135
135
136 @pytest.mark.backends("git", "hg")
136 @pytest.mark.backends("git", "hg")
137 def test_api_merge_pull_request_as_another_user_no_perms_to_merge(
137 def test_api_merge_pull_request_as_another_user_no_perms_to_merge(
138 self, pr_util, no_notifications, user_util):
138 self, pr_util, no_notifications, user_util):
139 merge_user = user_util.create_user()
139 merge_user = user_util.create_user()
140 merge_user_id = merge_user.user_id
140 merge_user_id = merge_user.user_id
141 merge_user_username = merge_user.username
141 merge_user_username = merge_user.username
142
142
143 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
143 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
144
144
145 pull_request_id = pull_request.pull_request_id
145 pull_request_id = pull_request.pull_request_id
146 pull_request_repo = pull_request.target_repo.repo_name
146 pull_request_repo = pull_request.target_repo.repo_name
147
147
148 id_, params = build_data(
148 id_, params = build_data(
149 self.apikey, 'comment_pull_request',
149 self.apikey, 'comment_pull_request',
150 repoid=pull_request_repo,
150 repoid=pull_request_repo,
151 pullrequestid=pull_request_id,
151 pullrequestid=pull_request_id,
152 status='approved')
152 status='approved')
153
153
154 response = api_call(self.app, params)
154 response = api_call(self.app, params)
155 expected = {
155 expected = {
156 'comment_id': response.json.get('result', {}).get('comment_id'),
156 'comment_id': response.json.get('result', {}).get('comment_id'),
157 'pull_request_id': pull_request_id,
157 'pull_request_id': pull_request_id,
158 'status': {'given': 'approved', 'was_changed': True}
158 'status': {'given': 'approved', 'was_changed': True}
159 }
159 }
160 assert_ok(id_, expected, given=response.body)
160 assert_ok(id_, expected, given=response.body)
161 id_, params = build_data(
161 id_, params = build_data(
162 self.apikey, 'merge_pull_request',
162 self.apikey, 'merge_pull_request',
163 repoid=pull_request_repo,
163 repoid=pull_request_repo,
164 pullrequestid=pull_request_id,
164 pullrequestid=pull_request_id,
165 userid=merge_user_id
165 userid=merge_user_id
166 )
166 )
167
167
168 response = api_call(self.app, params)
168 response = api_call(self.app, params)
169 expected = 'merge not possible for following reasons: User `{}` ' \
169 expected = 'merge not possible for following reasons: User `{}` ' \
170 'not allowed to perform merge.'.format(merge_user_username)
170 'not allowed to perform merge.'.format(merge_user_username)
171 assert_error(id_, expected, response.body)
171 assert_error(id_, expected, response.body)
172
172
173 @pytest.mark.backends("git", "hg")
173 @pytest.mark.backends("git", "hg")
174 def test_api_merge_pull_request_as_another_user(self, pr_util, no_notifications, user_util):
174 def test_api_merge_pull_request_as_another_user(self, pr_util, no_notifications, user_util):
175 merge_user = user_util.create_user()
175 merge_user = user_util.create_user()
176 merge_user_id = merge_user.user_id
176 merge_user_id = merge_user.user_id
177 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
177 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
178 user_util.grant_user_permission_to_repo(
178 user_util.grant_user_permission_to_repo(
179 pull_request.target_repo, merge_user, 'repository.write')
179 pull_request.target_repo, merge_user, 'repository.write')
180 author = pull_request.user_id
180 author = pull_request.user_id
181 repo = pull_request.target_repo.repo_id
181 repo = pull_request.target_repo.repo_id
182 pull_request_id = pull_request.pull_request_id
182 pull_request_id = pull_request.pull_request_id
183 pull_request_repo = pull_request.target_repo.repo_name
183 pull_request_repo = pull_request.target_repo.repo_name
184
184
185 id_, params = build_data(
185 id_, params = build_data(
186 self.apikey, 'comment_pull_request',
186 self.apikey, 'comment_pull_request',
187 repoid=pull_request_repo,
187 repoid=pull_request_repo,
188 pullrequestid=pull_request_id,
188 pullrequestid=pull_request_id,
189 status='approved')
189 status='approved')
190
190
191 response = api_call(self.app, params)
191 response = api_call(self.app, params)
192 expected = {
192 expected = {
193 'comment_id': response.json.get('result', {}).get('comment_id'),
193 'comment_id': response.json.get('result', {}).get('comment_id'),
194 'pull_request_id': pull_request_id,
194 'pull_request_id': pull_request_id,
195 'status': {'given': 'approved', 'was_changed': True}
195 'status': {'given': 'approved', 'was_changed': True}
196 }
196 }
197 assert_ok(id_, expected, given=response.body)
197 assert_ok(id_, expected, given=response.body)
198
198
199 id_, params = build_data(
199 id_, params = build_data(
200 self.apikey, 'merge_pull_request',
200 self.apikey, 'merge_pull_request',
201 repoid=pull_request_repo,
201 repoid=pull_request_repo,
202 pullrequestid=pull_request_id,
202 pullrequestid=pull_request_id,
203 userid=merge_user_id
203 userid=merge_user_id
204 )
204 )
205
205
206 response = api_call(self.app, params)
206 response = api_call(self.app, params)
207
207
208 pull_request = PullRequest.get(pull_request_id)
208 pull_request = PullRequest.get(pull_request_id)
209
209
210 expected = {
210 expected = {
211 'executed': True,
211 'executed': True,
212 'failure_reason': 0,
212 'failure_reason': 0,
213 'merge_status_message': 'This pull request can be automatically merged.',
213 'merge_status_message': 'This pull request can be automatically merged.',
214 'possible': True,
214 'possible': True,
215 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
215 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
216 'merge_ref': pull_request.shadow_merge_ref._asdict()
216 'merge_ref': pull_request.shadow_merge_ref._asdict()
217 }
217 }
218
218
219 assert_ok(id_, expected, response.body)
219 assert_ok(id_, expected, response.body)
220
220
221 journal = UserLog.query() \
221 journal = UserLog.query() \
222 .filter(UserLog.user_id == merge_user_id) \
222 .filter(UserLog.user_id == merge_user_id) \
223 .filter(UserLog.repository_id == repo) \
223 .filter(UserLog.repository_id == repo) \
224 .order_by('user_log_id') \
224 .order_by(UserLog.user_log_id.asc()) \
225 .all()
225 .all()
226 assert journal[-2].action == 'repo.pull_request.merge'
226 assert journal[-2].action == 'repo.pull_request.merge'
227 assert journal[-1].action == 'repo.pull_request.close'
227 assert journal[-1].action == 'repo.pull_request.close'
228
228
229 id_, params = build_data(
229 id_, params = build_data(
230 self.apikey, 'merge_pull_request',
230 self.apikey, 'merge_pull_request',
231 repoid=pull_request_repo, pullrequestid=pull_request_id, userid=merge_user_id)
231 repoid=pull_request_repo, pullrequestid=pull_request_id, userid=merge_user_id)
232 response = api_call(self.app, params)
232 response = api_call(self.app, params)
233
233
234 expected = 'merge not possible for following reasons: This pull request is closed.'
234 expected = 'merge not possible for following reasons: This pull request is closed.'
235 assert_error(id_, expected, given=response.body)
235 assert_error(id_, expected, given=response.body)
236
236
237 @pytest.mark.backends("git", "hg")
237 @pytest.mark.backends("git", "hg")
238 def test_api_merge_pull_request_repo_error(self, pr_util):
238 def test_api_merge_pull_request_repo_error(self, pr_util):
239 pull_request = pr_util.create_pull_request()
239 pull_request = pr_util.create_pull_request()
240 id_, params = build_data(
240 id_, params = build_data(
241 self.apikey, 'merge_pull_request',
241 self.apikey, 'merge_pull_request',
242 repoid=666, pullrequestid=pull_request.pull_request_id)
242 repoid=666, pullrequestid=pull_request.pull_request_id)
243 response = api_call(self.app, params)
243 response = api_call(self.app, params)
244
244
245 expected = 'repository `666` does not exist'
245 expected = 'repository `666` does not exist'
246 assert_error(id_, expected, given=response.body)
246 assert_error(id_, expected, given=response.body)
247
247
248 @pytest.mark.backends("git", "hg")
248 @pytest.mark.backends("git", "hg")
249 def test_api_merge_pull_request_non_admin_with_userid_error(self, pr_util):
249 def test_api_merge_pull_request_non_admin_with_userid_error(self, pr_util):
250 pull_request = pr_util.create_pull_request(mergeable=True)
250 pull_request = pr_util.create_pull_request(mergeable=True)
251 id_, params = build_data(
251 id_, params = build_data(
252 self.apikey_regular, 'merge_pull_request',
252 self.apikey_regular, 'merge_pull_request',
253 repoid=pull_request.target_repo.repo_name,
253 repoid=pull_request.target_repo.repo_name,
254 pullrequestid=pull_request.pull_request_id,
254 pullrequestid=pull_request.pull_request_id,
255 userid=TEST_USER_ADMIN_LOGIN)
255 userid=TEST_USER_ADMIN_LOGIN)
256 response = api_call(self.app, params)
256 response = api_call(self.app, params)
257
257
258 expected = 'userid is not the same as your user'
258 expected = 'userid is not the same as your user'
259 assert_error(id_, expected, given=response.body)
259 assert_error(id_, expected, given=response.body)
@@ -1,362 +1,362 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import datetime
20 import datetime
21 import logging
21 import logging
22 import formencode
22 import formencode
23 import formencode.htmlfill
23 import formencode.htmlfill
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode import events
30 from rhodecode import events
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32
32
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, CSRFRequired, NotAnonymous,
34 LoginRequired, CSRFRequired, NotAnonymous,
35 HasPermissionAny, HasRepoGroupPermissionAny)
35 HasPermissionAny, HasRepoGroupPermissionAny)
36 from rhodecode.lib import helpers as h, audit_logger
36 from rhodecode.lib import helpers as h, audit_logger
37 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
37 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
38 from rhodecode.model.forms import RepoGroupForm
38 from rhodecode.model.forms import RepoGroupForm
39 from rhodecode.model.permission import PermissionModel
39 from rhodecode.model.permission import PermissionModel
40 from rhodecode.model.repo_group import RepoGroupModel
40 from rhodecode.model.repo_group import RepoGroupModel
41 from rhodecode.model.scm import RepoGroupList
41 from rhodecode.model.scm import RepoGroupList
42 from rhodecode.model.db import (
42 from rhodecode.model.db import (
43 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
43 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
48 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
49
49
50 def load_default_context(self):
50 def load_default_context(self):
51 c = self._get_local_tmpl_context()
51 c = self._get_local_tmpl_context()
52
52
53 return c
53 return c
54
54
55 def _load_form_data(self, c):
55 def _load_form_data(self, c):
56 allow_empty_group = False
56 allow_empty_group = False
57
57
58 if self._can_create_repo_group():
58 if self._can_create_repo_group():
59 # we're global admin, we're ok and we can create TOP level groups
59 # we're global admin, we're ok and we can create TOP level groups
60 allow_empty_group = True
60 allow_empty_group = True
61
61
62 # override the choices for this form, we need to filter choices
62 # override the choices for this form, we need to filter choices
63 # and display only those we have ADMIN right
63 # and display only those we have ADMIN right
64 groups_with_admin_rights = RepoGroupList(
64 groups_with_admin_rights = RepoGroupList(
65 RepoGroup.query().all(),
65 RepoGroup.query().all(),
66 perm_set=['group.admin'])
66 perm_set=['group.admin'])
67 c.repo_groups = RepoGroup.groups_choices(
67 c.repo_groups = RepoGroup.groups_choices(
68 groups=groups_with_admin_rights,
68 groups=groups_with_admin_rights,
69 show_empty_group=allow_empty_group)
69 show_empty_group=allow_empty_group)
70
70
71 def _can_create_repo_group(self, parent_group_id=None):
71 def _can_create_repo_group(self, parent_group_id=None):
72 is_admin = HasPermissionAny('hg.admin')('group create controller')
72 is_admin = HasPermissionAny('hg.admin')('group create controller')
73 create_repo_group = HasPermissionAny(
73 create_repo_group = HasPermissionAny(
74 'hg.repogroup.create.true')('group create controller')
74 'hg.repogroup.create.true')('group create controller')
75 if is_admin or (create_repo_group and not parent_group_id):
75 if is_admin or (create_repo_group and not parent_group_id):
76 # we're global admin, or we have global repo group create
76 # we're global admin, or we have global repo group create
77 # permission
77 # permission
78 # we're ok and we can create TOP level groups
78 # we're ok and we can create TOP level groups
79 return True
79 return True
80 elif parent_group_id:
80 elif parent_group_id:
81 # we check the permission if we can write to parent group
81 # we check the permission if we can write to parent group
82 group = RepoGroup.get(parent_group_id)
82 group = RepoGroup.get(parent_group_id)
83 group_name = group.group_name if group else None
83 group_name = group.group_name if group else None
84 if HasRepoGroupPermissionAny('group.admin')(
84 if HasRepoGroupPermissionAny('group.admin')(
85 group_name, 'check if user is an admin of group'):
85 group_name, 'check if user is an admin of group'):
86 # we're an admin of passed in group, we're ok.
86 # we're an admin of passed in group, we're ok.
87 return True
87 return True
88 else:
88 else:
89 return False
89 return False
90 return False
90 return False
91
91
92 # permission check in data loading of
92 # permission check in data loading of
93 # `repo_group_list_data` via RepoGroupList
93 # `repo_group_list_data` via RepoGroupList
94 @LoginRequired()
94 @LoginRequired()
95 @NotAnonymous()
95 @NotAnonymous()
96 @view_config(
96 @view_config(
97 route_name='repo_groups', request_method='GET',
97 route_name='repo_groups', request_method='GET',
98 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
98 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
99 def repo_group_list(self):
99 def repo_group_list(self):
100 c = self.load_default_context()
100 c = self.load_default_context()
101 return self._get_template_context(c)
101 return self._get_template_context(c)
102
102
103 # permission check inside
103 # permission check inside
104 @LoginRequired()
104 @LoginRequired()
105 @NotAnonymous()
105 @NotAnonymous()
106 @view_config(
106 @view_config(
107 route_name='repo_groups_data', request_method='GET',
107 route_name='repo_groups_data', request_method='GET',
108 renderer='json_ext', xhr=True)
108 renderer='json_ext', xhr=True)
109 def repo_group_list_data(self):
109 def repo_group_list_data(self):
110 self.load_default_context()
110 self.load_default_context()
111 column_map = {
111 column_map = {
112 'name_raw': 'group_name_hash',
112 'name_raw': 'group_name_hash',
113 'desc': 'group_description',
113 'desc': 'group_description',
114 'last_change_raw': 'updated_on',
114 'last_change_raw': 'updated_on',
115 'top_level_repos': 'repos_total',
115 'top_level_repos': 'repos_total',
116 'owner': 'user_username',
116 'owner': 'user_username',
117 }
117 }
118 draw, start, limit = self._extract_chunk(self.request)
118 draw, start, limit = self._extract_chunk(self.request)
119 search_q, order_by, order_dir = self._extract_ordering(
119 search_q, order_by, order_dir = self._extract_ordering(
120 self.request, column_map=column_map)
120 self.request, column_map=column_map)
121
121
122 _render = self.request.get_partial_renderer(
122 _render = self.request.get_partial_renderer(
123 'rhodecode:templates/data_table/_dt_elements.mako')
123 'rhodecode:templates/data_table/_dt_elements.mako')
124 c = _render.get_call_context()
124 c = _render.get_call_context()
125
125
126 def quick_menu(repo_group_name):
126 def quick_menu(repo_group_name):
127 return _render('quick_repo_group_menu', repo_group_name)
127 return _render('quick_repo_group_menu', repo_group_name)
128
128
129 def repo_group_lnk(repo_group_name):
129 def repo_group_lnk(repo_group_name):
130 return _render('repo_group_name', repo_group_name)
130 return _render('repo_group_name', repo_group_name)
131
131
132 def last_change(last_change):
132 def last_change(last_change):
133 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
133 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
134 delta = datetime.timedelta(
134 delta = datetime.timedelta(
135 seconds=(datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
135 seconds=(datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
136 last_change = last_change + delta
136 last_change = last_change + delta
137 return _render("last_change", last_change)
137 return _render("last_change", last_change)
138
138
139 def desc(desc, personal):
139 def desc(desc, personal):
140 return _render(
140 return _render(
141 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
141 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
142
142
143 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
143 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
144 return _render(
144 return _render(
145 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
145 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
146
146
147 def user_profile(username):
147 def user_profile(username):
148 return _render('user_profile', username)
148 return _render('user_profile', username)
149
149
150 auth_repo_group_list = RepoGroupList(
150 auth_repo_group_list = RepoGroupList(
151 RepoGroup.query().all(), perm_set=['group.admin'])
151 RepoGroup.query().all(), perm_set=['group.admin'])
152
152
153 allowed_ids = [-1]
153 allowed_ids = [-1]
154 for repo_group in auth_repo_group_list:
154 for repo_group in auth_repo_group_list:
155 allowed_ids.append(repo_group.group_id)
155 allowed_ids.append(repo_group.group_id)
156
156
157 repo_groups_data_total_count = RepoGroup.query()\
157 repo_groups_data_total_count = RepoGroup.query()\
158 .filter(or_(
158 .filter(or_(
159 # generate multiple IN to fix limitation problems
159 # generate multiple IN to fix limitation problems
160 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 *in_filter_generator(RepoGroup.group_id, allowed_ids)
161 )) \
161 )) \
162 .count()
162 .count()
163
163
164 repo_groups_data_total_inactive_count = RepoGroup.query()\
164 repo_groups_data_total_inactive_count = RepoGroup.query()\
165 .filter(RepoGroup.group_id.in_(allowed_ids))\
165 .filter(RepoGroup.group_id.in_(allowed_ids))\
166 .count()
166 .count()
167
167
168 repo_count = count(Repository.repo_id)
168 repo_count = count(Repository.repo_id)
169 base_q = Session.query(
169 base_q = Session.query(
170 RepoGroup.group_name,
170 RepoGroup.group_name,
171 RepoGroup.group_name_hash,
171 RepoGroup.group_name_hash,
172 RepoGroup.group_description,
172 RepoGroup.group_description,
173 RepoGroup.group_id,
173 RepoGroup.group_id,
174 RepoGroup.personal,
174 RepoGroup.personal,
175 RepoGroup.updated_on,
175 RepoGroup.updated_on,
176 User,
176 User,
177 repo_count.label('repos_count')
177 repo_count.label('repos_count')
178 ) \
178 ) \
179 .filter(or_(
179 .filter(or_(
180 # generate multiple IN to fix limitation problems
180 # generate multiple IN to fix limitation problems
181 *in_filter_generator(RepoGroup.group_id, allowed_ids)
181 *in_filter_generator(RepoGroup.group_id, allowed_ids)
182 )) \
182 )) \
183 .outerjoin(Repository) \
183 .outerjoin(Repository, Repository.group_id == RepoGroup.group_id) \
184 .join(User, User.user_id == RepoGroup.user_id) \
184 .join(User, User.user_id == RepoGroup.user_id) \
185 .group_by(RepoGroup, User)
185 .group_by(RepoGroup, User)
186
186
187 if search_q:
187 if search_q:
188 like_expression = u'%{}%'.format(safe_unicode(search_q))
188 like_expression = u'%{}%'.format(safe_unicode(search_q))
189 base_q = base_q.filter(or_(
189 base_q = base_q.filter(or_(
190 RepoGroup.group_name.ilike(like_expression),
190 RepoGroup.group_name.ilike(like_expression),
191 ))
191 ))
192
192
193 repo_groups_data_total_filtered_count = base_q.count()
193 repo_groups_data_total_filtered_count = base_q.count()
194 # the inactive isn't really used, but we still make it same as other data grids
194 # the inactive isn't really used, but we still make it same as other data grids
195 # which use inactive (users,user groups)
195 # which use inactive (users,user groups)
196 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
196 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
197
197
198 sort_defined = False
198 sort_defined = False
199 if order_by == 'group_name':
199 if order_by == 'group_name':
200 sort_col = func.lower(RepoGroup.group_name)
200 sort_col = func.lower(RepoGroup.group_name)
201 sort_defined = True
201 sort_defined = True
202 elif order_by == 'repos_total':
202 elif order_by == 'repos_total':
203 sort_col = repo_count
203 sort_col = repo_count
204 sort_defined = True
204 sort_defined = True
205 elif order_by == 'user_username':
205 elif order_by == 'user_username':
206 sort_col = User.username
206 sort_col = User.username
207 else:
207 else:
208 sort_col = getattr(RepoGroup, order_by, None)
208 sort_col = getattr(RepoGroup, order_by, None)
209
209
210 if sort_defined or sort_col:
210 if sort_defined or sort_col:
211 if order_dir == 'asc':
211 if order_dir == 'asc':
212 sort_col = sort_col.asc()
212 sort_col = sort_col.asc()
213 else:
213 else:
214 sort_col = sort_col.desc()
214 sort_col = sort_col.desc()
215
215
216 base_q = base_q.order_by(sort_col)
216 base_q = base_q.order_by(sort_col)
217 base_q = base_q.offset(start).limit(limit)
217 base_q = base_q.offset(start).limit(limit)
218
218
219 # authenticated access to user groups
219 # authenticated access to user groups
220 auth_repo_group_list = base_q.all()
220 auth_repo_group_list = base_q.all()
221
221
222 repo_groups_data = []
222 repo_groups_data = []
223 for repo_gr in auth_repo_group_list:
223 for repo_gr in auth_repo_group_list:
224 row = {
224 row = {
225 "menu": quick_menu(repo_gr.group_name),
225 "menu": quick_menu(repo_gr.group_name),
226 "name": repo_group_lnk(repo_gr.group_name),
226 "name": repo_group_lnk(repo_gr.group_name),
227 "name_raw": repo_gr.group_name,
227 "name_raw": repo_gr.group_name,
228 "last_change": last_change(repo_gr.updated_on),
228 "last_change": last_change(repo_gr.updated_on),
229 "last_change_raw": datetime_to_time(repo_gr.updated_on),
229 "last_change_raw": datetime_to_time(repo_gr.updated_on),
230
230
231 "last_changeset": "",
231 "last_changeset": "",
232 "last_changeset_raw": "",
232 "last_changeset_raw": "",
233
233
234 "desc": desc(repo_gr.group_description, repo_gr.personal),
234 "desc": desc(repo_gr.group_description, repo_gr.personal),
235 "owner": user_profile(repo_gr.User.username),
235 "owner": user_profile(repo_gr.User.username),
236 "top_level_repos": repo_gr.repos_count,
236 "top_level_repos": repo_gr.repos_count,
237 "action": repo_group_actions(
237 "action": repo_group_actions(
238 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
238 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
239
239
240 }
240 }
241
241
242 repo_groups_data.append(row)
242 repo_groups_data.append(row)
243
243
244 data = ({
244 data = ({
245 'draw': draw,
245 'draw': draw,
246 'data': repo_groups_data,
246 'data': repo_groups_data,
247 'recordsTotal': repo_groups_data_total_count,
247 'recordsTotal': repo_groups_data_total_count,
248 'recordsTotalInactive': repo_groups_data_total_inactive_count,
248 'recordsTotalInactive': repo_groups_data_total_inactive_count,
249 'recordsFiltered': repo_groups_data_total_filtered_count,
249 'recordsFiltered': repo_groups_data_total_filtered_count,
250 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
250 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
251 })
251 })
252
252
253 return data
253 return data
254
254
255 @LoginRequired()
255 @LoginRequired()
256 @NotAnonymous()
256 @NotAnonymous()
257 # perm checks inside
257 # perm checks inside
258 @view_config(
258 @view_config(
259 route_name='repo_group_new', request_method='GET',
259 route_name='repo_group_new', request_method='GET',
260 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
260 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
261 def repo_group_new(self):
261 def repo_group_new(self):
262 c = self.load_default_context()
262 c = self.load_default_context()
263
263
264 # perm check for admin, create_group perm or admin of parent_group
264 # perm check for admin, create_group perm or admin of parent_group
265 parent_group_id = safe_int(self.request.GET.get('parent_group'))
265 parent_group_id = safe_int(self.request.GET.get('parent_group'))
266 if not self._can_create_repo_group(parent_group_id):
266 if not self._can_create_repo_group(parent_group_id):
267 raise HTTPForbidden()
267 raise HTTPForbidden()
268
268
269 self._load_form_data(c)
269 self._load_form_data(c)
270
270
271 defaults = {} # Future proof for default of repo group
271 defaults = {} # Future proof for default of repo group
272 data = render(
272 data = render(
273 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
273 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
274 self._get_template_context(c), self.request)
274 self._get_template_context(c), self.request)
275 html = formencode.htmlfill.render(
275 html = formencode.htmlfill.render(
276 data,
276 data,
277 defaults=defaults,
277 defaults=defaults,
278 encoding="UTF-8",
278 encoding="UTF-8",
279 force_defaults=False
279 force_defaults=False
280 )
280 )
281 return Response(html)
281 return Response(html)
282
282
283 @LoginRequired()
283 @LoginRequired()
284 @NotAnonymous()
284 @NotAnonymous()
285 @CSRFRequired()
285 @CSRFRequired()
286 # perm checks inside
286 # perm checks inside
287 @view_config(
287 @view_config(
288 route_name='repo_group_create', request_method='POST',
288 route_name='repo_group_create', request_method='POST',
289 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
289 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
290 def repo_group_create(self):
290 def repo_group_create(self):
291 c = self.load_default_context()
291 c = self.load_default_context()
292 _ = self.request.translate
292 _ = self.request.translate
293
293
294 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
294 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
295 can_create = self._can_create_repo_group(parent_group_id)
295 can_create = self._can_create_repo_group(parent_group_id)
296
296
297 self._load_form_data(c)
297 self._load_form_data(c)
298 # permissions for can create group based on parent_id are checked
298 # permissions for can create group based on parent_id are checked
299 # here in the Form
299 # here in the Form
300 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
300 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
301 repo_group_form = RepoGroupForm(
301 repo_group_form = RepoGroupForm(
302 self.request.translate, available_groups=available_groups,
302 self.request.translate, available_groups=available_groups,
303 can_create_in_root=can_create)()
303 can_create_in_root=can_create)()
304
304
305 repo_group_name = self.request.POST.get('group_name')
305 repo_group_name = self.request.POST.get('group_name')
306 try:
306 try:
307 owner = self._rhodecode_user
307 owner = self._rhodecode_user
308 form_result = repo_group_form.to_python(dict(self.request.POST))
308 form_result = repo_group_form.to_python(dict(self.request.POST))
309 copy_permissions = form_result.get('group_copy_permissions')
309 copy_permissions = form_result.get('group_copy_permissions')
310 repo_group = RepoGroupModel().create(
310 repo_group = RepoGroupModel().create(
311 group_name=form_result['group_name_full'],
311 group_name=form_result['group_name_full'],
312 group_description=form_result['group_description'],
312 group_description=form_result['group_description'],
313 owner=owner.user_id,
313 owner=owner.user_id,
314 copy_permissions=form_result['group_copy_permissions']
314 copy_permissions=form_result['group_copy_permissions']
315 )
315 )
316 Session().flush()
316 Session().flush()
317
317
318 repo_group_data = repo_group.get_api_data()
318 repo_group_data = repo_group.get_api_data()
319 audit_logger.store_web(
319 audit_logger.store_web(
320 'repo_group.create', action_data={'data': repo_group_data},
320 'repo_group.create', action_data={'data': repo_group_data},
321 user=self._rhodecode_user)
321 user=self._rhodecode_user)
322
322
323 Session().commit()
323 Session().commit()
324
324
325 _new_group_name = form_result['group_name_full']
325 _new_group_name = form_result['group_name_full']
326
326
327 repo_group_url = h.link_to(
327 repo_group_url = h.link_to(
328 _new_group_name,
328 _new_group_name,
329 h.route_path('repo_group_home', repo_group_name=_new_group_name))
329 h.route_path('repo_group_home', repo_group_name=_new_group_name))
330 h.flash(h.literal(_('Created repository group %s')
330 h.flash(h.literal(_('Created repository group %s')
331 % repo_group_url), category='success')
331 % repo_group_url), category='success')
332
332
333 except formencode.Invalid as errors:
333 except formencode.Invalid as errors:
334 data = render(
334 data = render(
335 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
335 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
336 self._get_template_context(c), self.request)
336 self._get_template_context(c), self.request)
337 html = formencode.htmlfill.render(
337 html = formencode.htmlfill.render(
338 data,
338 data,
339 defaults=errors.value,
339 defaults=errors.value,
340 errors=errors.error_dict or {},
340 errors=errors.error_dict or {},
341 prefix_error=False,
341 prefix_error=False,
342 encoding="UTF-8",
342 encoding="UTF-8",
343 force_defaults=False
343 force_defaults=False
344 )
344 )
345 return Response(html)
345 return Response(html)
346 except Exception:
346 except Exception:
347 log.exception("Exception during creation of repository group")
347 log.exception("Exception during creation of repository group")
348 h.flash(_('Error occurred during creation of repository group %s')
348 h.flash(_('Error occurred during creation of repository group %s')
349 % repo_group_name, category='error')
349 % repo_group_name, category='error')
350 raise HTTPFound(h.route_path('home'))
350 raise HTTPFound(h.route_path('home'))
351
351
352 affected_user_ids = [self._rhodecode_user.user_id]
352 affected_user_ids = [self._rhodecode_user.user_id]
353 if copy_permissions:
353 if copy_permissions:
354 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
354 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
355 copy_perms = [perm['user_id'] for perm in user_group_perms]
355 copy_perms = [perm['user_id'] for perm in user_group_perms]
356 # also include those newly created by copy
356 # also include those newly created by copy
357 affected_user_ids.extend(copy_perms)
357 affected_user_ids.extend(copy_perms)
358 PermissionModel().trigger_permission_flush(affected_user_ids)
358 PermissionModel().trigger_permission_flush(affected_user_ids)
359
359
360 raise HTTPFound(
360 raise HTTPFound(
361 h.route_path('repo_group_home',
361 h.route_path('repo_group_home',
362 repo_group_name=form_result['group_name_full']))
362 repo_group_name=form_result['group_name_full']))
@@ -1,273 +1,273 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.response import Response
28 from pyramid.response import Response
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
34 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
35 from rhodecode.lib import helpers as h, audit_logger
35 from rhodecode.lib import helpers as h, audit_logger
36 from rhodecode.lib.utils2 import safe_unicode
36 from rhodecode.lib.utils2 import safe_unicode
37
37
38 from rhodecode.model.forms import UserGroupForm
38 from rhodecode.model.forms import UserGroupForm
39 from rhodecode.model.permission import PermissionModel
39 from rhodecode.model.permission import PermissionModel
40 from rhodecode.model.scm import UserGroupList
40 from rhodecode.model.scm import UserGroupList
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 or_, count, User, UserGroup, UserGroupMember, in_filter_generator)
42 or_, count, User, UserGroup, UserGroupMember, in_filter_generator)
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.model.user_group import UserGroupModel
44 from rhodecode.model.user_group import UserGroupModel
45 from rhodecode.model.db import true
45 from rhodecode.model.db import true
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class AdminUserGroupsView(BaseAppView, DataGridAppView):
50 class AdminUserGroupsView(BaseAppView, DataGridAppView):
51
51
52 def load_default_context(self):
52 def load_default_context(self):
53 c = self._get_local_tmpl_context()
53 c = self._get_local_tmpl_context()
54
54
55 PermissionModel().set_global_permission_choices(
55 PermissionModel().set_global_permission_choices(
56 c, gettext_translator=self.request.translate)
56 c, gettext_translator=self.request.translate)
57
57
58 return c
58 return c
59
59
60 # permission check in data loading of
60 # permission check in data loading of
61 # `user_groups_list_data` via UserGroupList
61 # `user_groups_list_data` via UserGroupList
62 @LoginRequired()
62 @LoginRequired()
63 @NotAnonymous()
63 @NotAnonymous()
64 @view_config(
64 @view_config(
65 route_name='user_groups', request_method='GET',
65 route_name='user_groups', request_method='GET',
66 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
66 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
67 def user_groups_list(self):
67 def user_groups_list(self):
68 c = self.load_default_context()
68 c = self.load_default_context()
69 return self._get_template_context(c)
69 return self._get_template_context(c)
70
70
71 # permission check inside
71 # permission check inside
72 @LoginRequired()
72 @LoginRequired()
73 @NotAnonymous()
73 @NotAnonymous()
74 @view_config(
74 @view_config(
75 route_name='user_groups_data', request_method='GET',
75 route_name='user_groups_data', request_method='GET',
76 renderer='json_ext', xhr=True)
76 renderer='json_ext', xhr=True)
77 def user_groups_list_data(self):
77 def user_groups_list_data(self):
78 self.load_default_context()
78 self.load_default_context()
79 column_map = {
79 column_map = {
80 'active': 'users_group_active',
80 'active': 'users_group_active',
81 'description': 'user_group_description',
81 'description': 'user_group_description',
82 'members': 'members_total',
82 'members': 'members_total',
83 'owner': 'user_username',
83 'owner': 'user_username',
84 'sync': 'group_data'
84 'sync': 'group_data'
85 }
85 }
86 draw, start, limit = self._extract_chunk(self.request)
86 draw, start, limit = self._extract_chunk(self.request)
87 search_q, order_by, order_dir = self._extract_ordering(
87 search_q, order_by, order_dir = self._extract_ordering(
88 self.request, column_map=column_map)
88 self.request, column_map=column_map)
89
89
90 _render = self.request.get_partial_renderer(
90 _render = self.request.get_partial_renderer(
91 'rhodecode:templates/data_table/_dt_elements.mako')
91 'rhodecode:templates/data_table/_dt_elements.mako')
92
92
93 def user_group_name(user_group_name):
93 def user_group_name(user_group_name):
94 return _render("user_group_name", user_group_name)
94 return _render("user_group_name", user_group_name)
95
95
96 def user_group_actions(user_group_id, user_group_name):
96 def user_group_actions(user_group_id, user_group_name):
97 return _render("user_group_actions", user_group_id, user_group_name)
97 return _render("user_group_actions", user_group_id, user_group_name)
98
98
99 def user_profile(username):
99 def user_profile(username):
100 return _render('user_profile', username)
100 return _render('user_profile', username)
101
101
102 auth_user_group_list = UserGroupList(
102 auth_user_group_list = UserGroupList(
103 UserGroup.query().all(), perm_set=['usergroup.admin'])
103 UserGroup.query().all(), perm_set=['usergroup.admin'])
104
104
105 allowed_ids = [-1]
105 allowed_ids = [-1]
106 for user_group in auth_user_group_list:
106 for user_group in auth_user_group_list:
107 allowed_ids.append(user_group.users_group_id)
107 allowed_ids.append(user_group.users_group_id)
108
108
109 user_groups_data_total_count = UserGroup.query()\
109 user_groups_data_total_count = UserGroup.query()\
110 .filter(or_(
110 .filter(or_(
111 # generate multiple IN to fix limitation problems
111 # generate multiple IN to fix limitation problems
112 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
112 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
113 ))\
113 ))\
114 .count()
114 .count()
115
115
116 user_groups_data_total_inactive_count = UserGroup.query()\
116 user_groups_data_total_inactive_count = UserGroup.query()\
117 .filter(or_(
117 .filter(or_(
118 # generate multiple IN to fix limitation problems
118 # generate multiple IN to fix limitation problems
119 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
119 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
120 ))\
120 ))\
121 .filter(UserGroup.users_group_active != true()).count()
121 .filter(UserGroup.users_group_active != true()).count()
122
122
123 member_count = count(UserGroupMember.user_id)
123 member_count = count(UserGroupMember.user_id)
124 base_q = Session.query(
124 base_q = Session.query(
125 UserGroup.users_group_name,
125 UserGroup.users_group_name,
126 UserGroup.user_group_description,
126 UserGroup.user_group_description,
127 UserGroup.users_group_active,
127 UserGroup.users_group_active,
128 UserGroup.users_group_id,
128 UserGroup.users_group_id,
129 UserGroup.group_data,
129 UserGroup.group_data,
130 User,
130 User,
131 member_count.label('member_count')
131 member_count.label('member_count')
132 ) \
132 ) \
133 .filter(or_(
133 .filter(or_(
134 # generate multiple IN to fix limitation problems
134 # generate multiple IN to fix limitation problems
135 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
135 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
136 )) \
136 )) \
137 .outerjoin(UserGroupMember) \
137 .outerjoin(UserGroupMember, UserGroupMember.users_group_id == UserGroup.users_group_id) \
138 .join(User, User.user_id == UserGroup.user_id) \
138 .join(User, User.user_id == UserGroup.user_id) \
139 .group_by(UserGroup, User)
139 .group_by(UserGroup, User)
140
140
141 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
141 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
142
142
143 if search_q:
143 if search_q:
144 like_expression = u'%{}%'.format(safe_unicode(search_q))
144 like_expression = u'%{}%'.format(safe_unicode(search_q))
145 base_q = base_q.filter(or_(
145 base_q = base_q.filter(or_(
146 UserGroup.users_group_name.ilike(like_expression),
146 UserGroup.users_group_name.ilike(like_expression),
147 ))
147 ))
148 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
148 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
149
149
150 user_groups_data_total_filtered_count = base_q.count()
150 user_groups_data_total_filtered_count = base_q.count()
151 user_groups_data_total_filtered_inactive_count = base_q_inactive.count()
151 user_groups_data_total_filtered_inactive_count = base_q_inactive.count()
152
152
153 sort_defined = False
153 sort_defined = False
154 if order_by == 'members_total':
154 if order_by == 'members_total':
155 sort_col = member_count
155 sort_col = member_count
156 sort_defined = True
156 sort_defined = True
157 elif order_by == 'user_username':
157 elif order_by == 'user_username':
158 sort_col = User.username
158 sort_col = User.username
159 else:
159 else:
160 sort_col = getattr(UserGroup, order_by, None)
160 sort_col = getattr(UserGroup, order_by, None)
161
161
162 if sort_defined or sort_col:
162 if sort_defined or sort_col:
163 if order_dir == 'asc':
163 if order_dir == 'asc':
164 sort_col = sort_col.asc()
164 sort_col = sort_col.asc()
165 else:
165 else:
166 sort_col = sort_col.desc()
166 sort_col = sort_col.desc()
167
167
168 base_q = base_q.order_by(sort_col)
168 base_q = base_q.order_by(sort_col)
169 base_q = base_q.offset(start).limit(limit)
169 base_q = base_q.offset(start).limit(limit)
170
170
171 # authenticated access to user groups
171 # authenticated access to user groups
172 auth_user_group_list = base_q.all()
172 auth_user_group_list = base_q.all()
173
173
174 user_groups_data = []
174 user_groups_data = []
175 for user_gr in auth_user_group_list:
175 for user_gr in auth_user_group_list:
176 row = {
176 row = {
177 "users_group_name": user_group_name(user_gr.users_group_name),
177 "users_group_name": user_group_name(user_gr.users_group_name),
178 "name_raw": h.escape(user_gr.users_group_name),
178 "name_raw": h.escape(user_gr.users_group_name),
179 "description": h.escape(user_gr.user_group_description),
179 "description": h.escape(user_gr.user_group_description),
180 "members": user_gr.member_count,
180 "members": user_gr.member_count,
181 # NOTE(marcink): because of advanced query we
181 # NOTE(marcink): because of advanced query we
182 # need to load it like that
182 # need to load it like that
183 "sync": UserGroup._load_sync(
183 "sync": UserGroup._load_sync(
184 UserGroup._load_group_data(user_gr.group_data)),
184 UserGroup._load_group_data(user_gr.group_data)),
185 "active": h.bool2icon(user_gr.users_group_active),
185 "active": h.bool2icon(user_gr.users_group_active),
186 "owner": user_profile(user_gr.User.username),
186 "owner": user_profile(user_gr.User.username),
187 "action": user_group_actions(
187 "action": user_group_actions(
188 user_gr.users_group_id, user_gr.users_group_name)
188 user_gr.users_group_id, user_gr.users_group_name)
189 }
189 }
190 user_groups_data.append(row)
190 user_groups_data.append(row)
191
191
192 data = ({
192 data = ({
193 'draw': draw,
193 'draw': draw,
194 'data': user_groups_data,
194 'data': user_groups_data,
195 'recordsTotal': user_groups_data_total_count,
195 'recordsTotal': user_groups_data_total_count,
196 'recordsTotalInactive': user_groups_data_total_inactive_count,
196 'recordsTotalInactive': user_groups_data_total_inactive_count,
197 'recordsFiltered': user_groups_data_total_filtered_count,
197 'recordsFiltered': user_groups_data_total_filtered_count,
198 'recordsFilteredInactive': user_groups_data_total_filtered_inactive_count,
198 'recordsFilteredInactive': user_groups_data_total_filtered_inactive_count,
199 })
199 })
200
200
201 return data
201 return data
202
202
203 @LoginRequired()
203 @LoginRequired()
204 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
204 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
205 @view_config(
205 @view_config(
206 route_name='user_groups_new', request_method='GET',
206 route_name='user_groups_new', request_method='GET',
207 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
207 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
208 def user_groups_new(self):
208 def user_groups_new(self):
209 c = self.load_default_context()
209 c = self.load_default_context()
210 return self._get_template_context(c)
210 return self._get_template_context(c)
211
211
212 @LoginRequired()
212 @LoginRequired()
213 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
213 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
214 @CSRFRequired()
214 @CSRFRequired()
215 @view_config(
215 @view_config(
216 route_name='user_groups_create', request_method='POST',
216 route_name='user_groups_create', request_method='POST',
217 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
217 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
218 def user_groups_create(self):
218 def user_groups_create(self):
219 _ = self.request.translate
219 _ = self.request.translate
220 c = self.load_default_context()
220 c = self.load_default_context()
221 users_group_form = UserGroupForm(self.request.translate)()
221 users_group_form = UserGroupForm(self.request.translate)()
222
222
223 user_group_name = self.request.POST.get('users_group_name')
223 user_group_name = self.request.POST.get('users_group_name')
224 try:
224 try:
225 form_result = users_group_form.to_python(dict(self.request.POST))
225 form_result = users_group_form.to_python(dict(self.request.POST))
226 user_group = UserGroupModel().create(
226 user_group = UserGroupModel().create(
227 name=form_result['users_group_name'],
227 name=form_result['users_group_name'],
228 description=form_result['user_group_description'],
228 description=form_result['user_group_description'],
229 owner=self._rhodecode_user.user_id,
229 owner=self._rhodecode_user.user_id,
230 active=form_result['users_group_active'])
230 active=form_result['users_group_active'])
231 Session().flush()
231 Session().flush()
232 creation_data = user_group.get_api_data()
232 creation_data = user_group.get_api_data()
233 user_group_name = form_result['users_group_name']
233 user_group_name = form_result['users_group_name']
234
234
235 audit_logger.store_web(
235 audit_logger.store_web(
236 'user_group.create', action_data={'data': creation_data},
236 'user_group.create', action_data={'data': creation_data},
237 user=self._rhodecode_user)
237 user=self._rhodecode_user)
238
238
239 user_group_link = h.link_to(
239 user_group_link = h.link_to(
240 h.escape(user_group_name),
240 h.escape(user_group_name),
241 h.route_path(
241 h.route_path(
242 'edit_user_group', user_group_id=user_group.users_group_id))
242 'edit_user_group', user_group_id=user_group.users_group_id))
243 h.flash(h.literal(_('Created user group %(user_group_link)s')
243 h.flash(h.literal(_('Created user group %(user_group_link)s')
244 % {'user_group_link': user_group_link}),
244 % {'user_group_link': user_group_link}),
245 category='success')
245 category='success')
246 Session().commit()
246 Session().commit()
247 user_group_id = user_group.users_group_id
247 user_group_id = user_group.users_group_id
248 except formencode.Invalid as errors:
248 except formencode.Invalid as errors:
249
249
250 data = render(
250 data = render(
251 'rhodecode:templates/admin/user_groups/user_group_add.mako',
251 'rhodecode:templates/admin/user_groups/user_group_add.mako',
252 self._get_template_context(c), self.request)
252 self._get_template_context(c), self.request)
253 html = formencode.htmlfill.render(
253 html = formencode.htmlfill.render(
254 data,
254 data,
255 defaults=errors.value,
255 defaults=errors.value,
256 errors=errors.error_dict or {},
256 errors=errors.error_dict or {},
257 prefix_error=False,
257 prefix_error=False,
258 encoding="UTF-8",
258 encoding="UTF-8",
259 force_defaults=False
259 force_defaults=False
260 )
260 )
261 return Response(html)
261 return Response(html)
262
262
263 except Exception:
263 except Exception:
264 log.exception("Exception creating user group")
264 log.exception("Exception creating user group")
265 h.flash(_('Error occurred during creation of user group %s') \
265 h.flash(_('Error occurred during creation of user group %s') \
266 % user_group_name, category='error')
266 % user_group_name, category='error')
267 raise HTTPFound(h.route_path('user_groups_new'))
267 raise HTTPFound(h.route_path('user_groups_new'))
268
268
269 affected_user_ids = [self._rhodecode_user.user_id]
269 affected_user_ids = [self._rhodecode_user.user_id]
270 PermissionModel().trigger_permission_flush(affected_user_ids)
270 PermissionModel().trigger_permission_flush(affected_user_ids)
271
271
272 raise HTTPFound(
272 raise HTTPFound(
273 h.route_path('edit_user_group', user_group_id=user_group_id))
273 h.route_path('edit_user_group', user_group_id=user_group_id))
@@ -1,1221 +1,1221 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 import rhodecode
23 import rhodecode
24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
25 from rhodecode.lib.vcs.nodes import FileNode
25 from rhodecode.lib.vcs.nodes import FileNode
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.model.changeset_status import ChangesetStatusModel
27 from rhodecode.model.changeset_status import ChangesetStatusModel
28 from rhodecode.model.db import (
28 from rhodecode.model.db import (
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment, Repository)
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment, Repository)
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31 from rhodecode.model.pull_request import PullRequestModel
31 from rhodecode.model.pull_request import PullRequestModel
32 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
33 from rhodecode.tests import (
33 from rhodecode.tests import (
34 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
34 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
35
35
36
36
37 def route_path(name, params=None, **kwargs):
37 def route_path(name, params=None, **kwargs):
38 import urllib
38 import urllib
39
39
40 base_url = {
40 base_url = {
41 'repo_changelog': '/{repo_name}/changelog',
41 'repo_changelog': '/{repo_name}/changelog',
42 'repo_changelog_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
42 'repo_changelog_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
43 'repo_commits': '/{repo_name}/commits',
43 'repo_commits': '/{repo_name}/commits',
44 'repo_commits_file': '/{repo_name}/commits/{commit_id}/{f_path}',
44 'repo_commits_file': '/{repo_name}/commits/{commit_id}/{f_path}',
45 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
45 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
46 'pullrequest_show_all': '/{repo_name}/pull-request',
46 'pullrequest_show_all': '/{repo_name}/pull-request',
47 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
47 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
48 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
48 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
49 'pullrequest_repo_targets': '/{repo_name}/pull-request/repo-destinations',
49 'pullrequest_repo_targets': '/{repo_name}/pull-request/repo-destinations',
50 'pullrequest_new': '/{repo_name}/pull-request/new',
50 'pullrequest_new': '/{repo_name}/pull-request/new',
51 'pullrequest_create': '/{repo_name}/pull-request/create',
51 'pullrequest_create': '/{repo_name}/pull-request/create',
52 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
52 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
53 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
53 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
54 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
54 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
55 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
55 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
56 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
56 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
57 }[name].format(**kwargs)
57 }[name].format(**kwargs)
58
58
59 if params:
59 if params:
60 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
60 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
61 return base_url
61 return base_url
62
62
63
63
64 @pytest.mark.usefixtures('app', 'autologin_user')
64 @pytest.mark.usefixtures('app', 'autologin_user')
65 @pytest.mark.backends("git", "hg")
65 @pytest.mark.backends("git", "hg")
66 class TestPullrequestsView(object):
66 class TestPullrequestsView(object):
67
67
68 def test_index(self, backend):
68 def test_index(self, backend):
69 self.app.get(route_path(
69 self.app.get(route_path(
70 'pullrequest_new',
70 'pullrequest_new',
71 repo_name=backend.repo_name))
71 repo_name=backend.repo_name))
72
72
73 def test_option_menu_create_pull_request_exists(self, backend):
73 def test_option_menu_create_pull_request_exists(self, backend):
74 repo_name = backend.repo_name
74 repo_name = backend.repo_name
75 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
75 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
76
76
77 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
77 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
78 'pullrequest_new', repo_name=repo_name)
78 'pullrequest_new', repo_name=repo_name)
79 response.mustcontain(create_pr_link)
79 response.mustcontain(create_pr_link)
80
80
81 def test_create_pr_form_with_raw_commit_id(self, backend):
81 def test_create_pr_form_with_raw_commit_id(self, backend):
82 repo = backend.repo
82 repo = backend.repo
83
83
84 self.app.get(
84 self.app.get(
85 route_path('pullrequest_new', repo_name=repo.repo_name,
85 route_path('pullrequest_new', repo_name=repo.repo_name,
86 commit=repo.get_commit().raw_id),
86 commit=repo.get_commit().raw_id),
87 status=200)
87 status=200)
88
88
89 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
89 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
90 @pytest.mark.parametrize('range_diff', ["0", "1"])
90 @pytest.mark.parametrize('range_diff', ["0", "1"])
91 def test_show(self, pr_util, pr_merge_enabled, range_diff):
91 def test_show(self, pr_util, pr_merge_enabled, range_diff):
92 pull_request = pr_util.create_pull_request(
92 pull_request = pr_util.create_pull_request(
93 mergeable=pr_merge_enabled, enable_notifications=False)
93 mergeable=pr_merge_enabled, enable_notifications=False)
94
94
95 response = self.app.get(route_path(
95 response = self.app.get(route_path(
96 'pullrequest_show',
96 'pullrequest_show',
97 repo_name=pull_request.target_repo.scm_instance().name,
97 repo_name=pull_request.target_repo.scm_instance().name,
98 pull_request_id=pull_request.pull_request_id,
98 pull_request_id=pull_request.pull_request_id,
99 params={'range-diff': range_diff}))
99 params={'range-diff': range_diff}))
100
100
101 for commit_id in pull_request.revisions:
101 for commit_id in pull_request.revisions:
102 response.mustcontain(commit_id)
102 response.mustcontain(commit_id)
103
103
104 assert pull_request.target_ref_parts.type in response
104 assert pull_request.target_ref_parts.type in response
105 assert pull_request.target_ref_parts.name in response
105 assert pull_request.target_ref_parts.name in response
106 target_clone_url = pull_request.target_repo.clone_url()
106 target_clone_url = pull_request.target_repo.clone_url()
107 assert target_clone_url in response
107 assert target_clone_url in response
108
108
109 assert 'class="pull-request-merge"' in response
109 assert 'class="pull-request-merge"' in response
110 if pr_merge_enabled:
110 if pr_merge_enabled:
111 response.mustcontain('Pull request reviewer approval is pending')
111 response.mustcontain('Pull request reviewer approval is pending')
112 else:
112 else:
113 response.mustcontain('Server-side pull request merging is disabled.')
113 response.mustcontain('Server-side pull request merging is disabled.')
114
114
115 if range_diff == "1":
115 if range_diff == "1":
116 response.mustcontain('Turn off: Show the diff as commit range')
116 response.mustcontain('Turn off: Show the diff as commit range')
117
117
118 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
118 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
119 # Logout
119 # Logout
120 response = self.app.post(
120 response = self.app.post(
121 h.route_path('logout'),
121 h.route_path('logout'),
122 params={'csrf_token': csrf_token})
122 params={'csrf_token': csrf_token})
123 # Login as regular user
123 # Login as regular user
124 response = self.app.post(h.route_path('login'),
124 response = self.app.post(h.route_path('login'),
125 {'username': TEST_USER_REGULAR_LOGIN,
125 {'username': TEST_USER_REGULAR_LOGIN,
126 'password': 'test12'})
126 'password': 'test12'})
127
127
128 pull_request = pr_util.create_pull_request(
128 pull_request = pr_util.create_pull_request(
129 author=TEST_USER_REGULAR_LOGIN)
129 author=TEST_USER_REGULAR_LOGIN)
130
130
131 response = self.app.get(route_path(
131 response = self.app.get(route_path(
132 'pullrequest_show',
132 'pullrequest_show',
133 repo_name=pull_request.target_repo.scm_instance().name,
133 repo_name=pull_request.target_repo.scm_instance().name,
134 pull_request_id=pull_request.pull_request_id))
134 pull_request_id=pull_request.pull_request_id))
135
135
136 response.mustcontain('Server-side pull request merging is disabled.')
136 response.mustcontain('Server-side pull request merging is disabled.')
137
137
138 assert_response = response.assert_response()
138 assert_response = response.assert_response()
139 # for regular user without a merge permissions, we don't see it
139 # for regular user without a merge permissions, we don't see it
140 assert_response.no_element_exists('#close-pull-request-action')
140 assert_response.no_element_exists('#close-pull-request-action')
141
141
142 user_util.grant_user_permission_to_repo(
142 user_util.grant_user_permission_to_repo(
143 pull_request.target_repo,
143 pull_request.target_repo,
144 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
144 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
145 'repository.write')
145 'repository.write')
146 response = self.app.get(route_path(
146 response = self.app.get(route_path(
147 'pullrequest_show',
147 'pullrequest_show',
148 repo_name=pull_request.target_repo.scm_instance().name,
148 repo_name=pull_request.target_repo.scm_instance().name,
149 pull_request_id=pull_request.pull_request_id))
149 pull_request_id=pull_request.pull_request_id))
150
150
151 response.mustcontain('Server-side pull request merging is disabled.')
151 response.mustcontain('Server-side pull request merging is disabled.')
152
152
153 assert_response = response.assert_response()
153 assert_response = response.assert_response()
154 # now regular user has a merge permissions, we have CLOSE button
154 # now regular user has a merge permissions, we have CLOSE button
155 assert_response.one_element_exists('#close-pull-request-action')
155 assert_response.one_element_exists('#close-pull-request-action')
156
156
157 def test_show_invalid_commit_id(self, pr_util):
157 def test_show_invalid_commit_id(self, pr_util):
158 # Simulating invalid revisions which will cause a lookup error
158 # Simulating invalid revisions which will cause a lookup error
159 pull_request = pr_util.create_pull_request()
159 pull_request = pr_util.create_pull_request()
160 pull_request.revisions = ['invalid']
160 pull_request.revisions = ['invalid']
161 Session().add(pull_request)
161 Session().add(pull_request)
162 Session().commit()
162 Session().commit()
163
163
164 response = self.app.get(route_path(
164 response = self.app.get(route_path(
165 'pullrequest_show',
165 'pullrequest_show',
166 repo_name=pull_request.target_repo.scm_instance().name,
166 repo_name=pull_request.target_repo.scm_instance().name,
167 pull_request_id=pull_request.pull_request_id))
167 pull_request_id=pull_request.pull_request_id))
168
168
169 for commit_id in pull_request.revisions:
169 for commit_id in pull_request.revisions:
170 response.mustcontain(commit_id)
170 response.mustcontain(commit_id)
171
171
172 def test_show_invalid_source_reference(self, pr_util):
172 def test_show_invalid_source_reference(self, pr_util):
173 pull_request = pr_util.create_pull_request()
173 pull_request = pr_util.create_pull_request()
174 pull_request.source_ref = 'branch:b:invalid'
174 pull_request.source_ref = 'branch:b:invalid'
175 Session().add(pull_request)
175 Session().add(pull_request)
176 Session().commit()
176 Session().commit()
177
177
178 self.app.get(route_path(
178 self.app.get(route_path(
179 'pullrequest_show',
179 'pullrequest_show',
180 repo_name=pull_request.target_repo.scm_instance().name,
180 repo_name=pull_request.target_repo.scm_instance().name,
181 pull_request_id=pull_request.pull_request_id))
181 pull_request_id=pull_request.pull_request_id))
182
182
183 def test_edit_title_description(self, pr_util, csrf_token):
183 def test_edit_title_description(self, pr_util, csrf_token):
184 pull_request = pr_util.create_pull_request()
184 pull_request = pr_util.create_pull_request()
185 pull_request_id = pull_request.pull_request_id
185 pull_request_id = pull_request.pull_request_id
186
186
187 response = self.app.post(
187 response = self.app.post(
188 route_path('pullrequest_update',
188 route_path('pullrequest_update',
189 repo_name=pull_request.target_repo.repo_name,
189 repo_name=pull_request.target_repo.repo_name,
190 pull_request_id=pull_request_id),
190 pull_request_id=pull_request_id),
191 params={
191 params={
192 'edit_pull_request': 'true',
192 'edit_pull_request': 'true',
193 'title': 'New title',
193 'title': 'New title',
194 'description': 'New description',
194 'description': 'New description',
195 'csrf_token': csrf_token})
195 'csrf_token': csrf_token})
196
196
197 assert_session_flash(
197 assert_session_flash(
198 response, u'Pull request title & description updated.',
198 response, u'Pull request title & description updated.',
199 category='success')
199 category='success')
200
200
201 pull_request = PullRequest.get(pull_request_id)
201 pull_request = PullRequest.get(pull_request_id)
202 assert pull_request.title == 'New title'
202 assert pull_request.title == 'New title'
203 assert pull_request.description == 'New description'
203 assert pull_request.description == 'New description'
204
204
205 def test_edit_title_description_closed(self, pr_util, csrf_token):
205 def test_edit_title_description_closed(self, pr_util, csrf_token):
206 pull_request = pr_util.create_pull_request()
206 pull_request = pr_util.create_pull_request()
207 pull_request_id = pull_request.pull_request_id
207 pull_request_id = pull_request.pull_request_id
208 repo_name = pull_request.target_repo.repo_name
208 repo_name = pull_request.target_repo.repo_name
209 pr_util.close()
209 pr_util.close()
210
210
211 response = self.app.post(
211 response = self.app.post(
212 route_path('pullrequest_update',
212 route_path('pullrequest_update',
213 repo_name=repo_name, pull_request_id=pull_request_id),
213 repo_name=repo_name, pull_request_id=pull_request_id),
214 params={
214 params={
215 'edit_pull_request': 'true',
215 'edit_pull_request': 'true',
216 'title': 'New title',
216 'title': 'New title',
217 'description': 'New description',
217 'description': 'New description',
218 'csrf_token': csrf_token}, status=200)
218 'csrf_token': csrf_token}, status=200)
219 assert_session_flash(
219 assert_session_flash(
220 response, u'Cannot update closed pull requests.',
220 response, u'Cannot update closed pull requests.',
221 category='error')
221 category='error')
222
222
223 def test_update_invalid_source_reference(self, pr_util, csrf_token):
223 def test_update_invalid_source_reference(self, pr_util, csrf_token):
224 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
224 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
225
225
226 pull_request = pr_util.create_pull_request()
226 pull_request = pr_util.create_pull_request()
227 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
227 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
228 Session().add(pull_request)
228 Session().add(pull_request)
229 Session().commit()
229 Session().commit()
230
230
231 pull_request_id = pull_request.pull_request_id
231 pull_request_id = pull_request.pull_request_id
232
232
233 response = self.app.post(
233 response = self.app.post(
234 route_path('pullrequest_update',
234 route_path('pullrequest_update',
235 repo_name=pull_request.target_repo.repo_name,
235 repo_name=pull_request.target_repo.repo_name,
236 pull_request_id=pull_request_id),
236 pull_request_id=pull_request_id),
237 params={'update_commits': 'true', 'csrf_token': csrf_token})
237 params={'update_commits': 'true', 'csrf_token': csrf_token})
238
238
239 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
239 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
240 UpdateFailureReason.MISSING_SOURCE_REF])
240 UpdateFailureReason.MISSING_SOURCE_REF])
241 assert_session_flash(response, expected_msg, category='error')
241 assert_session_flash(response, expected_msg, category='error')
242
242
243 def test_missing_target_reference(self, pr_util, csrf_token):
243 def test_missing_target_reference(self, pr_util, csrf_token):
244 from rhodecode.lib.vcs.backends.base import MergeFailureReason
244 from rhodecode.lib.vcs.backends.base import MergeFailureReason
245 pull_request = pr_util.create_pull_request(
245 pull_request = pr_util.create_pull_request(
246 approved=True, mergeable=True)
246 approved=True, mergeable=True)
247 unicode_reference = u'branch:invalid-branch:invalid-commit-id'
247 unicode_reference = u'branch:invalid-branch:invalid-commit-id'
248 pull_request.target_ref = unicode_reference
248 pull_request.target_ref = unicode_reference
249 Session().add(pull_request)
249 Session().add(pull_request)
250 Session().commit()
250 Session().commit()
251
251
252 pull_request_id = pull_request.pull_request_id
252 pull_request_id = pull_request.pull_request_id
253 pull_request_url = route_path(
253 pull_request_url = route_path(
254 'pullrequest_show',
254 'pullrequest_show',
255 repo_name=pull_request.target_repo.repo_name,
255 repo_name=pull_request.target_repo.repo_name,
256 pull_request_id=pull_request_id)
256 pull_request_id=pull_request_id)
257
257
258 response = self.app.get(pull_request_url)
258 response = self.app.get(pull_request_url)
259 target_ref_id = 'invalid-branch'
259 target_ref_id = 'invalid-branch'
260 merge_resp = MergeResponse(
260 merge_resp = MergeResponse(
261 True, True, '', MergeFailureReason.MISSING_TARGET_REF,
261 True, True, '', MergeFailureReason.MISSING_TARGET_REF,
262 metadata={'target_ref': PullRequest.unicode_to_reference(unicode_reference)})
262 metadata={'target_ref': PullRequest.unicode_to_reference(unicode_reference)})
263 response.assert_response().element_contains(
263 response.assert_response().element_contains(
264 'span[data-role="merge-message"]', merge_resp.merge_status_message)
264 'span[data-role="merge-message"]', merge_resp.merge_status_message)
265
265
266 def test_comment_and_close_pull_request_custom_message_approved(
266 def test_comment_and_close_pull_request_custom_message_approved(
267 self, pr_util, csrf_token, xhr_header):
267 self, pr_util, csrf_token, xhr_header):
268
268
269 pull_request = pr_util.create_pull_request(approved=True)
269 pull_request = pr_util.create_pull_request(approved=True)
270 pull_request_id = pull_request.pull_request_id
270 pull_request_id = pull_request.pull_request_id
271 author = pull_request.user_id
271 author = pull_request.user_id
272 repo = pull_request.target_repo.repo_id
272 repo = pull_request.target_repo.repo_id
273
273
274 self.app.post(
274 self.app.post(
275 route_path('pullrequest_comment_create',
275 route_path('pullrequest_comment_create',
276 repo_name=pull_request.target_repo.scm_instance().name,
276 repo_name=pull_request.target_repo.scm_instance().name,
277 pull_request_id=pull_request_id),
277 pull_request_id=pull_request_id),
278 params={
278 params={
279 'close_pull_request': '1',
279 'close_pull_request': '1',
280 'text': 'Closing a PR',
280 'text': 'Closing a PR',
281 'csrf_token': csrf_token},
281 'csrf_token': csrf_token},
282 extra_environ=xhr_header,)
282 extra_environ=xhr_header,)
283
283
284 journal = UserLog.query()\
284 journal = UserLog.query()\
285 .filter(UserLog.user_id == author)\
285 .filter(UserLog.user_id == author)\
286 .filter(UserLog.repository_id == repo) \
286 .filter(UserLog.repository_id == repo) \
287 .order_by('user_log_id') \
287 .order_by(UserLog.user_log_id.asc()) \
288 .all()
288 .all()
289 assert journal[-1].action == 'repo.pull_request.close'
289 assert journal[-1].action == 'repo.pull_request.close'
290
290
291 pull_request = PullRequest.get(pull_request_id)
291 pull_request = PullRequest.get(pull_request_id)
292 assert pull_request.is_closed()
292 assert pull_request.is_closed()
293
293
294 status = ChangesetStatusModel().get_status(
294 status = ChangesetStatusModel().get_status(
295 pull_request.source_repo, pull_request=pull_request)
295 pull_request.source_repo, pull_request=pull_request)
296 assert status == ChangesetStatus.STATUS_APPROVED
296 assert status == ChangesetStatus.STATUS_APPROVED
297 comments = ChangesetComment().query() \
297 comments = ChangesetComment().query() \
298 .filter(ChangesetComment.pull_request == pull_request) \
298 .filter(ChangesetComment.pull_request == pull_request) \
299 .order_by(ChangesetComment.comment_id.asc())\
299 .order_by(ChangesetComment.comment_id.asc())\
300 .all()
300 .all()
301 assert comments[-1].text == 'Closing a PR'
301 assert comments[-1].text == 'Closing a PR'
302
302
303 def test_comment_force_close_pull_request_rejected(
303 def test_comment_force_close_pull_request_rejected(
304 self, pr_util, csrf_token, xhr_header):
304 self, pr_util, csrf_token, xhr_header):
305 pull_request = pr_util.create_pull_request()
305 pull_request = pr_util.create_pull_request()
306 pull_request_id = pull_request.pull_request_id
306 pull_request_id = pull_request.pull_request_id
307 PullRequestModel().update_reviewers(
307 PullRequestModel().update_reviewers(
308 pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
308 pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
309 pull_request.author)
309 pull_request.author)
310 author = pull_request.user_id
310 author = pull_request.user_id
311 repo = pull_request.target_repo.repo_id
311 repo = pull_request.target_repo.repo_id
312
312
313 self.app.post(
313 self.app.post(
314 route_path('pullrequest_comment_create',
314 route_path('pullrequest_comment_create',
315 repo_name=pull_request.target_repo.scm_instance().name,
315 repo_name=pull_request.target_repo.scm_instance().name,
316 pull_request_id=pull_request_id),
316 pull_request_id=pull_request_id),
317 params={
317 params={
318 'close_pull_request': '1',
318 'close_pull_request': '1',
319 'csrf_token': csrf_token},
319 'csrf_token': csrf_token},
320 extra_environ=xhr_header)
320 extra_environ=xhr_header)
321
321
322 pull_request = PullRequest.get(pull_request_id)
322 pull_request = PullRequest.get(pull_request_id)
323
323
324 journal = UserLog.query()\
324 journal = UserLog.query()\
325 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
325 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
326 .order_by('user_log_id') \
326 .order_by(UserLog.user_log_id.asc()) \
327 .all()
327 .all()
328 assert journal[-1].action == 'repo.pull_request.close'
328 assert journal[-1].action == 'repo.pull_request.close'
329
329
330 # check only the latest status, not the review status
330 # check only the latest status, not the review status
331 status = ChangesetStatusModel().get_status(
331 status = ChangesetStatusModel().get_status(
332 pull_request.source_repo, pull_request=pull_request)
332 pull_request.source_repo, pull_request=pull_request)
333 assert status == ChangesetStatus.STATUS_REJECTED
333 assert status == ChangesetStatus.STATUS_REJECTED
334
334
335 def test_comment_and_close_pull_request(
335 def test_comment_and_close_pull_request(
336 self, pr_util, csrf_token, xhr_header):
336 self, pr_util, csrf_token, xhr_header):
337 pull_request = pr_util.create_pull_request()
337 pull_request = pr_util.create_pull_request()
338 pull_request_id = pull_request.pull_request_id
338 pull_request_id = pull_request.pull_request_id
339
339
340 response = self.app.post(
340 response = self.app.post(
341 route_path('pullrequest_comment_create',
341 route_path('pullrequest_comment_create',
342 repo_name=pull_request.target_repo.scm_instance().name,
342 repo_name=pull_request.target_repo.scm_instance().name,
343 pull_request_id=pull_request.pull_request_id),
343 pull_request_id=pull_request.pull_request_id),
344 params={
344 params={
345 'close_pull_request': 'true',
345 'close_pull_request': 'true',
346 'csrf_token': csrf_token},
346 'csrf_token': csrf_token},
347 extra_environ=xhr_header)
347 extra_environ=xhr_header)
348
348
349 assert response.json
349 assert response.json
350
350
351 pull_request = PullRequest.get(pull_request_id)
351 pull_request = PullRequest.get(pull_request_id)
352 assert pull_request.is_closed()
352 assert pull_request.is_closed()
353
353
354 # check only the latest status, not the review status
354 # check only the latest status, not the review status
355 status = ChangesetStatusModel().get_status(
355 status = ChangesetStatusModel().get_status(
356 pull_request.source_repo, pull_request=pull_request)
356 pull_request.source_repo, pull_request=pull_request)
357 assert status == ChangesetStatus.STATUS_REJECTED
357 assert status == ChangesetStatus.STATUS_REJECTED
358
358
359 def test_create_pull_request(self, backend, csrf_token):
359 def test_create_pull_request(self, backend, csrf_token):
360 commits = [
360 commits = [
361 {'message': 'ancestor'},
361 {'message': 'ancestor'},
362 {'message': 'change'},
362 {'message': 'change'},
363 {'message': 'change2'},
363 {'message': 'change2'},
364 ]
364 ]
365 commit_ids = backend.create_master_repo(commits)
365 commit_ids = backend.create_master_repo(commits)
366 target = backend.create_repo(heads=['ancestor'])
366 target = backend.create_repo(heads=['ancestor'])
367 source = backend.create_repo(heads=['change2'])
367 source = backend.create_repo(heads=['change2'])
368
368
369 response = self.app.post(
369 response = self.app.post(
370 route_path('pullrequest_create', repo_name=source.repo_name),
370 route_path('pullrequest_create', repo_name=source.repo_name),
371 [
371 [
372 ('source_repo', source.repo_name),
372 ('source_repo', source.repo_name),
373 ('source_ref', 'branch:default:' + commit_ids['change2']),
373 ('source_ref', 'branch:default:' + commit_ids['change2']),
374 ('target_repo', target.repo_name),
374 ('target_repo', target.repo_name),
375 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
375 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
376 ('common_ancestor', commit_ids['ancestor']),
376 ('common_ancestor', commit_ids['ancestor']),
377 ('pullrequest_title', 'Title'),
377 ('pullrequest_title', 'Title'),
378 ('pullrequest_desc', 'Description'),
378 ('pullrequest_desc', 'Description'),
379 ('description_renderer', 'markdown'),
379 ('description_renderer', 'markdown'),
380 ('__start__', 'review_members:sequence'),
380 ('__start__', 'review_members:sequence'),
381 ('__start__', 'reviewer:mapping'),
381 ('__start__', 'reviewer:mapping'),
382 ('user_id', '1'),
382 ('user_id', '1'),
383 ('__start__', 'reasons:sequence'),
383 ('__start__', 'reasons:sequence'),
384 ('reason', 'Some reason'),
384 ('reason', 'Some reason'),
385 ('__end__', 'reasons:sequence'),
385 ('__end__', 'reasons:sequence'),
386 ('__start__', 'rules:sequence'),
386 ('__start__', 'rules:sequence'),
387 ('__end__', 'rules:sequence'),
387 ('__end__', 'rules:sequence'),
388 ('mandatory', 'False'),
388 ('mandatory', 'False'),
389 ('__end__', 'reviewer:mapping'),
389 ('__end__', 'reviewer:mapping'),
390 ('__end__', 'review_members:sequence'),
390 ('__end__', 'review_members:sequence'),
391 ('__start__', 'revisions:sequence'),
391 ('__start__', 'revisions:sequence'),
392 ('revisions', commit_ids['change']),
392 ('revisions', commit_ids['change']),
393 ('revisions', commit_ids['change2']),
393 ('revisions', commit_ids['change2']),
394 ('__end__', 'revisions:sequence'),
394 ('__end__', 'revisions:sequence'),
395 ('user', ''),
395 ('user', ''),
396 ('csrf_token', csrf_token),
396 ('csrf_token', csrf_token),
397 ],
397 ],
398 status=302)
398 status=302)
399
399
400 location = response.headers['Location']
400 location = response.headers['Location']
401 pull_request_id = location.rsplit('/', 1)[1]
401 pull_request_id = location.rsplit('/', 1)[1]
402 assert pull_request_id != 'new'
402 assert pull_request_id != 'new'
403 pull_request = PullRequest.get(int(pull_request_id))
403 pull_request = PullRequest.get(int(pull_request_id))
404
404
405 # check that we have now both revisions
405 # check that we have now both revisions
406 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
406 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
407 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
407 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
408 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
408 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
409 assert pull_request.target_ref == expected_target_ref
409 assert pull_request.target_ref == expected_target_ref
410
410
411 def test_reviewer_notifications(self, backend, csrf_token):
411 def test_reviewer_notifications(self, backend, csrf_token):
412 # We have to use the app.post for this test so it will create the
412 # We have to use the app.post for this test so it will create the
413 # notifications properly with the new PR
413 # notifications properly with the new PR
414 commits = [
414 commits = [
415 {'message': 'ancestor',
415 {'message': 'ancestor',
416 'added': [FileNode('file_A', content='content_of_ancestor')]},
416 'added': [FileNode('file_A', content='content_of_ancestor')]},
417 {'message': 'change',
417 {'message': 'change',
418 'added': [FileNode('file_a', content='content_of_change')]},
418 'added': [FileNode('file_a', content='content_of_change')]},
419 {'message': 'change-child'},
419 {'message': 'change-child'},
420 {'message': 'ancestor-child', 'parents': ['ancestor'],
420 {'message': 'ancestor-child', 'parents': ['ancestor'],
421 'added': [
421 'added': [
422 FileNode('file_B', content='content_of_ancestor_child')]},
422 FileNode('file_B', content='content_of_ancestor_child')]},
423 {'message': 'ancestor-child-2'},
423 {'message': 'ancestor-child-2'},
424 ]
424 ]
425 commit_ids = backend.create_master_repo(commits)
425 commit_ids = backend.create_master_repo(commits)
426 target = backend.create_repo(heads=['ancestor-child'])
426 target = backend.create_repo(heads=['ancestor-child'])
427 source = backend.create_repo(heads=['change'])
427 source = backend.create_repo(heads=['change'])
428
428
429 response = self.app.post(
429 response = self.app.post(
430 route_path('pullrequest_create', repo_name=source.repo_name),
430 route_path('pullrequest_create', repo_name=source.repo_name),
431 [
431 [
432 ('source_repo', source.repo_name),
432 ('source_repo', source.repo_name),
433 ('source_ref', 'branch:default:' + commit_ids['change']),
433 ('source_ref', 'branch:default:' + commit_ids['change']),
434 ('target_repo', target.repo_name),
434 ('target_repo', target.repo_name),
435 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
435 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
436 ('common_ancestor', commit_ids['ancestor']),
436 ('common_ancestor', commit_ids['ancestor']),
437 ('pullrequest_title', 'Title'),
437 ('pullrequest_title', 'Title'),
438 ('pullrequest_desc', 'Description'),
438 ('pullrequest_desc', 'Description'),
439 ('description_renderer', 'markdown'),
439 ('description_renderer', 'markdown'),
440 ('__start__', 'review_members:sequence'),
440 ('__start__', 'review_members:sequence'),
441 ('__start__', 'reviewer:mapping'),
441 ('__start__', 'reviewer:mapping'),
442 ('user_id', '2'),
442 ('user_id', '2'),
443 ('__start__', 'reasons:sequence'),
443 ('__start__', 'reasons:sequence'),
444 ('reason', 'Some reason'),
444 ('reason', 'Some reason'),
445 ('__end__', 'reasons:sequence'),
445 ('__end__', 'reasons:sequence'),
446 ('__start__', 'rules:sequence'),
446 ('__start__', 'rules:sequence'),
447 ('__end__', 'rules:sequence'),
447 ('__end__', 'rules:sequence'),
448 ('mandatory', 'False'),
448 ('mandatory', 'False'),
449 ('__end__', 'reviewer:mapping'),
449 ('__end__', 'reviewer:mapping'),
450 ('__end__', 'review_members:sequence'),
450 ('__end__', 'review_members:sequence'),
451 ('__start__', 'revisions:sequence'),
451 ('__start__', 'revisions:sequence'),
452 ('revisions', commit_ids['change']),
452 ('revisions', commit_ids['change']),
453 ('__end__', 'revisions:sequence'),
453 ('__end__', 'revisions:sequence'),
454 ('user', ''),
454 ('user', ''),
455 ('csrf_token', csrf_token),
455 ('csrf_token', csrf_token),
456 ],
456 ],
457 status=302)
457 status=302)
458
458
459 location = response.headers['Location']
459 location = response.headers['Location']
460
460
461 pull_request_id = location.rsplit('/', 1)[1]
461 pull_request_id = location.rsplit('/', 1)[1]
462 assert pull_request_id != 'new'
462 assert pull_request_id != 'new'
463 pull_request = PullRequest.get(int(pull_request_id))
463 pull_request = PullRequest.get(int(pull_request_id))
464
464
465 # Check that a notification was made
465 # Check that a notification was made
466 notifications = Notification.query()\
466 notifications = Notification.query()\
467 .filter(Notification.created_by == pull_request.author.user_id,
467 .filter(Notification.created_by == pull_request.author.user_id,
468 Notification.type_ == Notification.TYPE_PULL_REQUEST,
468 Notification.type_ == Notification.TYPE_PULL_REQUEST,
469 Notification.subject.contains(
469 Notification.subject.contains(
470 "wants you to review pull request #%s" % pull_request_id))
470 "wants you to review pull request #%s" % pull_request_id))
471 assert len(notifications.all()) == 1
471 assert len(notifications.all()) == 1
472
472
473 # Change reviewers and check that a notification was made
473 # Change reviewers and check that a notification was made
474 PullRequestModel().update_reviewers(
474 PullRequestModel().update_reviewers(
475 pull_request.pull_request_id, [(1, [], False, [])],
475 pull_request.pull_request_id, [(1, [], False, [])],
476 pull_request.author)
476 pull_request.author)
477 assert len(notifications.all()) == 2
477 assert len(notifications.all()) == 2
478
478
479 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
479 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
480 csrf_token):
480 csrf_token):
481 commits = [
481 commits = [
482 {'message': 'ancestor',
482 {'message': 'ancestor',
483 'added': [FileNode('file_A', content='content_of_ancestor')]},
483 'added': [FileNode('file_A', content='content_of_ancestor')]},
484 {'message': 'change',
484 {'message': 'change',
485 'added': [FileNode('file_a', content='content_of_change')]},
485 'added': [FileNode('file_a', content='content_of_change')]},
486 {'message': 'change-child'},
486 {'message': 'change-child'},
487 {'message': 'ancestor-child', 'parents': ['ancestor'],
487 {'message': 'ancestor-child', 'parents': ['ancestor'],
488 'added': [
488 'added': [
489 FileNode('file_B', content='content_of_ancestor_child')]},
489 FileNode('file_B', content='content_of_ancestor_child')]},
490 {'message': 'ancestor-child-2'},
490 {'message': 'ancestor-child-2'},
491 ]
491 ]
492 commit_ids = backend.create_master_repo(commits)
492 commit_ids = backend.create_master_repo(commits)
493 target = backend.create_repo(heads=['ancestor-child'])
493 target = backend.create_repo(heads=['ancestor-child'])
494 source = backend.create_repo(heads=['change'])
494 source = backend.create_repo(heads=['change'])
495
495
496 response = self.app.post(
496 response = self.app.post(
497 route_path('pullrequest_create', repo_name=source.repo_name),
497 route_path('pullrequest_create', repo_name=source.repo_name),
498 [
498 [
499 ('source_repo', source.repo_name),
499 ('source_repo', source.repo_name),
500 ('source_ref', 'branch:default:' + commit_ids['change']),
500 ('source_ref', 'branch:default:' + commit_ids['change']),
501 ('target_repo', target.repo_name),
501 ('target_repo', target.repo_name),
502 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
502 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
503 ('common_ancestor', commit_ids['ancestor']),
503 ('common_ancestor', commit_ids['ancestor']),
504 ('pullrequest_title', 'Title'),
504 ('pullrequest_title', 'Title'),
505 ('pullrequest_desc', 'Description'),
505 ('pullrequest_desc', 'Description'),
506 ('description_renderer', 'markdown'),
506 ('description_renderer', 'markdown'),
507 ('__start__', 'review_members:sequence'),
507 ('__start__', 'review_members:sequence'),
508 ('__start__', 'reviewer:mapping'),
508 ('__start__', 'reviewer:mapping'),
509 ('user_id', '1'),
509 ('user_id', '1'),
510 ('__start__', 'reasons:sequence'),
510 ('__start__', 'reasons:sequence'),
511 ('reason', 'Some reason'),
511 ('reason', 'Some reason'),
512 ('__end__', 'reasons:sequence'),
512 ('__end__', 'reasons:sequence'),
513 ('__start__', 'rules:sequence'),
513 ('__start__', 'rules:sequence'),
514 ('__end__', 'rules:sequence'),
514 ('__end__', 'rules:sequence'),
515 ('mandatory', 'False'),
515 ('mandatory', 'False'),
516 ('__end__', 'reviewer:mapping'),
516 ('__end__', 'reviewer:mapping'),
517 ('__end__', 'review_members:sequence'),
517 ('__end__', 'review_members:sequence'),
518 ('__start__', 'revisions:sequence'),
518 ('__start__', 'revisions:sequence'),
519 ('revisions', commit_ids['change']),
519 ('revisions', commit_ids['change']),
520 ('__end__', 'revisions:sequence'),
520 ('__end__', 'revisions:sequence'),
521 ('user', ''),
521 ('user', ''),
522 ('csrf_token', csrf_token),
522 ('csrf_token', csrf_token),
523 ],
523 ],
524 status=302)
524 status=302)
525
525
526 location = response.headers['Location']
526 location = response.headers['Location']
527
527
528 pull_request_id = location.rsplit('/', 1)[1]
528 pull_request_id = location.rsplit('/', 1)[1]
529 assert pull_request_id != 'new'
529 assert pull_request_id != 'new'
530 pull_request = PullRequest.get(int(pull_request_id))
530 pull_request = PullRequest.get(int(pull_request_id))
531
531
532 # target_ref has to point to the ancestor's commit_id in order to
532 # target_ref has to point to the ancestor's commit_id in order to
533 # show the correct diff
533 # show the correct diff
534 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
534 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
535 assert pull_request.target_ref == expected_target_ref
535 assert pull_request.target_ref == expected_target_ref
536
536
537 # Check generated diff contents
537 # Check generated diff contents
538 response = response.follow()
538 response = response.follow()
539 assert 'content_of_ancestor' not in response.body
539 assert 'content_of_ancestor' not in response.body
540 assert 'content_of_ancestor-child' not in response.body
540 assert 'content_of_ancestor-child' not in response.body
541 assert 'content_of_change' in response.body
541 assert 'content_of_change' in response.body
542
542
543 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
543 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
544 # Clear any previous calls to rcextensions
544 # Clear any previous calls to rcextensions
545 rhodecode.EXTENSIONS.calls.clear()
545 rhodecode.EXTENSIONS.calls.clear()
546
546
547 pull_request = pr_util.create_pull_request(
547 pull_request = pr_util.create_pull_request(
548 approved=True, mergeable=True)
548 approved=True, mergeable=True)
549 pull_request_id = pull_request.pull_request_id
549 pull_request_id = pull_request.pull_request_id
550 repo_name = pull_request.target_repo.scm_instance().name,
550 repo_name = pull_request.target_repo.scm_instance().name,
551
551
552 response = self.app.post(
552 response = self.app.post(
553 route_path('pullrequest_merge',
553 route_path('pullrequest_merge',
554 repo_name=str(repo_name[0]),
554 repo_name=str(repo_name[0]),
555 pull_request_id=pull_request_id),
555 pull_request_id=pull_request_id),
556 params={'csrf_token': csrf_token}).follow()
556 params={'csrf_token': csrf_token}).follow()
557
557
558 pull_request = PullRequest.get(pull_request_id)
558 pull_request = PullRequest.get(pull_request_id)
559
559
560 assert response.status_int == 200
560 assert response.status_int == 200
561 assert pull_request.is_closed()
561 assert pull_request.is_closed()
562 assert_pull_request_status(
562 assert_pull_request_status(
563 pull_request, ChangesetStatus.STATUS_APPROVED)
563 pull_request, ChangesetStatus.STATUS_APPROVED)
564
564
565 # Check the relevant log entries were added
565 # Check the relevant log entries were added
566 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
566 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(3)
567 actions = [log.action for log in user_logs]
567 actions = [log.action for log in user_logs]
568 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
568 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
569 expected_actions = [
569 expected_actions = [
570 u'repo.pull_request.close',
570 u'repo.pull_request.close',
571 u'repo.pull_request.merge',
571 u'repo.pull_request.merge',
572 u'repo.pull_request.comment.create'
572 u'repo.pull_request.comment.create'
573 ]
573 ]
574 assert actions == expected_actions
574 assert actions == expected_actions
575
575
576 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
576 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(4)
577 actions = [log for log in user_logs]
577 actions = [log for log in user_logs]
578 assert actions[-1].action == 'user.push'
578 assert actions[-1].action == 'user.push'
579 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
579 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
580
580
581 # Check post_push rcextension was really executed
581 # Check post_push rcextension was really executed
582 push_calls = rhodecode.EXTENSIONS.calls['_push_hook']
582 push_calls = rhodecode.EXTENSIONS.calls['_push_hook']
583 assert len(push_calls) == 1
583 assert len(push_calls) == 1
584 unused_last_call_args, last_call_kwargs = push_calls[0]
584 unused_last_call_args, last_call_kwargs = push_calls[0]
585 assert last_call_kwargs['action'] == 'push'
585 assert last_call_kwargs['action'] == 'push'
586 assert last_call_kwargs['commit_ids'] == pr_commit_ids
586 assert last_call_kwargs['commit_ids'] == pr_commit_ids
587
587
588 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
588 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
589 pull_request = pr_util.create_pull_request(mergeable=False)
589 pull_request = pr_util.create_pull_request(mergeable=False)
590 pull_request_id = pull_request.pull_request_id
590 pull_request_id = pull_request.pull_request_id
591 pull_request = PullRequest.get(pull_request_id)
591 pull_request = PullRequest.get(pull_request_id)
592
592
593 response = self.app.post(
593 response = self.app.post(
594 route_path('pullrequest_merge',
594 route_path('pullrequest_merge',
595 repo_name=pull_request.target_repo.scm_instance().name,
595 repo_name=pull_request.target_repo.scm_instance().name,
596 pull_request_id=pull_request.pull_request_id),
596 pull_request_id=pull_request.pull_request_id),
597 params={'csrf_token': csrf_token}).follow()
597 params={'csrf_token': csrf_token}).follow()
598
598
599 assert response.status_int == 200
599 assert response.status_int == 200
600 response.mustcontain(
600 response.mustcontain(
601 'Merge is not currently possible because of below failed checks.')
601 'Merge is not currently possible because of below failed checks.')
602 response.mustcontain('Server-side pull request merging is disabled.')
602 response.mustcontain('Server-side pull request merging is disabled.')
603
603
604 @pytest.mark.skip_backends('svn')
604 @pytest.mark.skip_backends('svn')
605 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
605 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
606 pull_request = pr_util.create_pull_request(mergeable=True)
606 pull_request = pr_util.create_pull_request(mergeable=True)
607 pull_request_id = pull_request.pull_request_id
607 pull_request_id = pull_request.pull_request_id
608 repo_name = pull_request.target_repo.scm_instance().name
608 repo_name = pull_request.target_repo.scm_instance().name
609
609
610 response = self.app.post(
610 response = self.app.post(
611 route_path('pullrequest_merge',
611 route_path('pullrequest_merge',
612 repo_name=repo_name, pull_request_id=pull_request_id),
612 repo_name=repo_name, pull_request_id=pull_request_id),
613 params={'csrf_token': csrf_token}).follow()
613 params={'csrf_token': csrf_token}).follow()
614
614
615 assert response.status_int == 200
615 assert response.status_int == 200
616
616
617 response.mustcontain(
617 response.mustcontain(
618 'Merge is not currently possible because of below failed checks.')
618 'Merge is not currently possible because of below failed checks.')
619 response.mustcontain('Pull request reviewer approval is pending.')
619 response.mustcontain('Pull request reviewer approval is pending.')
620
620
621 def test_merge_pull_request_renders_failure_reason(
621 def test_merge_pull_request_renders_failure_reason(
622 self, user_regular, csrf_token, pr_util):
622 self, user_regular, csrf_token, pr_util):
623 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
623 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
624 pull_request_id = pull_request.pull_request_id
624 pull_request_id = pull_request.pull_request_id
625 repo_name = pull_request.target_repo.scm_instance().name
625 repo_name = pull_request.target_repo.scm_instance().name
626
626
627 merge_resp = MergeResponse(True, False, 'STUB_COMMIT_ID',
627 merge_resp = MergeResponse(True, False, 'STUB_COMMIT_ID',
628 MergeFailureReason.PUSH_FAILED,
628 MergeFailureReason.PUSH_FAILED,
629 metadata={'target': 'shadow repo',
629 metadata={'target': 'shadow repo',
630 'merge_commit': 'xxx'})
630 'merge_commit': 'xxx'})
631 model_patcher = mock.patch.multiple(
631 model_patcher = mock.patch.multiple(
632 PullRequestModel,
632 PullRequestModel,
633 merge_repo=mock.Mock(return_value=merge_resp),
633 merge_repo=mock.Mock(return_value=merge_resp),
634 merge_status=mock.Mock(return_value=(True, 'WRONG_MESSAGE')))
634 merge_status=mock.Mock(return_value=(True, 'WRONG_MESSAGE')))
635
635
636 with model_patcher:
636 with model_patcher:
637 response = self.app.post(
637 response = self.app.post(
638 route_path('pullrequest_merge',
638 route_path('pullrequest_merge',
639 repo_name=repo_name,
639 repo_name=repo_name,
640 pull_request_id=pull_request_id),
640 pull_request_id=pull_request_id),
641 params={'csrf_token': csrf_token}, status=302)
641 params={'csrf_token': csrf_token}, status=302)
642
642
643 merge_resp = MergeResponse(True, True, '', MergeFailureReason.PUSH_FAILED,
643 merge_resp = MergeResponse(True, True, '', MergeFailureReason.PUSH_FAILED,
644 metadata={'target': 'shadow repo',
644 metadata={'target': 'shadow repo',
645 'merge_commit': 'xxx'})
645 'merge_commit': 'xxx'})
646 assert_session_flash(response, merge_resp.merge_status_message)
646 assert_session_flash(response, merge_resp.merge_status_message)
647
647
648 def test_update_source_revision(self, backend, csrf_token):
648 def test_update_source_revision(self, backend, csrf_token):
649 commits = [
649 commits = [
650 {'message': 'ancestor'},
650 {'message': 'ancestor'},
651 {'message': 'change'},
651 {'message': 'change'},
652 {'message': 'change-2'},
652 {'message': 'change-2'},
653 ]
653 ]
654 commit_ids = backend.create_master_repo(commits)
654 commit_ids = backend.create_master_repo(commits)
655 target = backend.create_repo(heads=['ancestor'])
655 target = backend.create_repo(heads=['ancestor'])
656 source = backend.create_repo(heads=['change'])
656 source = backend.create_repo(heads=['change'])
657
657
658 # create pr from a in source to A in target
658 # create pr from a in source to A in target
659 pull_request = PullRequest()
659 pull_request = PullRequest()
660
660
661 pull_request.source_repo = source
661 pull_request.source_repo = source
662 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
662 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
663 branch=backend.default_branch_name, commit_id=commit_ids['change'])
663 branch=backend.default_branch_name, commit_id=commit_ids['change'])
664
664
665 pull_request.target_repo = target
665 pull_request.target_repo = target
666 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
666 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
667 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
667 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
668
668
669 pull_request.revisions = [commit_ids['change']]
669 pull_request.revisions = [commit_ids['change']]
670 pull_request.title = u"Test"
670 pull_request.title = u"Test"
671 pull_request.description = u"Description"
671 pull_request.description = u"Description"
672 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
672 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
673 pull_request.pull_request_state = PullRequest.STATE_CREATED
673 pull_request.pull_request_state = PullRequest.STATE_CREATED
674 Session().add(pull_request)
674 Session().add(pull_request)
675 Session().commit()
675 Session().commit()
676 pull_request_id = pull_request.pull_request_id
676 pull_request_id = pull_request.pull_request_id
677
677
678 # source has ancestor - change - change-2
678 # source has ancestor - change - change-2
679 backend.pull_heads(source, heads=['change-2'])
679 backend.pull_heads(source, heads=['change-2'])
680
680
681 # update PR
681 # update PR
682 self.app.post(
682 self.app.post(
683 route_path('pullrequest_update',
683 route_path('pullrequest_update',
684 repo_name=target.repo_name, pull_request_id=pull_request_id),
684 repo_name=target.repo_name, pull_request_id=pull_request_id),
685 params={'update_commits': 'true', 'csrf_token': csrf_token})
685 params={'update_commits': 'true', 'csrf_token': csrf_token})
686
686
687 response = self.app.get(
687 response = self.app.get(
688 route_path('pullrequest_show',
688 route_path('pullrequest_show',
689 repo_name=target.repo_name,
689 repo_name=target.repo_name,
690 pull_request_id=pull_request.pull_request_id))
690 pull_request_id=pull_request.pull_request_id))
691
691
692 assert response.status_int == 200
692 assert response.status_int == 200
693 assert 'Pull request updated to' in response.body
693 assert 'Pull request updated to' in response.body
694 assert 'with 1 added, 0 removed commits.' in response.body
694 assert 'with 1 added, 0 removed commits.' in response.body
695
695
696 # check that we have now both revisions
696 # check that we have now both revisions
697 pull_request = PullRequest.get(pull_request_id)
697 pull_request = PullRequest.get(pull_request_id)
698 assert pull_request.revisions == [commit_ids['change-2'], commit_ids['change']]
698 assert pull_request.revisions == [commit_ids['change-2'], commit_ids['change']]
699
699
700 def test_update_target_revision(self, backend, csrf_token):
700 def test_update_target_revision(self, backend, csrf_token):
701 commits = [
701 commits = [
702 {'message': 'ancestor'},
702 {'message': 'ancestor'},
703 {'message': 'change'},
703 {'message': 'change'},
704 {'message': 'ancestor-new', 'parents': ['ancestor']},
704 {'message': 'ancestor-new', 'parents': ['ancestor']},
705 {'message': 'change-rebased'},
705 {'message': 'change-rebased'},
706 ]
706 ]
707 commit_ids = backend.create_master_repo(commits)
707 commit_ids = backend.create_master_repo(commits)
708 target = backend.create_repo(heads=['ancestor'])
708 target = backend.create_repo(heads=['ancestor'])
709 source = backend.create_repo(heads=['change'])
709 source = backend.create_repo(heads=['change'])
710
710
711 # create pr from a in source to A in target
711 # create pr from a in source to A in target
712 pull_request = PullRequest()
712 pull_request = PullRequest()
713
713
714 pull_request.source_repo = source
714 pull_request.source_repo = source
715 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
715 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
716 branch=backend.default_branch_name, commit_id=commit_ids['change'])
716 branch=backend.default_branch_name, commit_id=commit_ids['change'])
717
717
718 pull_request.target_repo = target
718 pull_request.target_repo = target
719 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
719 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
720 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
720 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
721
721
722 pull_request.revisions = [commit_ids['change']]
722 pull_request.revisions = [commit_ids['change']]
723 pull_request.title = u"Test"
723 pull_request.title = u"Test"
724 pull_request.description = u"Description"
724 pull_request.description = u"Description"
725 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
725 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
726 pull_request.pull_request_state = PullRequest.STATE_CREATED
726 pull_request.pull_request_state = PullRequest.STATE_CREATED
727
727
728 Session().add(pull_request)
728 Session().add(pull_request)
729 Session().commit()
729 Session().commit()
730 pull_request_id = pull_request.pull_request_id
730 pull_request_id = pull_request.pull_request_id
731
731
732 # target has ancestor - ancestor-new
732 # target has ancestor - ancestor-new
733 # source has ancestor - ancestor-new - change-rebased
733 # source has ancestor - ancestor-new - change-rebased
734 backend.pull_heads(target, heads=['ancestor-new'])
734 backend.pull_heads(target, heads=['ancestor-new'])
735 backend.pull_heads(source, heads=['change-rebased'])
735 backend.pull_heads(source, heads=['change-rebased'])
736
736
737 # update PR
737 # update PR
738 self.app.post(
738 self.app.post(
739 route_path('pullrequest_update',
739 route_path('pullrequest_update',
740 repo_name=target.repo_name,
740 repo_name=target.repo_name,
741 pull_request_id=pull_request_id),
741 pull_request_id=pull_request_id),
742 params={'update_commits': 'true', 'csrf_token': csrf_token},
742 params={'update_commits': 'true', 'csrf_token': csrf_token},
743 status=200)
743 status=200)
744
744
745 # check that we have now both revisions
745 # check that we have now both revisions
746 pull_request = PullRequest.get(pull_request_id)
746 pull_request = PullRequest.get(pull_request_id)
747 assert pull_request.revisions == [commit_ids['change-rebased']]
747 assert pull_request.revisions == [commit_ids['change-rebased']]
748 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
748 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
749 branch=backend.default_branch_name, commit_id=commit_ids['ancestor-new'])
749 branch=backend.default_branch_name, commit_id=commit_ids['ancestor-new'])
750
750
751 response = self.app.get(
751 response = self.app.get(
752 route_path('pullrequest_show',
752 route_path('pullrequest_show',
753 repo_name=target.repo_name,
753 repo_name=target.repo_name,
754 pull_request_id=pull_request.pull_request_id))
754 pull_request_id=pull_request.pull_request_id))
755 assert response.status_int == 200
755 assert response.status_int == 200
756 assert 'Pull request updated to' in response.body
756 assert 'Pull request updated to' in response.body
757 assert 'with 1 added, 1 removed commits.' in response.body
757 assert 'with 1 added, 1 removed commits.' in response.body
758
758
759 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
759 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
760 backend = backend_git
760 backend = backend_git
761 commits = [
761 commits = [
762 {'message': 'master-commit-1'},
762 {'message': 'master-commit-1'},
763 {'message': 'master-commit-2-change-1'},
763 {'message': 'master-commit-2-change-1'},
764 {'message': 'master-commit-3-change-2'},
764 {'message': 'master-commit-3-change-2'},
765
765
766 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
766 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
767 {'message': 'feat-commit-2'},
767 {'message': 'feat-commit-2'},
768 ]
768 ]
769 commit_ids = backend.create_master_repo(commits)
769 commit_ids = backend.create_master_repo(commits)
770 target = backend.create_repo(heads=['master-commit-3-change-2'])
770 target = backend.create_repo(heads=['master-commit-3-change-2'])
771 source = backend.create_repo(heads=['feat-commit-2'])
771 source = backend.create_repo(heads=['feat-commit-2'])
772
772
773 # create pr from a in source to A in target
773 # create pr from a in source to A in target
774 pull_request = PullRequest()
774 pull_request = PullRequest()
775 pull_request.source_repo = source
775 pull_request.source_repo = source
776
776
777 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
777 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
778 branch=backend.default_branch_name,
778 branch=backend.default_branch_name,
779 commit_id=commit_ids['master-commit-3-change-2'])
779 commit_id=commit_ids['master-commit-3-change-2'])
780
780
781 pull_request.target_repo = target
781 pull_request.target_repo = target
782 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
782 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
783 branch=backend.default_branch_name, commit_id=commit_ids['feat-commit-2'])
783 branch=backend.default_branch_name, commit_id=commit_ids['feat-commit-2'])
784
784
785 pull_request.revisions = [
785 pull_request.revisions = [
786 commit_ids['feat-commit-1'],
786 commit_ids['feat-commit-1'],
787 commit_ids['feat-commit-2']
787 commit_ids['feat-commit-2']
788 ]
788 ]
789 pull_request.title = u"Test"
789 pull_request.title = u"Test"
790 pull_request.description = u"Description"
790 pull_request.description = u"Description"
791 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
791 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
792 pull_request.pull_request_state = PullRequest.STATE_CREATED
792 pull_request.pull_request_state = PullRequest.STATE_CREATED
793 Session().add(pull_request)
793 Session().add(pull_request)
794 Session().commit()
794 Session().commit()
795 pull_request_id = pull_request.pull_request_id
795 pull_request_id = pull_request.pull_request_id
796
796
797 # PR is created, now we simulate a force-push into target,
797 # PR is created, now we simulate a force-push into target,
798 # that drops a 2 last commits
798 # that drops a 2 last commits
799 vcsrepo = target.scm_instance()
799 vcsrepo = target.scm_instance()
800 vcsrepo.config.clear_section('hooks')
800 vcsrepo.config.clear_section('hooks')
801 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
801 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
802
802
803 # update PR
803 # update PR
804 self.app.post(
804 self.app.post(
805 route_path('pullrequest_update',
805 route_path('pullrequest_update',
806 repo_name=target.repo_name,
806 repo_name=target.repo_name,
807 pull_request_id=pull_request_id),
807 pull_request_id=pull_request_id),
808 params={'update_commits': 'true', 'csrf_token': csrf_token},
808 params={'update_commits': 'true', 'csrf_token': csrf_token},
809 status=200)
809 status=200)
810
810
811 response = self.app.get(route_path('pullrequest_new', repo_name=target.repo_name))
811 response = self.app.get(route_path('pullrequest_new', repo_name=target.repo_name))
812 assert response.status_int == 200
812 assert response.status_int == 200
813 response.mustcontain('Pull request updated to')
813 response.mustcontain('Pull request updated to')
814 response.mustcontain('with 0 added, 0 removed commits.')
814 response.mustcontain('with 0 added, 0 removed commits.')
815
815
816 def test_update_of_ancestor_reference(self, backend, csrf_token):
816 def test_update_of_ancestor_reference(self, backend, csrf_token):
817 commits = [
817 commits = [
818 {'message': 'ancestor'},
818 {'message': 'ancestor'},
819 {'message': 'change'},
819 {'message': 'change'},
820 {'message': 'change-2'},
820 {'message': 'change-2'},
821 {'message': 'ancestor-new', 'parents': ['ancestor']},
821 {'message': 'ancestor-new', 'parents': ['ancestor']},
822 {'message': 'change-rebased'},
822 {'message': 'change-rebased'},
823 ]
823 ]
824 commit_ids = backend.create_master_repo(commits)
824 commit_ids = backend.create_master_repo(commits)
825 target = backend.create_repo(heads=['ancestor'])
825 target = backend.create_repo(heads=['ancestor'])
826 source = backend.create_repo(heads=['change'])
826 source = backend.create_repo(heads=['change'])
827
827
828 # create pr from a in source to A in target
828 # create pr from a in source to A in target
829 pull_request = PullRequest()
829 pull_request = PullRequest()
830 pull_request.source_repo = source
830 pull_request.source_repo = source
831
831
832 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
832 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
833 branch=backend.default_branch_name, commit_id=commit_ids['change'])
833 branch=backend.default_branch_name, commit_id=commit_ids['change'])
834 pull_request.target_repo = target
834 pull_request.target_repo = target
835 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
835 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
836 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
836 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
837 pull_request.revisions = [commit_ids['change']]
837 pull_request.revisions = [commit_ids['change']]
838 pull_request.title = u"Test"
838 pull_request.title = u"Test"
839 pull_request.description = u"Description"
839 pull_request.description = u"Description"
840 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
840 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
841 pull_request.pull_request_state = PullRequest.STATE_CREATED
841 pull_request.pull_request_state = PullRequest.STATE_CREATED
842 Session().add(pull_request)
842 Session().add(pull_request)
843 Session().commit()
843 Session().commit()
844 pull_request_id = pull_request.pull_request_id
844 pull_request_id = pull_request.pull_request_id
845
845
846 # target has ancestor - ancestor-new
846 # target has ancestor - ancestor-new
847 # source has ancestor - ancestor-new - change-rebased
847 # source has ancestor - ancestor-new - change-rebased
848 backend.pull_heads(target, heads=['ancestor-new'])
848 backend.pull_heads(target, heads=['ancestor-new'])
849 backend.pull_heads(source, heads=['change-rebased'])
849 backend.pull_heads(source, heads=['change-rebased'])
850
850
851 # update PR
851 # update PR
852 self.app.post(
852 self.app.post(
853 route_path('pullrequest_update',
853 route_path('pullrequest_update',
854 repo_name=target.repo_name, pull_request_id=pull_request_id),
854 repo_name=target.repo_name, pull_request_id=pull_request_id),
855 params={'update_commits': 'true', 'csrf_token': csrf_token},
855 params={'update_commits': 'true', 'csrf_token': csrf_token},
856 status=200)
856 status=200)
857
857
858 # Expect the target reference to be updated correctly
858 # Expect the target reference to be updated correctly
859 pull_request = PullRequest.get(pull_request_id)
859 pull_request = PullRequest.get(pull_request_id)
860 assert pull_request.revisions == [commit_ids['change-rebased']]
860 assert pull_request.revisions == [commit_ids['change-rebased']]
861 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
861 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
862 branch=backend.default_branch_name,
862 branch=backend.default_branch_name,
863 commit_id=commit_ids['ancestor-new'])
863 commit_id=commit_ids['ancestor-new'])
864 assert pull_request.target_ref == expected_target_ref
864 assert pull_request.target_ref == expected_target_ref
865
865
866 def test_remove_pull_request_branch(self, backend_git, csrf_token):
866 def test_remove_pull_request_branch(self, backend_git, csrf_token):
867 branch_name = 'development'
867 branch_name = 'development'
868 commits = [
868 commits = [
869 {'message': 'initial-commit'},
869 {'message': 'initial-commit'},
870 {'message': 'old-feature'},
870 {'message': 'old-feature'},
871 {'message': 'new-feature', 'branch': branch_name},
871 {'message': 'new-feature', 'branch': branch_name},
872 ]
872 ]
873 repo = backend_git.create_repo(commits)
873 repo = backend_git.create_repo(commits)
874 repo_name = repo.repo_name
874 repo_name = repo.repo_name
875 commit_ids = backend_git.commit_ids
875 commit_ids = backend_git.commit_ids
876
876
877 pull_request = PullRequest()
877 pull_request = PullRequest()
878 pull_request.source_repo = repo
878 pull_request.source_repo = repo
879 pull_request.target_repo = repo
879 pull_request.target_repo = repo
880 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
880 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
881 branch=branch_name, commit_id=commit_ids['new-feature'])
881 branch=branch_name, commit_id=commit_ids['new-feature'])
882 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
882 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
883 branch=backend_git.default_branch_name, commit_id=commit_ids['old-feature'])
883 branch=backend_git.default_branch_name, commit_id=commit_ids['old-feature'])
884 pull_request.revisions = [commit_ids['new-feature']]
884 pull_request.revisions = [commit_ids['new-feature']]
885 pull_request.title = u"Test"
885 pull_request.title = u"Test"
886 pull_request.description = u"Description"
886 pull_request.description = u"Description"
887 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
887 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
888 pull_request.pull_request_state = PullRequest.STATE_CREATED
888 pull_request.pull_request_state = PullRequest.STATE_CREATED
889 Session().add(pull_request)
889 Session().add(pull_request)
890 Session().commit()
890 Session().commit()
891
891
892 pull_request_id = pull_request.pull_request_id
892 pull_request_id = pull_request.pull_request_id
893
893
894 vcs = repo.scm_instance()
894 vcs = repo.scm_instance()
895 vcs.remove_ref('refs/heads/{}'.format(branch_name))
895 vcs.remove_ref('refs/heads/{}'.format(branch_name))
896
896
897 response = self.app.get(route_path(
897 response = self.app.get(route_path(
898 'pullrequest_show',
898 'pullrequest_show',
899 repo_name=repo_name,
899 repo_name=repo_name,
900 pull_request_id=pull_request_id))
900 pull_request_id=pull_request_id))
901
901
902 assert response.status_int == 200
902 assert response.status_int == 200
903
903
904 response.assert_response().element_contains(
904 response.assert_response().element_contains(
905 '#changeset_compare_view_content .alert strong',
905 '#changeset_compare_view_content .alert strong',
906 'Missing commits')
906 'Missing commits')
907 response.assert_response().element_contains(
907 response.assert_response().element_contains(
908 '#changeset_compare_view_content .alert',
908 '#changeset_compare_view_content .alert',
909 'This pull request cannot be displayed, because one or more'
909 'This pull request cannot be displayed, because one or more'
910 ' commits no longer exist in the source repository.')
910 ' commits no longer exist in the source repository.')
911
911
912 def test_strip_commits_from_pull_request(
912 def test_strip_commits_from_pull_request(
913 self, backend, pr_util, csrf_token):
913 self, backend, pr_util, csrf_token):
914 commits = [
914 commits = [
915 {'message': 'initial-commit'},
915 {'message': 'initial-commit'},
916 {'message': 'old-feature'},
916 {'message': 'old-feature'},
917 {'message': 'new-feature', 'parents': ['initial-commit']},
917 {'message': 'new-feature', 'parents': ['initial-commit']},
918 ]
918 ]
919 pull_request = pr_util.create_pull_request(
919 pull_request = pr_util.create_pull_request(
920 commits, target_head='initial-commit', source_head='new-feature',
920 commits, target_head='initial-commit', source_head='new-feature',
921 revisions=['new-feature'])
921 revisions=['new-feature'])
922
922
923 vcs = pr_util.source_repository.scm_instance()
923 vcs = pr_util.source_repository.scm_instance()
924 if backend.alias == 'git':
924 if backend.alias == 'git':
925 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
925 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
926 else:
926 else:
927 vcs.strip(pr_util.commit_ids['new-feature'])
927 vcs.strip(pr_util.commit_ids['new-feature'])
928
928
929 response = self.app.get(route_path(
929 response = self.app.get(route_path(
930 'pullrequest_show',
930 'pullrequest_show',
931 repo_name=pr_util.target_repository.repo_name,
931 repo_name=pr_util.target_repository.repo_name,
932 pull_request_id=pull_request.pull_request_id))
932 pull_request_id=pull_request.pull_request_id))
933
933
934 assert response.status_int == 200
934 assert response.status_int == 200
935
935
936 response.assert_response().element_contains(
936 response.assert_response().element_contains(
937 '#changeset_compare_view_content .alert strong',
937 '#changeset_compare_view_content .alert strong',
938 'Missing commits')
938 'Missing commits')
939 response.assert_response().element_contains(
939 response.assert_response().element_contains(
940 '#changeset_compare_view_content .alert',
940 '#changeset_compare_view_content .alert',
941 'This pull request cannot be displayed, because one or more'
941 'This pull request cannot be displayed, because one or more'
942 ' commits no longer exist in the source repository.')
942 ' commits no longer exist in the source repository.')
943 response.assert_response().element_contains(
943 response.assert_response().element_contains(
944 '#update_commits',
944 '#update_commits',
945 'Update commits')
945 'Update commits')
946
946
947 def test_strip_commits_and_update(
947 def test_strip_commits_and_update(
948 self, backend, pr_util, csrf_token):
948 self, backend, pr_util, csrf_token):
949 commits = [
949 commits = [
950 {'message': 'initial-commit'},
950 {'message': 'initial-commit'},
951 {'message': 'old-feature'},
951 {'message': 'old-feature'},
952 {'message': 'new-feature', 'parents': ['old-feature']},
952 {'message': 'new-feature', 'parents': ['old-feature']},
953 ]
953 ]
954 pull_request = pr_util.create_pull_request(
954 pull_request = pr_util.create_pull_request(
955 commits, target_head='old-feature', source_head='new-feature',
955 commits, target_head='old-feature', source_head='new-feature',
956 revisions=['new-feature'], mergeable=True)
956 revisions=['new-feature'], mergeable=True)
957
957
958 vcs = pr_util.source_repository.scm_instance()
958 vcs = pr_util.source_repository.scm_instance()
959 if backend.alias == 'git':
959 if backend.alias == 'git':
960 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
960 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
961 else:
961 else:
962 vcs.strip(pr_util.commit_ids['new-feature'])
962 vcs.strip(pr_util.commit_ids['new-feature'])
963
963
964 response = self.app.post(
964 response = self.app.post(
965 route_path('pullrequest_update',
965 route_path('pullrequest_update',
966 repo_name=pull_request.target_repo.repo_name,
966 repo_name=pull_request.target_repo.repo_name,
967 pull_request_id=pull_request.pull_request_id),
967 pull_request_id=pull_request.pull_request_id),
968 params={'update_commits': 'true',
968 params={'update_commits': 'true',
969 'csrf_token': csrf_token})
969 'csrf_token': csrf_token})
970
970
971 assert response.status_int == 200
971 assert response.status_int == 200
972 assert response.body == 'true'
972 assert response.body == 'true'
973
973
974 # Make sure that after update, it won't raise 500 errors
974 # Make sure that after update, it won't raise 500 errors
975 response = self.app.get(route_path(
975 response = self.app.get(route_path(
976 'pullrequest_show',
976 'pullrequest_show',
977 repo_name=pr_util.target_repository.repo_name,
977 repo_name=pr_util.target_repository.repo_name,
978 pull_request_id=pull_request.pull_request_id))
978 pull_request_id=pull_request.pull_request_id))
979
979
980 assert response.status_int == 200
980 assert response.status_int == 200
981 response.assert_response().element_contains(
981 response.assert_response().element_contains(
982 '#changeset_compare_view_content .alert strong',
982 '#changeset_compare_view_content .alert strong',
983 'Missing commits')
983 'Missing commits')
984
984
985 def test_branch_is_a_link(self, pr_util):
985 def test_branch_is_a_link(self, pr_util):
986 pull_request = pr_util.create_pull_request()
986 pull_request = pr_util.create_pull_request()
987 pull_request.source_ref = 'branch:origin:1234567890abcdef'
987 pull_request.source_ref = 'branch:origin:1234567890abcdef'
988 pull_request.target_ref = 'branch:target:abcdef1234567890'
988 pull_request.target_ref = 'branch:target:abcdef1234567890'
989 Session().add(pull_request)
989 Session().add(pull_request)
990 Session().commit()
990 Session().commit()
991
991
992 response = self.app.get(route_path(
992 response = self.app.get(route_path(
993 'pullrequest_show',
993 'pullrequest_show',
994 repo_name=pull_request.target_repo.scm_instance().name,
994 repo_name=pull_request.target_repo.scm_instance().name,
995 pull_request_id=pull_request.pull_request_id))
995 pull_request_id=pull_request.pull_request_id))
996 assert response.status_int == 200
996 assert response.status_int == 200
997
997
998 origin = response.assert_response().get_element('.pr-origininfo .tag')
998 origin = response.assert_response().get_element('.pr-origininfo .tag')
999 origin_children = origin.getchildren()
999 origin_children = origin.getchildren()
1000 assert len(origin_children) == 1
1000 assert len(origin_children) == 1
1001 target = response.assert_response().get_element('.pr-targetinfo .tag')
1001 target = response.assert_response().get_element('.pr-targetinfo .tag')
1002 target_children = target.getchildren()
1002 target_children = target.getchildren()
1003 assert len(target_children) == 1
1003 assert len(target_children) == 1
1004
1004
1005 expected_origin_link = route_path(
1005 expected_origin_link = route_path(
1006 'repo_commits',
1006 'repo_commits',
1007 repo_name=pull_request.source_repo.scm_instance().name,
1007 repo_name=pull_request.source_repo.scm_instance().name,
1008 params=dict(branch='origin'))
1008 params=dict(branch='origin'))
1009 expected_target_link = route_path(
1009 expected_target_link = route_path(
1010 'repo_commits',
1010 'repo_commits',
1011 repo_name=pull_request.target_repo.scm_instance().name,
1011 repo_name=pull_request.target_repo.scm_instance().name,
1012 params=dict(branch='target'))
1012 params=dict(branch='target'))
1013 assert origin_children[0].attrib['href'] == expected_origin_link
1013 assert origin_children[0].attrib['href'] == expected_origin_link
1014 assert origin_children[0].text == 'branch: origin'
1014 assert origin_children[0].text == 'branch: origin'
1015 assert target_children[0].attrib['href'] == expected_target_link
1015 assert target_children[0].attrib['href'] == expected_target_link
1016 assert target_children[0].text == 'branch: target'
1016 assert target_children[0].text == 'branch: target'
1017
1017
1018 def test_bookmark_is_not_a_link(self, pr_util):
1018 def test_bookmark_is_not_a_link(self, pr_util):
1019 pull_request = pr_util.create_pull_request()
1019 pull_request = pr_util.create_pull_request()
1020 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1020 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1021 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1021 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1022 Session().add(pull_request)
1022 Session().add(pull_request)
1023 Session().commit()
1023 Session().commit()
1024
1024
1025 response = self.app.get(route_path(
1025 response = self.app.get(route_path(
1026 'pullrequest_show',
1026 'pullrequest_show',
1027 repo_name=pull_request.target_repo.scm_instance().name,
1027 repo_name=pull_request.target_repo.scm_instance().name,
1028 pull_request_id=pull_request.pull_request_id))
1028 pull_request_id=pull_request.pull_request_id))
1029 assert response.status_int == 200
1029 assert response.status_int == 200
1030
1030
1031 origin = response.assert_response().get_element('.pr-origininfo .tag')
1031 origin = response.assert_response().get_element('.pr-origininfo .tag')
1032 assert origin.text.strip() == 'bookmark: origin'
1032 assert origin.text.strip() == 'bookmark: origin'
1033 assert origin.getchildren() == []
1033 assert origin.getchildren() == []
1034
1034
1035 target = response.assert_response().get_element('.pr-targetinfo .tag')
1035 target = response.assert_response().get_element('.pr-targetinfo .tag')
1036 assert target.text.strip() == 'bookmark: target'
1036 assert target.text.strip() == 'bookmark: target'
1037 assert target.getchildren() == []
1037 assert target.getchildren() == []
1038
1038
1039 def test_tag_is_not_a_link(self, pr_util):
1039 def test_tag_is_not_a_link(self, pr_util):
1040 pull_request = pr_util.create_pull_request()
1040 pull_request = pr_util.create_pull_request()
1041 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1041 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1042 pull_request.target_ref = 'tag:target:abcdef1234567890'
1042 pull_request.target_ref = 'tag:target:abcdef1234567890'
1043 Session().add(pull_request)
1043 Session().add(pull_request)
1044 Session().commit()
1044 Session().commit()
1045
1045
1046 response = self.app.get(route_path(
1046 response = self.app.get(route_path(
1047 'pullrequest_show',
1047 'pullrequest_show',
1048 repo_name=pull_request.target_repo.scm_instance().name,
1048 repo_name=pull_request.target_repo.scm_instance().name,
1049 pull_request_id=pull_request.pull_request_id))
1049 pull_request_id=pull_request.pull_request_id))
1050 assert response.status_int == 200
1050 assert response.status_int == 200
1051
1051
1052 origin = response.assert_response().get_element('.pr-origininfo .tag')
1052 origin = response.assert_response().get_element('.pr-origininfo .tag')
1053 assert origin.text.strip() == 'tag: origin'
1053 assert origin.text.strip() == 'tag: origin'
1054 assert origin.getchildren() == []
1054 assert origin.getchildren() == []
1055
1055
1056 target = response.assert_response().get_element('.pr-targetinfo .tag')
1056 target = response.assert_response().get_element('.pr-targetinfo .tag')
1057 assert target.text.strip() == 'tag: target'
1057 assert target.text.strip() == 'tag: target'
1058 assert target.getchildren() == []
1058 assert target.getchildren() == []
1059
1059
1060 @pytest.mark.parametrize('mergeable', [True, False])
1060 @pytest.mark.parametrize('mergeable', [True, False])
1061 def test_shadow_repository_link(
1061 def test_shadow_repository_link(
1062 self, mergeable, pr_util, http_host_only_stub):
1062 self, mergeable, pr_util, http_host_only_stub):
1063 """
1063 """
1064 Check that the pull request summary page displays a link to the shadow
1064 Check that the pull request summary page displays a link to the shadow
1065 repository if the pull request is mergeable. If it is not mergeable
1065 repository if the pull request is mergeable. If it is not mergeable
1066 the link should not be displayed.
1066 the link should not be displayed.
1067 """
1067 """
1068 pull_request = pr_util.create_pull_request(
1068 pull_request = pr_util.create_pull_request(
1069 mergeable=mergeable, enable_notifications=False)
1069 mergeable=mergeable, enable_notifications=False)
1070 target_repo = pull_request.target_repo.scm_instance()
1070 target_repo = pull_request.target_repo.scm_instance()
1071 pr_id = pull_request.pull_request_id
1071 pr_id = pull_request.pull_request_id
1072 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1072 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1073 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1073 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1074
1074
1075 response = self.app.get(route_path(
1075 response = self.app.get(route_path(
1076 'pullrequest_show',
1076 'pullrequest_show',
1077 repo_name=target_repo.name,
1077 repo_name=target_repo.name,
1078 pull_request_id=pr_id))
1078 pull_request_id=pr_id))
1079
1079
1080 if mergeable:
1080 if mergeable:
1081 response.assert_response().element_value_contains(
1081 response.assert_response().element_value_contains(
1082 'input.pr-mergeinfo', shadow_url)
1082 'input.pr-mergeinfo', shadow_url)
1083 response.assert_response().element_value_contains(
1083 response.assert_response().element_value_contains(
1084 'input.pr-mergeinfo ', 'pr-merge')
1084 'input.pr-mergeinfo ', 'pr-merge')
1085 else:
1085 else:
1086 response.assert_response().no_element_exists('.pr-mergeinfo')
1086 response.assert_response().no_element_exists('.pr-mergeinfo')
1087
1087
1088
1088
1089 @pytest.mark.usefixtures('app')
1089 @pytest.mark.usefixtures('app')
1090 @pytest.mark.backends("git", "hg")
1090 @pytest.mark.backends("git", "hg")
1091 class TestPullrequestsControllerDelete(object):
1091 class TestPullrequestsControllerDelete(object):
1092 def test_pull_request_delete_button_permissions_admin(
1092 def test_pull_request_delete_button_permissions_admin(
1093 self, autologin_user, user_admin, pr_util):
1093 self, autologin_user, user_admin, pr_util):
1094 pull_request = pr_util.create_pull_request(
1094 pull_request = pr_util.create_pull_request(
1095 author=user_admin.username, enable_notifications=False)
1095 author=user_admin.username, enable_notifications=False)
1096
1096
1097 response = self.app.get(route_path(
1097 response = self.app.get(route_path(
1098 'pullrequest_show',
1098 'pullrequest_show',
1099 repo_name=pull_request.target_repo.scm_instance().name,
1099 repo_name=pull_request.target_repo.scm_instance().name,
1100 pull_request_id=pull_request.pull_request_id))
1100 pull_request_id=pull_request.pull_request_id))
1101
1101
1102 response.mustcontain('id="delete_pullrequest"')
1102 response.mustcontain('id="delete_pullrequest"')
1103 response.mustcontain('Confirm to delete this pull request')
1103 response.mustcontain('Confirm to delete this pull request')
1104
1104
1105 def test_pull_request_delete_button_permissions_owner(
1105 def test_pull_request_delete_button_permissions_owner(
1106 self, autologin_regular_user, user_regular, pr_util):
1106 self, autologin_regular_user, user_regular, pr_util):
1107 pull_request = pr_util.create_pull_request(
1107 pull_request = pr_util.create_pull_request(
1108 author=user_regular.username, enable_notifications=False)
1108 author=user_regular.username, enable_notifications=False)
1109
1109
1110 response = self.app.get(route_path(
1110 response = self.app.get(route_path(
1111 'pullrequest_show',
1111 'pullrequest_show',
1112 repo_name=pull_request.target_repo.scm_instance().name,
1112 repo_name=pull_request.target_repo.scm_instance().name,
1113 pull_request_id=pull_request.pull_request_id))
1113 pull_request_id=pull_request.pull_request_id))
1114
1114
1115 response.mustcontain('id="delete_pullrequest"')
1115 response.mustcontain('id="delete_pullrequest"')
1116 response.mustcontain('Confirm to delete this pull request')
1116 response.mustcontain('Confirm to delete this pull request')
1117
1117
1118 def test_pull_request_delete_button_permissions_forbidden(
1118 def test_pull_request_delete_button_permissions_forbidden(
1119 self, autologin_regular_user, user_regular, user_admin, pr_util):
1119 self, autologin_regular_user, user_regular, user_admin, pr_util):
1120 pull_request = pr_util.create_pull_request(
1120 pull_request = pr_util.create_pull_request(
1121 author=user_admin.username, enable_notifications=False)
1121 author=user_admin.username, enable_notifications=False)
1122
1122
1123 response = self.app.get(route_path(
1123 response = self.app.get(route_path(
1124 'pullrequest_show',
1124 'pullrequest_show',
1125 repo_name=pull_request.target_repo.scm_instance().name,
1125 repo_name=pull_request.target_repo.scm_instance().name,
1126 pull_request_id=pull_request.pull_request_id))
1126 pull_request_id=pull_request.pull_request_id))
1127 response.mustcontain(no=['id="delete_pullrequest"'])
1127 response.mustcontain(no=['id="delete_pullrequest"'])
1128 response.mustcontain(no=['Confirm to delete this pull request'])
1128 response.mustcontain(no=['Confirm to delete this pull request'])
1129
1129
1130 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1130 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1131 self, autologin_regular_user, user_regular, user_admin, pr_util,
1131 self, autologin_regular_user, user_regular, user_admin, pr_util,
1132 user_util):
1132 user_util):
1133
1133
1134 pull_request = pr_util.create_pull_request(
1134 pull_request = pr_util.create_pull_request(
1135 author=user_admin.username, enable_notifications=False)
1135 author=user_admin.username, enable_notifications=False)
1136
1136
1137 user_util.grant_user_permission_to_repo(
1137 user_util.grant_user_permission_to_repo(
1138 pull_request.target_repo, user_regular,
1138 pull_request.target_repo, user_regular,
1139 'repository.write')
1139 'repository.write')
1140
1140
1141 response = self.app.get(route_path(
1141 response = self.app.get(route_path(
1142 'pullrequest_show',
1142 'pullrequest_show',
1143 repo_name=pull_request.target_repo.scm_instance().name,
1143 repo_name=pull_request.target_repo.scm_instance().name,
1144 pull_request_id=pull_request.pull_request_id))
1144 pull_request_id=pull_request.pull_request_id))
1145
1145
1146 response.mustcontain('id="open_edit_pullrequest"')
1146 response.mustcontain('id="open_edit_pullrequest"')
1147 response.mustcontain('id="delete_pullrequest"')
1147 response.mustcontain('id="delete_pullrequest"')
1148 response.mustcontain(no=['Confirm to delete this pull request'])
1148 response.mustcontain(no=['Confirm to delete this pull request'])
1149
1149
1150 def test_delete_comment_returns_404_if_comment_does_not_exist(
1150 def test_delete_comment_returns_404_if_comment_does_not_exist(
1151 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1151 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1152
1152
1153 pull_request = pr_util.create_pull_request(
1153 pull_request = pr_util.create_pull_request(
1154 author=user_admin.username, enable_notifications=False)
1154 author=user_admin.username, enable_notifications=False)
1155
1155
1156 self.app.post(
1156 self.app.post(
1157 route_path(
1157 route_path(
1158 'pullrequest_comment_delete',
1158 'pullrequest_comment_delete',
1159 repo_name=pull_request.target_repo.scm_instance().name,
1159 repo_name=pull_request.target_repo.scm_instance().name,
1160 pull_request_id=pull_request.pull_request_id,
1160 pull_request_id=pull_request.pull_request_id,
1161 comment_id=1024404),
1161 comment_id=1024404),
1162 extra_environ=xhr_header,
1162 extra_environ=xhr_header,
1163 params={'csrf_token': csrf_token},
1163 params={'csrf_token': csrf_token},
1164 status=404
1164 status=404
1165 )
1165 )
1166
1166
1167 def test_delete_comment(
1167 def test_delete_comment(
1168 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1168 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1169
1169
1170 pull_request = pr_util.create_pull_request(
1170 pull_request = pr_util.create_pull_request(
1171 author=user_admin.username, enable_notifications=False)
1171 author=user_admin.username, enable_notifications=False)
1172 comment = pr_util.create_comment()
1172 comment = pr_util.create_comment()
1173 comment_id = comment.comment_id
1173 comment_id = comment.comment_id
1174
1174
1175 response = self.app.post(
1175 response = self.app.post(
1176 route_path(
1176 route_path(
1177 'pullrequest_comment_delete',
1177 'pullrequest_comment_delete',
1178 repo_name=pull_request.target_repo.scm_instance().name,
1178 repo_name=pull_request.target_repo.scm_instance().name,
1179 pull_request_id=pull_request.pull_request_id,
1179 pull_request_id=pull_request.pull_request_id,
1180 comment_id=comment_id),
1180 comment_id=comment_id),
1181 extra_environ=xhr_header,
1181 extra_environ=xhr_header,
1182 params={'csrf_token': csrf_token},
1182 params={'csrf_token': csrf_token},
1183 status=200
1183 status=200
1184 )
1184 )
1185 assert response.body == 'true'
1185 assert response.body == 'true'
1186
1186
1187 @pytest.mark.parametrize('url_type', [
1187 @pytest.mark.parametrize('url_type', [
1188 'pullrequest_new',
1188 'pullrequest_new',
1189 'pullrequest_create',
1189 'pullrequest_create',
1190 'pullrequest_update',
1190 'pullrequest_update',
1191 'pullrequest_merge',
1191 'pullrequest_merge',
1192 ])
1192 ])
1193 def test_pull_request_is_forbidden_on_archived_repo(
1193 def test_pull_request_is_forbidden_on_archived_repo(
1194 self, autologin_user, backend, xhr_header, user_util, url_type):
1194 self, autologin_user, backend, xhr_header, user_util, url_type):
1195
1195
1196 # create a temporary repo
1196 # create a temporary repo
1197 source = user_util.create_repo(repo_type=backend.alias)
1197 source = user_util.create_repo(repo_type=backend.alias)
1198 repo_name = source.repo_name
1198 repo_name = source.repo_name
1199 repo = Repository.get_by_repo_name(repo_name)
1199 repo = Repository.get_by_repo_name(repo_name)
1200 repo.archived = True
1200 repo.archived = True
1201 Session().commit()
1201 Session().commit()
1202
1202
1203 response = self.app.get(
1203 response = self.app.get(
1204 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302)
1204 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302)
1205
1205
1206 msg = 'Action not supported for archived repository.'
1206 msg = 'Action not supported for archived repository.'
1207 assert_session_flash(response, msg)
1207 assert_session_flash(response, msg)
1208
1208
1209
1209
1210 def assert_pull_request_status(pull_request, expected_status):
1210 def assert_pull_request_status(pull_request, expected_status):
1211 status = ChangesetStatusModel().calculated_review_status(
1211 status = ChangesetStatusModel().calculated_review_status(
1212 pull_request=pull_request)
1212 pull_request=pull_request)
1213 assert status == expected_status
1213 assert status == expected_status
1214
1214
1215
1215
1216 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1216 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1217 @pytest.mark.usefixtures("autologin_user")
1217 @pytest.mark.usefixtures("autologin_user")
1218 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1218 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1219 response = app.get(
1219 response = app.get(
1220 route_path(route, repo_name=backend_svn.repo_name), status=404)
1220 route_path(route, repo_name=backend_svn.repo_name), status=404)
1221
1221
@@ -1,151 +1,152 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23 from rhodecode.model.db import Session, UserLog
23 from rhodecode.model.db import Session, UserLog
24 from rhodecode.lib import hooks_base, utils2
24 from rhodecode.lib import hooks_base, utils2
25
25
26
26
27 def test_post_push_truncates_commits(user_regular, repo_stub):
27 def test_post_push_truncates_commits(user_regular, repo_stub):
28 extras = {
28 extras = {
29 'ip': '127.0.0.1',
29 'ip': '127.0.0.1',
30 'username': user_regular.username,
30 'username': user_regular.username,
31 'user_id': user_regular.user_id,
31 'user_id': user_regular.user_id,
32 'action': 'push_local',
32 'action': 'push_local',
33 'repository': repo_stub.repo_name,
33 'repository': repo_stub.repo_name,
34 'scm': 'git',
34 'scm': 'git',
35 'config': '',
35 'config': '',
36 'server_url': 'http://example.com',
36 'server_url': 'http://example.com',
37 'make_lock': None,
37 'make_lock': None,
38 'user_agent': 'some-client',
38 'user_agent': 'some-client',
39 'locked_by': [None],
39 'locked_by': [None],
40 'commit_ids': ['abcde12345' * 4] * 30000,
40 'commit_ids': ['abcde12345' * 4] * 30000,
41 'hook_type': 'large_push_test_type',
41 'hook_type': 'large_push_test_type',
42 'is_shadow_repo': False,
42 'is_shadow_repo': False,
43 }
43 }
44 extras = utils2.AttributeDict(extras)
44 extras = utils2.AttributeDict(extras)
45
45
46 hooks_base.post_push(extras)
46 hooks_base.post_push(extras)
47
47
48 # Calculate appropriate action string here
48 # Calculate appropriate action string here
49 commit_ids = extras.commit_ids[:400]
49 commit_ids = extras.commit_ids[:400]
50
50
51 entry = UserLog.query().order_by('-user_log_id').first()
51 entry = UserLog.query().order_by(UserLog.user_log_id.desc()).first()
52 assert entry.action == 'user.push'
52 assert entry.action == 'user.push'
53 assert entry.action_data['commit_ids'] == commit_ids
53 assert entry.action_data['commit_ids'] == commit_ids
54 Session().delete(entry)
54 Session().delete(entry)
55 Session().commit()
55 Session().commit()
56
56
57
57
58 def assert_called_with_mock(callable_, expected_mock_name):
58 def assert_called_with_mock(callable_, expected_mock_name):
59 mock_obj = callable_.call_args[0][0]
59 mock_obj = callable_.call_args[0][0]
60 mock_name = mock_obj._mock_new_parent._mock_new_name
60 mock_name = mock_obj._mock_new_parent._mock_new_name
61 assert mock_name == expected_mock_name
61 assert mock_name == expected_mock_name
62
62
63
63
64 @pytest.fixture()
64 @pytest.fixture()
65 def hook_extras(user_regular, repo_stub):
65 def hook_extras(user_regular, repo_stub):
66 extras = utils2.AttributeDict({
66 extras = utils2.AttributeDict({
67 'ip': '127.0.0.1',
67 'ip': '127.0.0.1',
68 'username': user_regular.username,
68 'username': user_regular.username,
69 'user_id': user_regular.user_id,
69 'user_id': user_regular.user_id,
70 'action': 'push',
70 'action': 'push',
71 'repository': repo_stub.repo_name,
71 'repository': repo_stub.repo_name,
72 'scm': '',
72 'scm': '',
73 'config': '',
73 'config': '',
74 'repo_store': '',
74 'repo_store': '',
75 'server_url': 'http://example.com',
75 'server_url': 'http://example.com',
76 'make_lock': None,
76 'make_lock': None,
77 'user_agent': 'some-client',
77 'user_agent': 'some-client',
78 'locked_by': [None],
78 'locked_by': [None],
79 'commit_ids': [],
79 'commit_ids': [],
80 'hook_type': 'test_type',
80 'hook_type': 'test_type',
81 'is_shadow_repo': False,
81 'is_shadow_repo': False,
82 })
82 })
83 return extras
83 return extras
84
84
85
85
86 @pytest.mark.parametrize('func, extension, event', [
86 @pytest.mark.parametrize('func, extension, event', [
87 (hooks_base.pre_push, 'pre_push_extension', 'RepoPrePushEvent'),
87 (hooks_base.pre_push, 'pre_push_extension', 'RepoPrePushEvent'),
88 (hooks_base.post_push, 'post_pull_extension', 'RepoPushEvent'),
88 (hooks_base.post_push, 'post_pull_extension', 'RepoPushEvent'),
89 (hooks_base.pre_pull, 'pre_pull_extension', 'RepoPrePullEvent'),
89 (hooks_base.pre_pull, 'pre_pull_extension', 'RepoPrePullEvent'),
90 (hooks_base.post_pull, 'post_push_extension', 'RepoPullEvent'),
90 (hooks_base.post_pull, 'post_push_extension', 'RepoPullEvent'),
91 ])
91 ])
92 def test_hooks_propagate(func, extension, event, hook_extras):
92 def test_hooks_propagate(func, extension, event, hook_extras):
93 """
93 """
94 Tests that our hook code propagates to rhodecode extensions and triggers
94 Tests that our hook code propagates to rhodecode extensions and triggers
95 the appropriate event.
95 the appropriate event.
96 """
96 """
97 class ExtensionMock(mock.Mock):
97 class ExtensionMock(mock.Mock):
98 @property
98 @property
99 def output(self):
99 def output(self):
100 return 'MOCK'
100 return 'MOCK'
101
101
102 extension_mock = ExtensionMock()
102 extension_mock = ExtensionMock()
103 events_mock = mock.Mock()
103 events_mock = mock.Mock()
104 patches = {
104 patches = {
105 'Repository': mock.Mock(),
105 'Repository': mock.Mock(),
106 'events': events_mock,
106 'events': events_mock,
107 extension: extension_mock,
107 extension: extension_mock,
108 }
108 }
109
109
110 # Clear shadow repo flag.
110 # Clear shadow repo flag.
111 hook_extras.is_shadow_repo = False
111 hook_extras.is_shadow_repo = False
112
112
113 # Execute hook function.
113 # Execute hook function.
114 with mock.patch.multiple(hooks_base, **patches):
114 with mock.patch.multiple(hooks_base, **patches):
115 func(hook_extras)
115 func(hook_extras)
116
116
117 # Assert that extensions are called and event was fired.
117 # Assert that extensions are called and event was fired.
118 extension_mock.called_once()
118 extension_mock.called_once()
119 assert_called_with_mock(events_mock.trigger, event)
119 assert_called_with_mock(events_mock.trigger, event)
120
120
121
121
122 @pytest.mark.parametrize('func, extension, event', [
122 @pytest.mark.parametrize('func, extension, event', [
123 (hooks_base.pre_push, 'pre_push_extension', 'RepoPrePushEvent'),
123 (hooks_base.pre_push, 'pre_push_extension', 'RepoPrePushEvent'),
124 (hooks_base.post_push, 'post_pull_extension', 'RepoPushEvent'),
124 (hooks_base.post_push, 'post_pull_extension', 'RepoPushEvent'),
125 (hooks_base.pre_pull, 'pre_pull_extension', 'RepoPrePullEvent'),
125 (hooks_base.pre_pull, 'pre_pull_extension', 'RepoPrePullEvent'),
126 (hooks_base.post_pull, 'post_push_extension', 'RepoPullEvent'),
126 (hooks_base.post_pull, 'post_push_extension', 'RepoPullEvent'),
127 ])
127 ])
128 def test_hooks_propagates_not_on_shadow(func, extension, event, hook_extras):
128 def test_hooks_propagates_not_on_shadow(func, extension, event, hook_extras):
129 """
129 """
130 If hooks are called by a request to a shadow repo we only want to run our
130 If hooks are called by a request to a shadow repo we only want to run our
131 internal hooks code but not external ones like rhodecode extensions or
131 internal hooks code but not external ones like rhodecode extensions or
132 trigger an event.
132 trigger an event.
133 """
133 """
134 extension_mock = mock.Mock()
134 extension_mock = mock.Mock()
135 events_mock = mock.Mock()
135 events_mock = mock.Mock()
136 patches = {
136 patches = {
137 'Repository': mock.Mock(),
137 'Repository': mock.Mock(),
138 'events': events_mock,
138 'events': events_mock,
139 extension: extension_mock,
139 extension: extension_mock,
140 }
140 }
141
141
142 # Set shadow repo flag.
142 # Set shadow repo flag.
143 hook_extras.is_shadow_repo = True
143 hook_extras.is_shadow_repo = True
144
144
145 # Execute hook function.
145 # Execute hook function.
146 with mock.patch.multiple(hooks_base, **patches):
146 with mock.patch.multiple(hooks_base, **patches):
147 func(hook_extras)
147 func(hook_extras)
148
148
149 # Assert that extensions are *not* called and event was *not* fired.
149 # Assert that extensions are *not* called and event was *not* fired.
150 assert not extension_mock.called
150 assert not extension_mock.called
151 assert not events_mock.trigger.called
151 assert not events_mock.trigger.called
152
General Comments 0
You need to be logged in to leave comments. Login now