##// END OF EJS Templates
api: pull-requests, make the repoid parameter optional. Because pullrequestid is global...
marcink -
r2395:a90c6294 default
parent child Browse files
Show More
@@ -1,131 +1,131 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.api.utils import Optional, OAttr
23 from rhodecode.api.utils import Optional, OAttr
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok)
25 build_data, api_call, assert_error, assert_ok)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestApi(object):
29 class TestApi(object):
30 maxDiff = None
30 maxDiff = None
31
31
32 def test_Optional_object(self):
32 def test_Optional_object(self):
33
33
34 option1 = Optional(None)
34 option1 = Optional(None)
35 assert '<Optional:%s>' % (None,) == repr(option1)
35 assert '<Optional:%s>' % (None,) == repr(option1)
36 assert option1() is None
36 assert option1() is None
37
37
38 assert 1 == Optional.extract(Optional(1))
38 assert 1 == Optional.extract(Optional(1))
39 assert 'example' == Optional.extract('example')
39 assert 'example' == Optional.extract('example')
40
40
41 def test_Optional_OAttr(self):
41 def test_Optional_OAttr(self):
42 option1 = Optional(OAttr('apiuser'))
42 option1 = Optional(OAttr('apiuser'))
43 assert 'apiuser' == Optional.extract(option1)
43 assert 'apiuser' == Optional.extract(option1)
44
44
45 def test_OAttr_object(self):
45 def test_OAttr_object(self):
46 oattr1 = OAttr('apiuser')
46 oattr1 = OAttr('apiuser')
47 assert '<OptionalAttr:apiuser>' == repr(oattr1)
47 assert '<OptionalAttr:apiuser>' == repr(oattr1)
48 assert oattr1() == oattr1
48 assert oattr1() == oattr1
49
49
50 def test_api_wrong_key(self):
50 def test_api_wrong_key(self):
51 id_, params = build_data('trololo', 'get_user')
51 id_, params = build_data('trololo', 'get_user')
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53
53
54 expected = 'Invalid API KEY'
54 expected = 'Invalid API KEY'
55 assert_error(id_, expected, given=response.body)
55 assert_error(id_, expected, given=response.body)
56
56
57 def test_api_missing_non_optional_param(self):
57 def test_api_missing_non_optional_param(self):
58 id_, params = build_data(self.apikey, 'get_repo')
58 id_, params = build_data(self.apikey, 'get_repo')
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60
60
61 expected = 'Missing non optional `repoid` arg in JSON DATA'
61 expected = 'Missing non optional `repoid` arg in JSON DATA'
62 assert_error(id_, expected, given=response.body)
62 assert_error(id_, expected, given=response.body)
63
63
64 def test_api_missing_non_optional_param_args_null(self):
64 def test_api_missing_non_optional_param_args_null(self):
65 id_, params = build_data(self.apikey, 'get_repo')
65 id_, params = build_data(self.apikey, 'get_repo')
66 params = params.replace('"args": {}', '"args": null')
66 params = params.replace('"args": {}', '"args": null')
67 response = api_call(self.app, params)
67 response = api_call(self.app, params)
68
68
69 expected = 'Missing non optional `repoid` arg in JSON DATA'
69 expected = 'Missing non optional `repoid` arg in JSON DATA'
70 assert_error(id_, expected, given=response.body)
70 assert_error(id_, expected, given=response.body)
71
71
72 def test_api_missing_non_optional_param_args_bad(self):
72 def test_api_missing_non_optional_param_args_bad(self):
73 id_, params = build_data(self.apikey, 'get_repo')
73 id_, params = build_data(self.apikey, 'get_repo')
74 params = params.replace('"args": {}', '"args": 1')
74 params = params.replace('"args": {}', '"args": 1')
75 response = api_call(self.app, params)
75 response = api_call(self.app, params)
76
76
77 expected = 'Missing non optional `repoid` arg in JSON DATA'
77 expected = 'Missing non optional `repoid` arg in JSON DATA'
78 assert_error(id_, expected, given=response.body)
78 assert_error(id_, expected, given=response.body)
79
79
80 def test_api_non_existing_method(self, request):
80 def test_api_non_existing_method(self, request):
81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 expected = 'No such method: not_existing. Similar methods: none'
83 expected = 'No such method: not_existing. Similar methods: none'
84 assert_error(id_, expected, given=response.body)
84 assert_error(id_, expected, given=response.body)
85
85
86 def test_api_non_existing_method_have_similar(self, request):
86 def test_api_non_existing_method_have_similar(self, request):
87 id_, params = build_data(self.apikey, 'comment', args='xx')
87 id_, params = build_data(self.apikey, 'comment', args='xx')
88 response = api_call(self.app, params)
88 response = api_call(self.app, params)
89 expected = 'No such method: comment. Similar methods: changeset_comment, comment_pull_request, comment_commit'
89 expected = 'No such method: comment. Similar methods: changeset_comment, comment_pull_request, get_pull_request_comments, comment_commit'
90 assert_error(id_, expected, given=response.body)
90 assert_error(id_, expected, given=response.body)
91
91
92 def test_api_disabled_user(self, request):
92 def test_api_disabled_user(self, request):
93
93
94 def set_active(active):
94 def set_active(active):
95 from rhodecode.model.db import Session, User
95 from rhodecode.model.db import Session, User
96 user = User.get_by_auth_token(self.apikey)
96 user = User.get_by_auth_token(self.apikey)
97 user.active = active
97 user.active = active
98 Session().add(user)
98 Session().add(user)
99 Session().commit()
99 Session().commit()
100
100
101 request.addfinalizer(lambda: set_active(True))
101 request.addfinalizer(lambda: set_active(True))
102
102
103 set_active(False)
103 set_active(False)
104 id_, params = build_data(self.apikey, 'test', args='xx')
104 id_, params = build_data(self.apikey, 'test', args='xx')
105 response = api_call(self.app, params)
105 response = api_call(self.app, params)
106 expected = 'Request from this user not allowed'
106 expected = 'Request from this user not allowed'
107 assert_error(id_, expected, given=response.body)
107 assert_error(id_, expected, given=response.body)
108
108
109 def test_api_args_is_null(self):
109 def test_api_args_is_null(self):
110 __, params = build_data(self.apikey, 'get_users', )
110 __, params = build_data(self.apikey, 'get_users', )
111 params = params.replace('"args": {}', '"args": null')
111 params = params.replace('"args": {}', '"args": null')
112 response = api_call(self.app, params)
112 response = api_call(self.app, params)
113 assert response.status == '200 OK'
113 assert response.status == '200 OK'
114
114
115 def test_api_args_is_bad(self):
115 def test_api_args_is_bad(self):
116 __, params = build_data(self.apikey, 'get_users', )
116 __, params = build_data(self.apikey, 'get_users', )
117 params = params.replace('"args": {}', '"args": 1')
117 params = params.replace('"args": {}', '"args": 1')
118 response = api_call(self.app, params)
118 response = api_call(self.app, params)
119 assert response.status == '200 OK'
119 assert response.status == '200 OK'
120
120
121 def test_api_args_different_args(self):
121 def test_api_args_different_args(self):
122 import string
122 import string
123 expected = {
123 expected = {
124 'ascii_letters': string.ascii_letters,
124 'ascii_letters': string.ascii_letters,
125 'ws': string.whitespace,
125 'ws': string.whitespace,
126 'printables': string.printable
126 'printables': string.printable
127 }
127 }
128 id_, params = build_data(self.apikey, 'test', args=expected)
128 id_, params = build_data(self.apikey, 'test', args=expected)
129 response = api_call(self.app, params)
129 response = api_call(self.app, params)
130 assert response.status == '200 OK'
130 assert response.status == '200 OK'
131 assert_ok(id_, expected, response.body)
131 assert_ok(id_, expected, response.body)
@@ -1,112 +1,113 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
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('user_log_id') \
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):
73 def test_api_close_pull_request_repo_error(self, pr_util):
74 pull_request = pr_util.create_pull_request()
74 id_, params = build_data(
75 id_, params = build_data(
75 self.apikey, 'close_pull_request',
76 self.apikey, 'close_pull_request',
76 repoid=666, pullrequestid=1)
77 repoid=666, pullrequestid=pull_request.pull_request_id)
77 response = api_call(self.app, params)
78 response = api_call(self.app, params)
78
79
79 expected = 'repository `666` does not exist'
80 expected = 'repository `666` does not exist'
80 assert_error(id_, expected, given=response.body)
81 assert_error(id_, expected, given=response.body)
81
82
82 @pytest.mark.backends("git", "hg")
83 @pytest.mark.backends("git", "hg")
83 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,
84 pr_util):
85 pr_util):
85 pull_request = pr_util.create_pull_request()
86 pull_request = pr_util.create_pull_request()
86 id_, params = build_data(
87 id_, params = build_data(
87 self.apikey_regular, 'close_pull_request',
88 self.apikey_regular, 'close_pull_request',
88 repoid=pull_request.target_repo.repo_name,
89 repoid=pull_request.target_repo.repo_name,
89 pullrequestid=pull_request.pull_request_id,
90 pullrequestid=pull_request.pull_request_id,
90 userid=TEST_USER_ADMIN_LOGIN)
91 userid=TEST_USER_ADMIN_LOGIN)
91 response = api_call(self.app, params)
92 response = api_call(self.app, params)
92
93
93 expected = 'userid is not the same as your user'
94 expected = 'userid is not the same as your user'
94 assert_error(id_, expected, given=response.body)
95 assert_error(id_, expected, given=response.body)
95
96
96 @pytest.mark.backends("git", "hg")
97 @pytest.mark.backends("git", "hg")
97 def test_api_close_pull_request_no_perms_to_close(
98 def test_api_close_pull_request_no_perms_to_close(
98 self, user_util, pr_util):
99 self, user_util, pr_util):
99 user = user_util.create_user()
100 user = user_util.create_user()
100 pull_request = pr_util.create_pull_request()
101 pull_request = pr_util.create_pull_request()
101
102
102 id_, params = build_data(
103 id_, params = build_data(
103 user.api_key, 'close_pull_request',
104 user.api_key, 'close_pull_request',
104 repoid=pull_request.target_repo.repo_name,
105 repoid=pull_request.target_repo.repo_name,
105 pullrequestid=pull_request.pull_request_id,)
106 pullrequestid=pull_request.pull_request_id,)
106 response = api_call(self.app, params)
107 response = api_call(self.app, params)
107
108
108 expected = ('pull request `%s` close failed, '
109 expected = ('pull request `%s` close failed, '
109 'no permission to close.') % pull_request.pull_request_id
110 'no permission to close.') % pull_request.pull_request_id
110
111
111 response_json = response.json['error']
112 response_json = response.json['error']
112 assert response_json == expected
113 assert response_json == expected
@@ -1,208 +1,209 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
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('user_log_id') \
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):
173 def test_api_comment_pull_request_repo_error(self, pr_util):
174 pull_request = pr_util.create_pull_request()
174 id_, params = build_data(
175 id_, params = build_data(
175 self.apikey, 'comment_pull_request',
176 self.apikey, 'comment_pull_request',
176 repoid=666, pullrequestid=1)
177 repoid=666, pullrequestid=pull_request.pull_request_id)
177 response = api_call(self.app, params)
178 response = api_call(self.app, params)
178
179
179 expected = 'repository `666` does not exist'
180 expected = 'repository `666` does not exist'
180 assert_error(id_, expected, given=response.body)
181 assert_error(id_, expected, given=response.body)
181
182
182 @pytest.mark.backends("git", "hg")
183 @pytest.mark.backends("git", "hg")
183 def test_api_comment_pull_request_non_admin_with_userid_error(
184 def test_api_comment_pull_request_non_admin_with_userid_error(
184 self, pr_util):
185 self, pr_util):
185 pull_request = pr_util.create_pull_request()
186 pull_request = pr_util.create_pull_request()
186 id_, params = build_data(
187 id_, params = build_data(
187 self.apikey_regular, 'comment_pull_request',
188 self.apikey_regular, 'comment_pull_request',
188 repoid=pull_request.target_repo.repo_name,
189 repoid=pull_request.target_repo.repo_name,
189 pullrequestid=pull_request.pull_request_id,
190 pullrequestid=pull_request.pull_request_id,
190 userid=TEST_USER_ADMIN_LOGIN)
191 userid=TEST_USER_ADMIN_LOGIN)
191 response = api_call(self.app, params)
192 response = api_call(self.app, params)
192
193
193 expected = 'userid is not the same as your user'
194 expected = 'userid is not the same as your user'
194 assert_error(id_, expected, given=response.body)
195 assert_error(id_, expected, given=response.body)
195
196
196 @pytest.mark.backends("git", "hg")
197 @pytest.mark.backends("git", "hg")
197 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):
198 pull_request = pr_util.create_pull_request()
199 pull_request = pr_util.create_pull_request()
199 id_, params = build_data(
200 id_, params = build_data(
200 self.apikey_regular, 'comment_pull_request',
201 self.apikey_regular, 'comment_pull_request',
201 repoid=pull_request.target_repo.repo_name,
202 repoid=pull_request.target_repo.repo_name,
202 status='approved',
203 status='approved',
203 pullrequestid=pull_request.pull_request_id,
204 pullrequestid=pull_request.pull_request_id,
204 commit_id='XXX')
205 commit_id='XXX')
205 response = api_call(self.app, params)
206 response = api_call(self.app, params)
206
207
207 expected = 'Invalid commit_id `XXX` for this pull request.'
208 expected = 'Invalid commit_id `XXX` for this pull request.'
208 assert_error(id_, expected, given=response.body)
209 assert_error(id_, expected, given=response.body)
@@ -1,58 +1,59 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGetMethod(object):
28 class TestGetMethod(object):
29 def test_get_methods_no_matches(self):
29 def test_get_methods_no_matches(self):
30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
31 response = api_call(self.app, params)
31 response = api_call(self.app, params)
32
32
33 expected = []
33 expected = []
34 assert_ok(id_, expected, given=response.body)
34 assert_ok(id_, expected, given=response.body)
35
35
36 def test_get_methods(self):
36 def test_get_methods(self):
37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 expected = ['changeset_comment', 'comment_pull_request',
40 expected = ['changeset_comment', 'comment_pull_request',
41 'comment_commit']
41 'get_pull_request_comments', 'comment_commit']
42 assert_ok(id_, expected, given=response.body)
42 assert_ok(id_, expected, given=response.body)
43
43
44 def test_get_methods_on_single_match(self):
44 def test_get_methods_on_single_match(self):
45 id_, params = build_data(self.apikey, 'get_method', pattern='*comment_commit*')
45 id_, params = build_data(self.apikey, 'get_method',
46 pattern='*comment_commit*')
46 response = api_call(self.app, params)
47 response = api_call(self.app, params)
47
48
48 expected = ['comment_commit',
49 expected = ['comment_commit',
49 {'apiuser': '<RequiredType>',
50 {'apiuser': '<RequiredType>',
50 'comment_type': "<Optional:u'note'>",
51 'comment_type': "<Optional:u'note'>",
51 'commit_id': '<RequiredType>',
52 'commit_id': '<RequiredType>',
52 'message': '<RequiredType>',
53 'message': '<RequiredType>',
53 'repoid': '<RequiredType>',
54 'repoid': '<RequiredType>',
54 'request': '<RequiredType>',
55 'request': '<RequiredType>',
55 'resolves_comment_id': '<Optional:None>',
56 'resolves_comment_id': '<Optional:None>',
56 'status': '<Optional:None>',
57 'status': '<Optional:None>',
57 'userid': '<Optional:<OptionalAttr:apiuser>>'}]
58 'userid': '<Optional:<OptionalAttr:apiuser>>'}]
58 assert_ok(id_, expected, given=response.body)
59 assert_ok(id_, expected, given=response.body)
@@ -1,134 +1,142 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23 import urlobject
23 import urlobject
24
24
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.utils2 import safe_unicode
28 from rhodecode.lib.utils2 import safe_unicode
29
29
30 pytestmark = pytest.mark.backends("git", "hg")
30 pytestmark = pytest.mark.backends("git", "hg")
31
31
32
32
33 @pytest.mark.usefixtures("testuser_api", "app")
33 @pytest.mark.usefixtures("testuser_api", "app")
34 class TestGetPullRequest(object):
34 class TestGetPullRequest(object):
35
35
36 def test_api_get_pull_request(self, pr_util, http_host_only_stub):
36 def test_api_get_pull_request(self, pr_util, http_host_only_stub):
37 from rhodecode.model.pull_request import PullRequestModel
37 from rhodecode.model.pull_request import PullRequestModel
38 pull_request = pr_util.create_pull_request(mergeable=True)
38 pull_request = pr_util.create_pull_request(mergeable=True)
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'get_pull_request',
40 self.apikey, 'get_pull_request',
41 repoid=pull_request.target_repo.repo_name,
42 pullrequestid=pull_request.pull_request_id)
41 pullrequestid=pull_request.pull_request_id)
43
42
44 response = api_call(self.app, params)
43 response = api_call(self.app, params)
45
44
46 assert response.status == '200 OK'
45 assert response.status == '200 OK'
47
46
48 url_obj = urlobject.URLObject(
47 url_obj = urlobject.URLObject(
49 h.route_url(
48 h.route_url(
50 'pullrequest_show',
49 'pullrequest_show',
51 repo_name=pull_request.target_repo.repo_name,
50 repo_name=pull_request.target_repo.repo_name,
52 pull_request_id=pull_request.pull_request_id))
51 pull_request_id=pull_request.pull_request_id))
53
52
54 pr_url = safe_unicode(
53 pr_url = safe_unicode(
55 url_obj.with_netloc(http_host_only_stub))
54 url_obj.with_netloc(http_host_only_stub))
56 source_url = safe_unicode(
55 source_url = safe_unicode(
57 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
56 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
58 target_url = safe_unicode(
57 target_url = safe_unicode(
59 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
58 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
60 shadow_url = safe_unicode(
59 shadow_url = safe_unicode(
61 PullRequestModel().get_shadow_clone_url(pull_request))
60 PullRequestModel().get_shadow_clone_url(pull_request))
62
61
63 expected = {
62 expected = {
64 'pull_request_id': pull_request.pull_request_id,
63 'pull_request_id': pull_request.pull_request_id,
65 'url': pr_url,
64 'url': pr_url,
66 'title': pull_request.title,
65 'title': pull_request.title,
67 'description': pull_request.description,
66 'description': pull_request.description,
68 'status': pull_request.status,
67 'status': pull_request.status,
69 'created_on': pull_request.created_on,
68 'created_on': pull_request.created_on,
70 'updated_on': pull_request.updated_on,
69 'updated_on': pull_request.updated_on,
71 'commit_ids': pull_request.revisions,
70 'commit_ids': pull_request.revisions,
72 'review_status': pull_request.calculated_review_status(),
71 'review_status': pull_request.calculated_review_status(),
73 'mergeable': {
72 'mergeable': {
74 'status': True,
73 'status': True,
75 'message': 'This pull request can be automatically merged.',
74 'message': 'This pull request can be automatically merged.',
76 },
75 },
77 'source': {
76 'source': {
78 'clone_url': source_url,
77 'clone_url': source_url,
79 'repository': pull_request.source_repo.repo_name,
78 'repository': pull_request.source_repo.repo_name,
80 'reference': {
79 'reference': {
81 'name': pull_request.source_ref_parts.name,
80 'name': pull_request.source_ref_parts.name,
82 'type': pull_request.source_ref_parts.type,
81 'type': pull_request.source_ref_parts.type,
83 'commit_id': pull_request.source_ref_parts.commit_id,
82 'commit_id': pull_request.source_ref_parts.commit_id,
84 },
83 },
85 },
84 },
86 'target': {
85 'target': {
87 'clone_url': target_url,
86 'clone_url': target_url,
88 'repository': pull_request.target_repo.repo_name,
87 'repository': pull_request.target_repo.repo_name,
89 'reference': {
88 'reference': {
90 'name': pull_request.target_ref_parts.name,
89 'name': pull_request.target_ref_parts.name,
91 'type': pull_request.target_ref_parts.type,
90 'type': pull_request.target_ref_parts.type,
92 'commit_id': pull_request.target_ref_parts.commit_id,
91 'commit_id': pull_request.target_ref_parts.commit_id,
93 },
92 },
94 },
93 },
95 'merge': {
94 'merge': {
96 'clone_url': shadow_url,
95 'clone_url': shadow_url,
97 'reference': {
96 'reference': {
98 'name': pull_request.shadow_merge_ref.name,
97 'name': pull_request.shadow_merge_ref.name,
99 'type': pull_request.shadow_merge_ref.type,
98 'type': pull_request.shadow_merge_ref.type,
100 'commit_id': pull_request.shadow_merge_ref.commit_id,
99 'commit_id': pull_request.shadow_merge_ref.commit_id,
101 },
100 },
102 },
101 },
103 'author': pull_request.author.get_api_data(include_secrets=False,
102 'author': pull_request.author.get_api_data(include_secrets=False,
104 details='basic'),
103 details='basic'),
105 'reviewers': [
104 'reviewers': [
106 {
105 {
107 'user': reviewer.get_api_data(include_secrets=False,
106 'user': reviewer.get_api_data(include_secrets=False,
108 details='basic'),
107 details='basic'),
109 'reasons': reasons,
108 'reasons': reasons,
110 'review_status': st[0][1].status if st else 'not_reviewed',
109 'review_status': st[0][1].status if st else 'not_reviewed',
111 }
110 }
112 for reviewer, reasons, mandatory, st in
111 for reviewer, reasons, mandatory, st in
113 pull_request.reviewers_statuses()
112 pull_request.reviewers_statuses()
114 ]
113 ]
115 }
114 }
116 assert_ok(id_, expected, response.body)
115 assert_ok(id_, expected, response.body)
117
116
118 def test_api_get_pull_request_repo_error(self):
117 def test_api_get_pull_request_repo_error(self, pr_util):
118 pull_request = pr_util.create_pull_request()
119 id_, params = build_data(
119 id_, params = build_data(
120 self.apikey, 'get_pull_request',
120 self.apikey, 'get_pull_request',
121 repoid=666, pullrequestid=1)
121 repoid=666, pullrequestid=pull_request.pull_request_id)
122 response = api_call(self.app, params)
122 response = api_call(self.app, params)
123
123
124 expected = 'repository `666` does not exist'
124 expected = 'repository `666` does not exist'
125 assert_error(id_, expected, given=response.body)
125 assert_error(id_, expected, given=response.body)
126
126
127 def test_api_get_pull_request_pull_request_error(self):
127 def test_api_get_pull_request_pull_request_error(self):
128 id_, params = build_data(
128 id_, params = build_data(
129 self.apikey, 'get_pull_request',
129 self.apikey, 'get_pull_request', pullrequestid=666)
130 repoid=1, pullrequestid=666)
131 response = api_call(self.app, params)
130 response = api_call(self.app, params)
132
131
133 expected = 'pull request `666` does not exist'
132 expected = 'pull request `666` does not exist'
134 assert_error(id_, expected, given=response.body)
133 assert_error(id_, expected, given=response.body)
134
135 def test_api_get_pull_request_pull_request_error_just_pr_id(self):
136 id_, params = build_data(
137 self.apikey, 'get_pull_request',
138 pullrequestid=666)
139 response = api_call(self.app, params)
140
141 expected = 'pull request `666` does not exist'
142 assert_error(id_, expected, given=response.body)
@@ -1,136 +1,137 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
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 @pytest.mark.backends("git", "hg")
32 @pytest.mark.backends("git", "hg")
33 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
33 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
34 pull_request = pr_util.create_pull_request(mergeable=True)
34 pull_request = pr_util.create_pull_request(mergeable=True)
35 author = pull_request.user_id
35 author = pull_request.user_id
36 repo = pull_request.target_repo.repo_id
36 repo = pull_request.target_repo.repo_id
37 pull_request_id = pull_request.pull_request_id
37 pull_request_id = pull_request.pull_request_id
38 pull_request_repo = pull_request.target_repo.repo_name
38 pull_request_repo = pull_request.target_repo.repo_name
39
39
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey, 'merge_pull_request',
41 self.apikey, 'merge_pull_request',
42 repoid=pull_request_repo,
42 repoid=pull_request_repo,
43 pullrequestid=pull_request_id)
43 pullrequestid=pull_request_id)
44
44
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46
46
47 # The above api call detaches the pull request DB object from the
47 # The above api call detaches the pull request DB object from the
48 # session because of an unconditional transaction rollback in our
48 # session because of an unconditional transaction rollback in our
49 # middleware. Therefore we need to add it back here if we want to use
49 # middleware. Therefore we need to add it back here if we want to use
50 # it.
50 # it.
51 Session().add(pull_request)
51 Session().add(pull_request)
52
52
53 expected = 'merge not possible for following reasons: ' \
53 expected = 'merge not possible for following reasons: ' \
54 'Pull request reviewer approval is pending.'
54 'Pull request reviewer approval is pending.'
55 assert_error(id_, expected, given=response.body)
55 assert_error(id_, expected, given=response.body)
56
56
57 @pytest.mark.backends("git", "hg")
57 @pytest.mark.backends("git", "hg")
58 def test_api_merge_pull_request(self, pr_util, no_notifications):
58 def test_api_merge_pull_request(self, pr_util, no_notifications):
59 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
59 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
60 author = pull_request.user_id
60 author = pull_request.user_id
61 repo = pull_request.target_repo.repo_id
61 repo = pull_request.target_repo.repo_id
62 pull_request_id = pull_request.pull_request_id
62 pull_request_id = pull_request.pull_request_id
63 pull_request_repo = pull_request.target_repo.repo_name
63 pull_request_repo = pull_request.target_repo.repo_name
64
64
65 id_, params = build_data(
65 id_, params = build_data(
66 self.apikey, 'comment_pull_request',
66 self.apikey, 'comment_pull_request',
67 repoid=pull_request_repo,
67 repoid=pull_request_repo,
68 pullrequestid=pull_request_id,
68 pullrequestid=pull_request_id,
69 status='approved')
69 status='approved')
70
70
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72 expected = {
72 expected = {
73 'comment_id': response.json.get('result', {}).get('comment_id'),
73 'comment_id': response.json.get('result', {}).get('comment_id'),
74 'pull_request_id': pull_request_id,
74 'pull_request_id': pull_request_id,
75 'status': {'given': 'approved', 'was_changed': True}
75 'status': {'given': 'approved', 'was_changed': True}
76 }
76 }
77 assert_ok(id_, expected, given=response.body)
77 assert_ok(id_, expected, given=response.body)
78
78
79 id_, params = build_data(
79 id_, params = build_data(
80 self.apikey, 'merge_pull_request',
80 self.apikey, 'merge_pull_request',
81 repoid=pull_request_repo,
81 repoid=pull_request_repo,
82 pullrequestid=pull_request_id)
82 pullrequestid=pull_request_id)
83
83
84 response = api_call(self.app, params)
84 response = api_call(self.app, params)
85
85
86 pull_request = PullRequest.get(pull_request_id)
86 pull_request = PullRequest.get(pull_request_id)
87
87
88 expected = {
88 expected = {
89 'executed': True,
89 'executed': True,
90 'failure_reason': 0,
90 'failure_reason': 0,
91 'possible': True,
91 'possible': True,
92 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
92 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
93 'merge_ref': pull_request.shadow_merge_ref._asdict()
93 'merge_ref': pull_request.shadow_merge_ref._asdict()
94 }
94 }
95
95
96 assert_ok(id_, expected, response.body)
96 assert_ok(id_, expected, response.body)
97
97
98 journal = UserLog.query()\
98 journal = UserLog.query()\
99 .filter(UserLog.user_id == author)\
99 .filter(UserLog.user_id == author)\
100 .filter(UserLog.repository_id == repo) \
100 .filter(UserLog.repository_id == repo) \
101 .order_by('user_log_id') \
101 .order_by('user_log_id') \
102 .all()
102 .all()
103 assert journal[-2].action == 'repo.pull_request.merge'
103 assert journal[-2].action == 'repo.pull_request.merge'
104 assert journal[-1].action == 'repo.pull_request.close'
104 assert journal[-1].action == 'repo.pull_request.close'
105
105
106 id_, params = build_data(
106 id_, params = build_data(
107 self.apikey, 'merge_pull_request',
107 self.apikey, 'merge_pull_request',
108 repoid=pull_request_repo, pullrequestid=pull_request_id)
108 repoid=pull_request_repo, pullrequestid=pull_request_id)
109 response = api_call(self.app, params)
109 response = api_call(self.app, params)
110
110
111 expected = 'merge not possible for following reasons: This pull request is closed.'
111 expected = 'merge not possible for following reasons: This pull request is closed.'
112 assert_error(id_, expected, given=response.body)
112 assert_error(id_, expected, given=response.body)
113
113
114 @pytest.mark.backends("git", "hg")
114 @pytest.mark.backends("git", "hg")
115 def test_api_merge_pull_request_repo_error(self):
115 def test_api_merge_pull_request_repo_error(self, pr_util):
116 pull_request = pr_util.create_pull_request()
116 id_, params = build_data(
117 id_, params = build_data(
117 self.apikey, 'merge_pull_request',
118 self.apikey, 'merge_pull_request',
118 repoid=666, pullrequestid=1)
119 repoid=666, pullrequestid=pull_request.pull_request_id)
119 response = api_call(self.app, params)
120 response = api_call(self.app, params)
120
121
121 expected = 'repository `666` does not exist'
122 expected = 'repository `666` does not exist'
122 assert_error(id_, expected, given=response.body)
123 assert_error(id_, expected, given=response.body)
123
124
124 @pytest.mark.backends("git", "hg")
125 @pytest.mark.backends("git", "hg")
125 def test_api_merge_pull_request_non_admin_with_userid_error(self,
126 def test_api_merge_pull_request_non_admin_with_userid_error(self,
126 pr_util):
127 pr_util):
127 pull_request = pr_util.create_pull_request(mergeable=True)
128 pull_request = pr_util.create_pull_request(mergeable=True)
128 id_, params = build_data(
129 id_, params = build_data(
129 self.apikey_regular, 'merge_pull_request',
130 self.apikey_regular, 'merge_pull_request',
130 repoid=pull_request.target_repo.repo_name,
131 repoid=pull_request.target_repo.repo_name,
131 pullrequestid=pull_request.pull_request_id,
132 pullrequestid=pull_request.pull_request_id,
132 userid=TEST_USER_ADMIN_LOGIN)
133 userid=TEST_USER_ADMIN_LOGIN)
133 response = api_call(self.app, params)
134 response = api_call(self.app, params)
134
135
135 expected = 'userid is not the same as your user'
136 expected = 'userid is not the same as your user'
136 assert_error(id_, expected, given=response.body)
137 assert_error(id_, expected, given=response.body)
@@ -1,212 +1,213 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.vcs.nodes import FileNode
23 from rhodecode.lib.vcs.nodes import FileNode
24 from rhodecode.model.db import User
24 from rhodecode.model.db import User
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_ok, assert_error)
28 build_data, api_call, assert_ok, assert_error)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestUpdatePullRequest(object):
32 class TestUpdatePullRequest(object):
33
33
34 @pytest.mark.backends("git", "hg")
34 @pytest.mark.backends("git", "hg")
35 def test_api_update_pull_request_title_or_description(
35 def test_api_update_pull_request_title_or_description(
36 self, pr_util, no_notifications):
36 self, pr_util, no_notifications):
37 pull_request = pr_util.create_pull_request()
37 pull_request = pr_util.create_pull_request()
38
38
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'update_pull_request',
40 self.apikey, 'update_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 title='New TITLE OF A PR',
43 title='New TITLE OF A PR',
44 description='New DESC OF A PR',
44 description='New DESC OF A PR',
45 )
45 )
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47
47
48 expected = {
48 expected = {
49 "msg": "Updated pull request `{}`".format(
49 "msg": "Updated pull request `{}`".format(
50 pull_request.pull_request_id),
50 pull_request.pull_request_id),
51 "pull_request": response.json['result']['pull_request'],
51 "pull_request": response.json['result']['pull_request'],
52 "updated_commits": {"added": [], "common": [], "removed": []},
52 "updated_commits": {"added": [], "common": [], "removed": []},
53 "updated_reviewers": {"added": [], "removed": []},
53 "updated_reviewers": {"added": [], "removed": []},
54 }
54 }
55
55
56 response_json = response.json['result']
56 response_json = response.json['result']
57 assert response_json == expected
57 assert response_json == expected
58 pr = response_json['pull_request']
58 pr = response_json['pull_request']
59 assert pr['title'] == 'New TITLE OF A PR'
59 assert pr['title'] == 'New TITLE OF A PR'
60 assert pr['description'] == 'New DESC OF A PR'
60 assert pr['description'] == 'New DESC OF A PR'
61
61
62 @pytest.mark.backends("git", "hg")
62 @pytest.mark.backends("git", "hg")
63 def test_api_try_update_closed_pull_request(
63 def test_api_try_update_closed_pull_request(
64 self, pr_util, no_notifications):
64 self, pr_util, no_notifications):
65 pull_request = pr_util.create_pull_request()
65 pull_request = pr_util.create_pull_request()
66 PullRequestModel().close_pull_request(
66 PullRequestModel().close_pull_request(
67 pull_request, TEST_USER_ADMIN_LOGIN)
67 pull_request, TEST_USER_ADMIN_LOGIN)
68
68
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey, 'update_pull_request',
70 self.apikey, 'update_pull_request',
71 repoid=pull_request.target_repo.repo_name,
71 repoid=pull_request.target_repo.repo_name,
72 pullrequestid=pull_request.pull_request_id)
72 pullrequestid=pull_request.pull_request_id)
73 response = api_call(self.app, params)
73 response = api_call(self.app, params)
74
74
75 expected = 'pull request `{}` update failed, pull request ' \
75 expected = 'pull request `{}` update failed, pull request ' \
76 'is closed'.format(pull_request.pull_request_id)
76 'is closed'.format(pull_request.pull_request_id)
77
77
78 assert_error(id_, expected, response.body)
78 assert_error(id_, expected, response.body)
79
79
80 @pytest.mark.backends("git", "hg")
80 @pytest.mark.backends("git", "hg")
81 def test_api_update_update_commits(self, pr_util, no_notifications):
81 def test_api_update_update_commits(self, pr_util, no_notifications):
82 commits = [
82 commits = [
83 {'message': 'a'},
83 {'message': 'a'},
84 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
84 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
85 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
85 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
86 ]
86 ]
87 pull_request = pr_util.create_pull_request(
87 pull_request = pr_util.create_pull_request(
88 commits=commits, target_head='a', source_head='b', revisions=['b'])
88 commits=commits, target_head='a', source_head='b', revisions=['b'])
89 pr_util.update_source_repository(head='c')
89 pr_util.update_source_repository(head='c')
90 repo = pull_request.source_repo.scm_instance()
90 repo = pull_request.source_repo.scm_instance()
91 commits = [x for x in repo.get_commits()]
91 commits = [x for x in repo.get_commits()]
92 print commits
92 print commits
93
93
94 added_commit_id = commits[-1].raw_id # c commit
94 added_commit_id = commits[-1].raw_id # c commit
95 common_commit_id = commits[1].raw_id # b commit is common ancestor
95 common_commit_id = commits[1].raw_id # b commit is common ancestor
96 total_commits = [added_commit_id, common_commit_id]
96 total_commits = [added_commit_id, common_commit_id]
97
97
98 id_, params = build_data(
98 id_, params = build_data(
99 self.apikey, 'update_pull_request',
99 self.apikey, 'update_pull_request',
100 repoid=pull_request.target_repo.repo_name,
100 repoid=pull_request.target_repo.repo_name,
101 pullrequestid=pull_request.pull_request_id,
101 pullrequestid=pull_request.pull_request_id,
102 update_commits=True
102 update_commits=True
103 )
103 )
104 response = api_call(self.app, params)
104 response = api_call(self.app, params)
105
105
106 expected = {
106 expected = {
107 "msg": "Updated pull request `{}`".format(
107 "msg": "Updated pull request `{}`".format(
108 pull_request.pull_request_id),
108 pull_request.pull_request_id),
109 "pull_request": response.json['result']['pull_request'],
109 "pull_request": response.json['result']['pull_request'],
110 "updated_commits": {"added": [added_commit_id],
110 "updated_commits": {"added": [added_commit_id],
111 "common": [common_commit_id],
111 "common": [common_commit_id],
112 "total": total_commits,
112 "total": total_commits,
113 "removed": []},
113 "removed": []},
114 "updated_reviewers": {"added": [], "removed": []},
114 "updated_reviewers": {"added": [], "removed": []},
115 }
115 }
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_update_change_reviewers(
120 def test_api_update_change_reviewers(
121 self, user_util, pr_util, no_notifications):
121 self, user_util, pr_util, no_notifications):
122 a = user_util.create_user()
122 a = user_util.create_user()
123 b = user_util.create_user()
123 b = user_util.create_user()
124 c = user_util.create_user()
124 c = user_util.create_user()
125 new_reviewers = [
125 new_reviewers = [
126 {'username': b.username,'reasons': ['updated via API'],
126 {'username': b.username,'reasons': ['updated via API'],
127 'mandatory':False},
127 'mandatory':False},
128 {'username': c.username, 'reasons': ['updated via API'],
128 {'username': c.username, 'reasons': ['updated via API'],
129 'mandatory':False},
129 'mandatory':False},
130 ]
130 ]
131
131
132 added = [b.username, c.username]
132 added = [b.username, c.username]
133 removed = [a.username]
133 removed = [a.username]
134
134
135 pull_request = pr_util.create_pull_request(
135 pull_request = pr_util.create_pull_request(
136 reviewers=[(a.username, ['added via API'], False)])
136 reviewers=[(a.username, ['added via API'], False)])
137
137
138 id_, params = build_data(
138 id_, params = build_data(
139 self.apikey, 'update_pull_request',
139 self.apikey, 'update_pull_request',
140 repoid=pull_request.target_repo.repo_name,
140 repoid=pull_request.target_repo.repo_name,
141 pullrequestid=pull_request.pull_request_id,
141 pullrequestid=pull_request.pull_request_id,
142 reviewers=new_reviewers)
142 reviewers=new_reviewers)
143 response = api_call(self.app, params)
143 response = api_call(self.app, params)
144 expected = {
144 expected = {
145 "msg": "Updated pull request `{}`".format(
145 "msg": "Updated pull request `{}`".format(
146 pull_request.pull_request_id),
146 pull_request.pull_request_id),
147 "pull_request": response.json['result']['pull_request'],
147 "pull_request": response.json['result']['pull_request'],
148 "updated_commits": {"added": [], "common": [], "removed": []},
148 "updated_commits": {"added": [], "common": [], "removed": []},
149 "updated_reviewers": {"added": added, "removed": removed},
149 "updated_reviewers": {"added": added, "removed": removed},
150 }
150 }
151
151
152 assert_ok(id_, expected, response.body)
152 assert_ok(id_, expected, response.body)
153
153
154 @pytest.mark.backends("git", "hg")
154 @pytest.mark.backends("git", "hg")
155 def test_api_update_bad_user_in_reviewers(self, pr_util):
155 def test_api_update_bad_user_in_reviewers(self, pr_util):
156 pull_request = pr_util.create_pull_request()
156 pull_request = pr_util.create_pull_request()
157
157
158 id_, params = build_data(
158 id_, params = build_data(
159 self.apikey, 'update_pull_request',
159 self.apikey, 'update_pull_request',
160 repoid=pull_request.target_repo.repo_name,
160 repoid=pull_request.target_repo.repo_name,
161 pullrequestid=pull_request.pull_request_id,
161 pullrequestid=pull_request.pull_request_id,
162 reviewers=[{'username': 'bad_name'}])
162 reviewers=[{'username': 'bad_name'}])
163 response = api_call(self.app, params)
163 response = api_call(self.app, params)
164
164
165 expected = 'user `bad_name` does not exist'
165 expected = 'user `bad_name` does not exist'
166
166
167 assert_error(id_, expected, response.body)
167 assert_error(id_, expected, response.body)
168
168
169 @pytest.mark.backends("git", "hg")
169 @pytest.mark.backends("git", "hg")
170 def test_api_update_repo_error(self, pr_util):
170 def test_api_update_repo_error(self, pr_util):
171 pull_request = pr_util.create_pull_request()
171 id_, params = build_data(
172 id_, params = build_data(
172 self.apikey, 'update_pull_request',
173 self.apikey, 'update_pull_request',
173 repoid='fake',
174 repoid='fake',
174 pullrequestid='fake',
175 pullrequestid=pull_request.pull_request_id,
175 reviewers=[{'username': 'bad_name'}])
176 reviewers=[{'username': 'bad_name'}])
176 response = api_call(self.app, params)
177 response = api_call(self.app, params)
177
178
178 expected = 'repository `fake` does not exist'
179 expected = 'repository `fake` does not exist'
179
180
180 response_json = response.json['error']
181 response_json = response.json['error']
181 assert response_json == expected
182 assert response_json == expected
182
183
183 @pytest.mark.backends("git", "hg")
184 @pytest.mark.backends("git", "hg")
184 def test_api_update_pull_request_error(self, pr_util):
185 def test_api_update_pull_request_error(self, pr_util):
185 pull_request = pr_util.create_pull_request()
186 pull_request = pr_util.create_pull_request()
186
187
187 id_, params = build_data(
188 id_, params = build_data(
188 self.apikey, 'update_pull_request',
189 self.apikey, 'update_pull_request',
189 repoid=pull_request.target_repo.repo_name,
190 repoid=pull_request.target_repo.repo_name,
190 pullrequestid=999999,
191 pullrequestid=999999,
191 reviewers=[{'username': 'bad_name'}])
192 reviewers=[{'username': 'bad_name'}])
192 response = api_call(self.app, params)
193 response = api_call(self.app, params)
193
194
194 expected = 'pull request `999999` does not exist'
195 expected = 'pull request `999999` does not exist'
195 assert_error(id_, expected, response.body)
196 assert_error(id_, expected, response.body)
196
197
197 @pytest.mark.backends("git", "hg")
198 @pytest.mark.backends("git", "hg")
198 def test_api_update_pull_request_no_perms_to_update(
199 def test_api_update_pull_request_no_perms_to_update(
199 self, user_util, pr_util):
200 self, user_util, pr_util):
200 user = user_util.create_user()
201 user = user_util.create_user()
201 pull_request = pr_util.create_pull_request()
202 pull_request = pr_util.create_pull_request()
202
203
203 id_, params = build_data(
204 id_, params = build_data(
204 user.api_key, 'update_pull_request',
205 user.api_key, 'update_pull_request',
205 repoid=pull_request.target_repo.repo_name,
206 repoid=pull_request.target_repo.repo_name,
206 pullrequestid=pull_request.pull_request_id,)
207 pullrequestid=pull_request.pull_request_id,)
207 response = api_call(self.app, params)
208 response = api_call(self.app, params)
208
209
209 expected = ('pull request `%s` update failed, '
210 expected = ('pull request `%s` update failed, '
210 'no permission to update.') % pull_request.pull_request_id
211 'no permission to update.') % pull_request.pull_request_id
211
212
212 assert_error(id_, expected, response.body)
213 assert_error(id_, expected, response.body)
@@ -1,883 +1,903 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode import events
24 from rhodecode import events
25 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
25 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
26 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
29 validate_repo_permissions, resolve_ref_or_error)
29 validate_repo_permissions, resolve_ref_or_error)
30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
31 from rhodecode.lib.base import vcs_operation_context
31 from rhodecode.lib.base import vcs_operation_context
32 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.lib.utils2 import str2bool
33 from rhodecode.model.changeset_status import ChangesetStatusModel
33 from rhodecode.model.changeset_status import ChangesetStatusModel
34 from rhodecode.model.comment import CommentsModel
34 from rhodecode.model.comment import CommentsModel
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
37 from rhodecode.model.settings import SettingsModel
37 from rhodecode.model.settings import SettingsModel
38 from rhodecode.model.validation_schema import Invalid
38 from rhodecode.model.validation_schema import Invalid
39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
40 ReviewerListSchema)
40 ReviewerListSchema)
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 @jsonrpc_method()
45 @jsonrpc_method()
46 def get_pull_request(request, apiuser, repoid, pullrequestid):
46 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None)):
47 """
47 """
48 Get a pull request based on the given ID.
48 Get a pull request based on the given ID.
49
49
50 :param apiuser: This is filled automatically from the |authtoken|.
50 :param apiuser: This is filled automatically from the |authtoken|.
51 :type apiuser: AuthUser
51 :type apiuser: AuthUser
52 :param repoid: Repository name or repository ID from where the pull
52 :param repoid: Optional, repository name or repository ID from where
53 request was opened.
53 the pull request was opened.
54 :type repoid: str or int
54 :type repoid: str or int
55 :param pullrequestid: ID of the requested pull request.
55 :param pullrequestid: ID of the requested pull request.
56 :type pullrequestid: int
56 :type pullrequestid: int
57
57
58 Example output:
58 Example output:
59
59
60 .. code-block:: bash
60 .. code-block:: bash
61
61
62 "id": <id_given_in_input>,
62 "id": <id_given_in_input>,
63 "result":
63 "result":
64 {
64 {
65 "pull_request_id": "<pull_request_id>",
65 "pull_request_id": "<pull_request_id>",
66 "url": "<url>",
66 "url": "<url>",
67 "title": "<title>",
67 "title": "<title>",
68 "description": "<description>",
68 "description": "<description>",
69 "status" : "<status>",
69 "status" : "<status>",
70 "created_on": "<date_time_created>",
70 "created_on": "<date_time_created>",
71 "updated_on": "<date_time_updated>",
71 "updated_on": "<date_time_updated>",
72 "commit_ids": [
72 "commit_ids": [
73 ...
73 ...
74 "<commit_id>",
74 "<commit_id>",
75 "<commit_id>",
75 "<commit_id>",
76 ...
76 ...
77 ],
77 ],
78 "review_status": "<review_status>",
78 "review_status": "<review_status>",
79 "mergeable": {
79 "mergeable": {
80 "status": "<bool>",
80 "status": "<bool>",
81 "message": "<message>",
81 "message": "<message>",
82 },
82 },
83 "source": {
83 "source": {
84 "clone_url": "<clone_url>",
84 "clone_url": "<clone_url>",
85 "repository": "<repository_name>",
85 "repository": "<repository_name>",
86 "reference":
86 "reference":
87 {
87 {
88 "name": "<name>",
88 "name": "<name>",
89 "type": "<type>",
89 "type": "<type>",
90 "commit_id": "<commit_id>",
90 "commit_id": "<commit_id>",
91 }
91 }
92 },
92 },
93 "target": {
93 "target": {
94 "clone_url": "<clone_url>",
94 "clone_url": "<clone_url>",
95 "repository": "<repository_name>",
95 "repository": "<repository_name>",
96 "reference":
96 "reference":
97 {
97 {
98 "name": "<name>",
98 "name": "<name>",
99 "type": "<type>",
99 "type": "<type>",
100 "commit_id": "<commit_id>",
100 "commit_id": "<commit_id>",
101 }
101 }
102 },
102 },
103 "merge": {
103 "merge": {
104 "clone_url": "<clone_url>",
104 "clone_url": "<clone_url>",
105 "reference":
105 "reference":
106 {
106 {
107 "name": "<name>",
107 "name": "<name>",
108 "type": "<type>",
108 "type": "<type>",
109 "commit_id": "<commit_id>",
109 "commit_id": "<commit_id>",
110 }
110 }
111 },
111 },
112 "author": <user_obj>,
112 "author": <user_obj>,
113 "reviewers": [
113 "reviewers": [
114 ...
114 ...
115 {
115 {
116 "user": "<user_obj>",
116 "user": "<user_obj>",
117 "review_status": "<review_status>",
117 "review_status": "<review_status>",
118 }
118 }
119 ...
119 ...
120 ]
120 ]
121 },
121 },
122 "error": null
122 "error": null
123 """
123 """
124 get_repo_or_error(repoid)
124
125 pull_request = get_pull_request_or_error(pullrequestid)
125 pull_request = get_pull_request_or_error(pullrequestid)
126 if Optional.extract(repoid):
127 repo = get_repo_or_error(repoid)
128 else:
129 repo = pull_request.target_repo
130
126 if not PullRequestModel().check_user_read(
131 if not PullRequestModel().check_user_read(
127 pull_request, apiuser, api=True):
132 pull_request, apiuser, api=True):
128 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
133 raise JSONRPCError('repository `%s` or pull request `%s` '
134 'does not exist' % (repoid, pullrequestid))
129 data = pull_request.get_api_data()
135 data = pull_request.get_api_data()
130 return data
136 return data
131
137
132
138
133 @jsonrpc_method()
139 @jsonrpc_method()
134 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
140 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
135 """
141 """
136 Get all pull requests from the repository specified in `repoid`.
142 Get all pull requests from the repository specified in `repoid`.
137
143
138 :param apiuser: This is filled automatically from the |authtoken|.
144 :param apiuser: This is filled automatically from the |authtoken|.
139 :type apiuser: AuthUser
145 :type apiuser: AuthUser
140 :param repoid: Repository name or repository ID.
146 :param repoid: Optional repository name or repository ID.
141 :type repoid: str or int
147 :type repoid: str or int
142 :param status: Only return pull requests with the specified status.
148 :param status: Only return pull requests with the specified status.
143 Valid options are.
149 Valid options are.
144 * ``new`` (default)
150 * ``new`` (default)
145 * ``open``
151 * ``open``
146 * ``closed``
152 * ``closed``
147 :type status: str
153 :type status: str
148
154
149 Example output:
155 Example output:
150
156
151 .. code-block:: bash
157 .. code-block:: bash
152
158
153 "id": <id_given_in_input>,
159 "id": <id_given_in_input>,
154 "result":
160 "result":
155 [
161 [
156 ...
162 ...
157 {
163 {
158 "pull_request_id": "<pull_request_id>",
164 "pull_request_id": "<pull_request_id>",
159 "url": "<url>",
165 "url": "<url>",
160 "title" : "<title>",
166 "title" : "<title>",
161 "description": "<description>",
167 "description": "<description>",
162 "status": "<status>",
168 "status": "<status>",
163 "created_on": "<date_time_created>",
169 "created_on": "<date_time_created>",
164 "updated_on": "<date_time_updated>",
170 "updated_on": "<date_time_updated>",
165 "commit_ids": [
171 "commit_ids": [
166 ...
172 ...
167 "<commit_id>",
173 "<commit_id>",
168 "<commit_id>",
174 "<commit_id>",
169 ...
175 ...
170 ],
176 ],
171 "review_status": "<review_status>",
177 "review_status": "<review_status>",
172 "mergeable": {
178 "mergeable": {
173 "status": "<bool>",
179 "status": "<bool>",
174 "message: "<message>",
180 "message: "<message>",
175 },
181 },
176 "source": {
182 "source": {
177 "clone_url": "<clone_url>",
183 "clone_url": "<clone_url>",
178 "reference":
184 "reference":
179 {
185 {
180 "name": "<name>",
186 "name": "<name>",
181 "type": "<type>",
187 "type": "<type>",
182 "commit_id": "<commit_id>",
188 "commit_id": "<commit_id>",
183 }
189 }
184 },
190 },
185 "target": {
191 "target": {
186 "clone_url": "<clone_url>",
192 "clone_url": "<clone_url>",
187 "reference":
193 "reference":
188 {
194 {
189 "name": "<name>",
195 "name": "<name>",
190 "type": "<type>",
196 "type": "<type>",
191 "commit_id": "<commit_id>",
197 "commit_id": "<commit_id>",
192 }
198 }
193 },
199 },
194 "merge": {
200 "merge": {
195 "clone_url": "<clone_url>",
201 "clone_url": "<clone_url>",
196 "reference":
202 "reference":
197 {
203 {
198 "name": "<name>",
204 "name": "<name>",
199 "type": "<type>",
205 "type": "<type>",
200 "commit_id": "<commit_id>",
206 "commit_id": "<commit_id>",
201 }
207 }
202 },
208 },
203 "author": <user_obj>,
209 "author": <user_obj>,
204 "reviewers": [
210 "reviewers": [
205 ...
211 ...
206 {
212 {
207 "user": "<user_obj>",
213 "user": "<user_obj>",
208 "review_status": "<review_status>",
214 "review_status": "<review_status>",
209 }
215 }
210 ...
216 ...
211 ]
217 ]
212 }
218 }
213 ...
219 ...
214 ],
220 ],
215 "error": null
221 "error": null
216
222
217 """
223 """
218 repo = get_repo_or_error(repoid)
224 repo = get_repo_or_error(repoid)
219 if not has_superadmin_permission(apiuser):
225 if not has_superadmin_permission(apiuser):
220 _perms = (
226 _perms = (
221 'repository.admin', 'repository.write', 'repository.read',)
227 'repository.admin', 'repository.write', 'repository.read',)
222 validate_repo_permissions(apiuser, repoid, repo, _perms)
228 validate_repo_permissions(apiuser, repoid, repo, _perms)
223
229
224 status = Optional.extract(status)
230 status = Optional.extract(status)
225 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
231 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
226 data = [pr.get_api_data() for pr in pull_requests]
232 data = [pr.get_api_data() for pr in pull_requests]
227 return data
233 return data
228
234
229
235
230 @jsonrpc_method()
236 @jsonrpc_method()
231 def merge_pull_request(
237 def merge_pull_request(
232 request, apiuser, repoid, pullrequestid,
238 request, apiuser, pullrequestid, repoid=Optional(None),
233 userid=Optional(OAttr('apiuser'))):
239 userid=Optional(OAttr('apiuser'))):
234 """
240 """
235 Merge the pull request specified by `pullrequestid` into its target
241 Merge the pull request specified by `pullrequestid` into its target
236 repository.
242 repository.
237
243
238 :param apiuser: This is filled automatically from the |authtoken|.
244 :param apiuser: This is filled automatically from the |authtoken|.
239 :type apiuser: AuthUser
245 :type apiuser: AuthUser
240 :param repoid: The Repository name or repository ID of the
246 :param repoid: Optional, repository name or repository ID of the
241 target repository to which the |pr| is to be merged.
247 target repository to which the |pr| is to be merged.
242 :type repoid: str or int
248 :type repoid: str or int
243 :param pullrequestid: ID of the pull request which shall be merged.
249 :param pullrequestid: ID of the pull request which shall be merged.
244 :type pullrequestid: int
250 :type pullrequestid: int
245 :param userid: Merge the pull request as this user.
251 :param userid: Merge the pull request as this user.
246 :type userid: Optional(str or int)
252 :type userid: Optional(str or int)
247
253
248 Example output:
254 Example output:
249
255
250 .. code-block:: bash
256 .. code-block:: bash
251
257
252 "id": <id_given_in_input>,
258 "id": <id_given_in_input>,
253 "result": {
259 "result": {
254 "executed": "<bool>",
260 "executed": "<bool>",
255 "failure_reason": "<int>",
261 "failure_reason": "<int>",
256 "merge_commit_id": "<merge_commit_id>",
262 "merge_commit_id": "<merge_commit_id>",
257 "possible": "<bool>",
263 "possible": "<bool>",
258 "merge_ref": {
264 "merge_ref": {
259 "commit_id": "<commit_id>",
265 "commit_id": "<commit_id>",
260 "type": "<type>",
266 "type": "<type>",
261 "name": "<name>"
267 "name": "<name>"
262 }
268 }
263 },
269 },
264 "error": null
270 "error": null
265 """
271 """
266 repo = get_repo_or_error(repoid)
272 pull_request = get_pull_request_or_error(pullrequestid)
273 if Optional.extract(repoid):
274 repo = get_repo_or_error(repoid)
275 else:
276 repo = pull_request.target_repo
277
267 if not isinstance(userid, Optional):
278 if not isinstance(userid, Optional):
268 if (has_superadmin_permission(apiuser) or
279 if (has_superadmin_permission(apiuser) or
269 HasRepoPermissionAnyApi('repository.admin')(
280 HasRepoPermissionAnyApi('repository.admin')(
270 user=apiuser, repo_name=repo.repo_name)):
281 user=apiuser, repo_name=repo.repo_name)):
271 apiuser = get_user_or_error(userid)
282 apiuser = get_user_or_error(userid)
272 else:
283 else:
273 raise JSONRPCError('userid is not the same as your user')
284 raise JSONRPCError('userid is not the same as your user')
274
285
275 pull_request = get_pull_request_or_error(pullrequestid)
276
277 check = MergeCheck.validate(
286 check = MergeCheck.validate(
278 pull_request, user=apiuser, translator=request.translate)
287 pull_request, user=apiuser, translator=request.translate)
279 merge_possible = not check.failed
288 merge_possible = not check.failed
280
289
281 if not merge_possible:
290 if not merge_possible:
282 error_messages = []
291 error_messages = []
283 for err_type, error_msg in check.errors:
292 for err_type, error_msg in check.errors:
284 error_msg = request.translate(error_msg)
293 error_msg = request.translate(error_msg)
285 error_messages.append(error_msg)
294 error_messages.append(error_msg)
286
295
287 reasons = ','.join(error_messages)
296 reasons = ','.join(error_messages)
288 raise JSONRPCError(
297 raise JSONRPCError(
289 'merge not possible for following reasons: {}'.format(reasons))
298 'merge not possible for following reasons: {}'.format(reasons))
290
299
291 target_repo = pull_request.target_repo
300 target_repo = pull_request.target_repo
292 extras = vcs_operation_context(
301 extras = vcs_operation_context(
293 request.environ, repo_name=target_repo.repo_name,
302 request.environ, repo_name=target_repo.repo_name,
294 username=apiuser.username, action='push',
303 username=apiuser.username, action='push',
295 scm=target_repo.repo_type)
304 scm=target_repo.repo_type)
296 merge_response = PullRequestModel().merge(
305 merge_response = PullRequestModel().merge(
297 pull_request, apiuser, extras=extras)
306 pull_request, apiuser, extras=extras)
298 if merge_response.executed:
307 if merge_response.executed:
299 PullRequestModel().close_pull_request(
308 PullRequestModel().close_pull_request(
300 pull_request.pull_request_id, apiuser)
309 pull_request.pull_request_id, apiuser)
301
310
302 Session().commit()
311 Session().commit()
303
312
304 # In previous versions the merge response directly contained the merge
313 # In previous versions the merge response directly contained the merge
305 # commit id. It is now contained in the merge reference object. To be
314 # commit id. It is now contained in the merge reference object. To be
306 # backwards compatible we have to extract it again.
315 # backwards compatible we have to extract it again.
307 merge_response = merge_response._asdict()
316 merge_response = merge_response._asdict()
308 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
317 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
309
318
310 return merge_response
319 return merge_response
311
320
312
321
313 @jsonrpc_method()
322 @jsonrpc_method()
314 def get_pull_request_comments(
323 def get_pull_request_comments(
315 request, apiuser, pullrequestid, repoid=Optional(None)):
324 request, apiuser, pullrequestid, repoid=Optional(None)):
316 """
325 """
317 Get all comments of pull request specified with the `pullrequestid`
326 Get all comments of pull request specified with the `pullrequestid`
318
327
319 :param apiuser: This is filled automatically from the |authtoken|.
328 :param apiuser: This is filled automatically from the |authtoken|.
320 :type apiuser: AuthUser
329 :type apiuser: AuthUser
321 :param repoid: Optional repository name or repository ID.
330 :param repoid: Optional repository name or repository ID.
322 :type repoid: str or int
331 :type repoid: str or int
323 :param pullrequestid: The pull request ID.
332 :param pullrequestid: The pull request ID.
324 :type pullrequestid: int
333 :type pullrequestid: int
325
334
326 Example output:
335 Example output:
327
336
328 .. code-block:: bash
337 .. code-block:: bash
329
338
330 id : <id_given_in_input>
339 id : <id_given_in_input>
331 result : [
340 result : [
332 {
341 {
333 "comment_author": {
342 "comment_author": {
334 "active": true,
343 "active": true,
335 "full_name_or_username": "Tom Gore",
344 "full_name_or_username": "Tom Gore",
336 "username": "admin"
345 "username": "admin"
337 },
346 },
338 "comment_created_on": "2017-01-02T18:43:45.533",
347 "comment_created_on": "2017-01-02T18:43:45.533",
339 "comment_f_path": null,
348 "comment_f_path": null,
340 "comment_id": 25,
349 "comment_id": 25,
341 "comment_lineno": null,
350 "comment_lineno": null,
342 "comment_status": {
351 "comment_status": {
343 "status": "under_review",
352 "status": "under_review",
344 "status_lbl": "Under Review"
353 "status_lbl": "Under Review"
345 },
354 },
346 "comment_text": "Example text",
355 "comment_text": "Example text",
347 "comment_type": null,
356 "comment_type": null,
348 "pull_request_version": null
357 "pull_request_version": null
349 }
358 }
350 ],
359 ],
351 error : null
360 error : null
352 """
361 """
353
362
354 pull_request = get_pull_request_or_error(pullrequestid)
363 pull_request = get_pull_request_or_error(pullrequestid)
355 if Optional.extract(repoid):
364 if Optional.extract(repoid):
356 repo = get_repo_or_error(repoid)
365 repo = get_repo_or_error(repoid)
357 else:
366 else:
358 repo = pull_request.target_repo
367 repo = pull_request.target_repo
359
368
360 if not PullRequestModel().check_user_read(
369 if not PullRequestModel().check_user_read(
361 pull_request, apiuser, api=True):
370 pull_request, apiuser, api=True):
362 raise JSONRPCError('repository `%s` or pull request `%s` '
371 raise JSONRPCError('repository `%s` or pull request `%s` '
363 'does not exist' % (repoid, pullrequestid))
372 'does not exist' % (repoid, pullrequestid))
364
373
365 (pull_request_latest,
374 (pull_request_latest,
366 pull_request_at_ver,
375 pull_request_at_ver,
367 pull_request_display_obj,
376 pull_request_display_obj,
368 at_version) = PullRequestModel().get_pr_version(
377 at_version) = PullRequestModel().get_pr_version(
369 pull_request.pull_request_id, version=None)
378 pull_request.pull_request_id, version=None)
370
379
371 versions = pull_request_display_obj.versions()
380 versions = pull_request_display_obj.versions()
372 ver_map = {
381 ver_map = {
373 ver.pull_request_version_id: cnt
382 ver.pull_request_version_id: cnt
374 for cnt, ver in enumerate(versions, 1)
383 for cnt, ver in enumerate(versions, 1)
375 }
384 }
376
385
377 # GENERAL COMMENTS with versions #
386 # GENERAL COMMENTS with versions #
378 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
387 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
379 q = q.order_by(ChangesetComment.comment_id.asc())
388 q = q.order_by(ChangesetComment.comment_id.asc())
380 general_comments = q.all()
389 general_comments = q.all()
381
390
382 # INLINE COMMENTS with versions #
391 # INLINE COMMENTS with versions #
383 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
392 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
384 q = q.order_by(ChangesetComment.comment_id.asc())
393 q = q.order_by(ChangesetComment.comment_id.asc())
385 inline_comments = q.all()
394 inline_comments = q.all()
386
395
387 data = []
396 data = []
388 for comment in inline_comments + general_comments:
397 for comment in inline_comments + general_comments:
389 full_data = comment.get_api_data()
398 full_data = comment.get_api_data()
390 pr_version_id = None
399 pr_version_id = None
391 if comment.pull_request_version_id:
400 if comment.pull_request_version_id:
392 pr_version_id = 'v{}'.format(
401 pr_version_id = 'v{}'.format(
393 ver_map[comment.pull_request_version_id])
402 ver_map[comment.pull_request_version_id])
394
403
395 # sanitize some entries
404 # sanitize some entries
396
405
397 full_data['pull_request_version'] = pr_version_id
406 full_data['pull_request_version'] = pr_version_id
398 full_data['comment_author'] = {
407 full_data['comment_author'] = {
399 'username': full_data['comment_author'].username,
408 'username': full_data['comment_author'].username,
400 'full_name_or_username': full_data['comment_author'].full_name_or_username,
409 'full_name_or_username': full_data['comment_author'].full_name_or_username,
401 'active': full_data['comment_author'].active,
410 'active': full_data['comment_author'].active,
402 }
411 }
403
412
404 if full_data['comment_status']:
413 if full_data['comment_status']:
405 full_data['comment_status'] = {
414 full_data['comment_status'] = {
406 'status': full_data['comment_status'][0].status,
415 'status': full_data['comment_status'][0].status,
407 'status_lbl': full_data['comment_status'][0].status_lbl,
416 'status_lbl': full_data['comment_status'][0].status_lbl,
408 }
417 }
409 else:
418 else:
410 full_data['comment_status'] = {}
419 full_data['comment_status'] = {}
411
420
412 data.append(full_data)
421 data.append(full_data)
413 return data
422 return data
414
423
415
424
416 @jsonrpc_method()
425 @jsonrpc_method()
417 def comment_pull_request(
426 def comment_pull_request(
418 request, apiuser, repoid, pullrequestid, message=Optional(None),
427 request, apiuser, pullrequestid, repoid=Optional(None),
419 commit_id=Optional(None), status=Optional(None),
428 message=Optional(None), commit_id=Optional(None), status=Optional(None),
420 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
429 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
421 resolves_comment_id=Optional(None),
430 resolves_comment_id=Optional(None),
422 userid=Optional(OAttr('apiuser'))):
431 userid=Optional(OAttr('apiuser'))):
423 """
432 """
424 Comment on the pull request specified with the `pullrequestid`,
433 Comment on the pull request specified with the `pullrequestid`,
425 in the |repo| specified by the `repoid`, and optionally change the
434 in the |repo| specified by the `repoid`, and optionally change the
426 review status.
435 review status.
427
436
428 :param apiuser: This is filled automatically from the |authtoken|.
437 :param apiuser: This is filled automatically from the |authtoken|.
429 :type apiuser: AuthUser
438 :type apiuser: AuthUser
430 :param repoid: The repository name or repository ID.
439 :param repoid: Optional repository name or repository ID.
431 :type repoid: str or int
440 :type repoid: str or int
432 :param pullrequestid: The pull request ID.
441 :param pullrequestid: The pull request ID.
433 :type pullrequestid: int
442 :type pullrequestid: int
434 :param commit_id: Specify the commit_id for which to set a comment. If
443 :param commit_id: Specify the commit_id for which to set a comment. If
435 given commit_id is different than latest in the PR status
444 given commit_id is different than latest in the PR status
436 change won't be performed.
445 change won't be performed.
437 :type commit_id: str
446 :type commit_id: str
438 :param message: The text content of the comment.
447 :param message: The text content of the comment.
439 :type message: str
448 :type message: str
440 :param status: (**Optional**) Set the approval status of the pull
449 :param status: (**Optional**) Set the approval status of the pull
441 request. One of: 'not_reviewed', 'approved', 'rejected',
450 request. One of: 'not_reviewed', 'approved', 'rejected',
442 'under_review'
451 'under_review'
443 :type status: str
452 :type status: str
444 :param comment_type: Comment type, one of: 'note', 'todo'
453 :param comment_type: Comment type, one of: 'note', 'todo'
445 :type comment_type: Optional(str), default: 'note'
454 :type comment_type: Optional(str), default: 'note'
446 :param userid: Comment on the pull request as this user
455 :param userid: Comment on the pull request as this user
447 :type userid: Optional(str or int)
456 :type userid: Optional(str or int)
448
457
449 Example output:
458 Example output:
450
459
451 .. code-block:: bash
460 .. code-block:: bash
452
461
453 id : <id_given_in_input>
462 id : <id_given_in_input>
454 result : {
463 result : {
455 "pull_request_id": "<Integer>",
464 "pull_request_id": "<Integer>",
456 "comment_id": "<Integer>",
465 "comment_id": "<Integer>",
457 "status": {"given": <given_status>,
466 "status": {"given": <given_status>,
458 "was_changed": <bool status_was_actually_changed> },
467 "was_changed": <bool status_was_actually_changed> },
459 },
468 },
460 error : null
469 error : null
461 """
470 """
462 repo = get_repo_or_error(repoid)
471 pull_request = get_pull_request_or_error(pullrequestid)
472 if Optional.extract(repoid):
473 repo = get_repo_or_error(repoid)
474 else:
475 repo = pull_request.target_repo
476
463 if not isinstance(userid, Optional):
477 if not isinstance(userid, Optional):
464 if (has_superadmin_permission(apiuser) or
478 if (has_superadmin_permission(apiuser) or
465 HasRepoPermissionAnyApi('repository.admin')(
479 HasRepoPermissionAnyApi('repository.admin')(
466 user=apiuser, repo_name=repo.repo_name)):
480 user=apiuser, repo_name=repo.repo_name)):
467 apiuser = get_user_or_error(userid)
481 apiuser = get_user_or_error(userid)
468 else:
482 else:
469 raise JSONRPCError('userid is not the same as your user')
483 raise JSONRPCError('userid is not the same as your user')
470
484
471 pull_request = get_pull_request_or_error(pullrequestid)
472 if not PullRequestModel().check_user_read(
485 if not PullRequestModel().check_user_read(
473 pull_request, apiuser, api=True):
486 pull_request, apiuser, api=True):
474 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
487 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
475 message = Optional.extract(message)
488 message = Optional.extract(message)
476 status = Optional.extract(status)
489 status = Optional.extract(status)
477 commit_id = Optional.extract(commit_id)
490 commit_id = Optional.extract(commit_id)
478 comment_type = Optional.extract(comment_type)
491 comment_type = Optional.extract(comment_type)
479 resolves_comment_id = Optional.extract(resolves_comment_id)
492 resolves_comment_id = Optional.extract(resolves_comment_id)
480
493
481 if not message and not status:
494 if not message and not status:
482 raise JSONRPCError(
495 raise JSONRPCError(
483 'Both message and status parameters are missing. '
496 'Both message and status parameters are missing. '
484 'At least one is required.')
497 'At least one is required.')
485
498
486 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
499 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
487 status is not None):
500 status is not None):
488 raise JSONRPCError('Unknown comment status: `%s`' % status)
501 raise JSONRPCError('Unknown comment status: `%s`' % status)
489
502
490 if commit_id and commit_id not in pull_request.revisions:
503 if commit_id and commit_id not in pull_request.revisions:
491 raise JSONRPCError(
504 raise JSONRPCError(
492 'Invalid commit_id `%s` for this pull request.' % commit_id)
505 'Invalid commit_id `%s` for this pull request.' % commit_id)
493
506
494 allowed_to_change_status = PullRequestModel().check_user_change_status(
507 allowed_to_change_status = PullRequestModel().check_user_change_status(
495 pull_request, apiuser)
508 pull_request, apiuser)
496
509
497 # if commit_id is passed re-validated if user is allowed to change status
510 # if commit_id is passed re-validated if user is allowed to change status
498 # based on latest commit_id from the PR
511 # based on latest commit_id from the PR
499 if commit_id:
512 if commit_id:
500 commit_idx = pull_request.revisions.index(commit_id)
513 commit_idx = pull_request.revisions.index(commit_id)
501 if commit_idx != 0:
514 if commit_idx != 0:
502 allowed_to_change_status = False
515 allowed_to_change_status = False
503
516
504 if resolves_comment_id:
517 if resolves_comment_id:
505 comment = ChangesetComment.get(resolves_comment_id)
518 comment = ChangesetComment.get(resolves_comment_id)
506 if not comment:
519 if not comment:
507 raise JSONRPCError(
520 raise JSONRPCError(
508 'Invalid resolves_comment_id `%s` for this pull request.'
521 'Invalid resolves_comment_id `%s` for this pull request.'
509 % resolves_comment_id)
522 % resolves_comment_id)
510 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
523 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
511 raise JSONRPCError(
524 raise JSONRPCError(
512 'Comment `%s` is wrong type for setting status to resolved.'
525 'Comment `%s` is wrong type for setting status to resolved.'
513 % resolves_comment_id)
526 % resolves_comment_id)
514
527
515 text = message
528 text = message
516 status_label = ChangesetStatus.get_status_lbl(status)
529 status_label = ChangesetStatus.get_status_lbl(status)
517 if status and allowed_to_change_status:
530 if status and allowed_to_change_status:
518 st_message = ('Status change %(transition_icon)s %(status)s'
531 st_message = ('Status change %(transition_icon)s %(status)s'
519 % {'transition_icon': '>', 'status': status_label})
532 % {'transition_icon': '>', 'status': status_label})
520 text = message or st_message
533 text = message or st_message
521
534
522 rc_config = SettingsModel().get_all_settings()
535 rc_config = SettingsModel().get_all_settings()
523 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
536 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
524
537
525 status_change = status and allowed_to_change_status
538 status_change = status and allowed_to_change_status
526 comment = CommentsModel().create(
539 comment = CommentsModel().create(
527 text=text,
540 text=text,
528 repo=pull_request.target_repo.repo_id,
541 repo=pull_request.target_repo.repo_id,
529 user=apiuser.user_id,
542 user=apiuser.user_id,
530 pull_request=pull_request.pull_request_id,
543 pull_request=pull_request.pull_request_id,
531 f_path=None,
544 f_path=None,
532 line_no=None,
545 line_no=None,
533 status_change=(status_label if status_change else None),
546 status_change=(status_label if status_change else None),
534 status_change_type=(status if status_change else None),
547 status_change_type=(status if status_change else None),
535 closing_pr=False,
548 closing_pr=False,
536 renderer=renderer,
549 renderer=renderer,
537 comment_type=comment_type,
550 comment_type=comment_type,
538 resolves_comment_id=resolves_comment_id
551 resolves_comment_id=resolves_comment_id
539 )
552 )
540
553
541 if allowed_to_change_status and status:
554 if allowed_to_change_status and status:
542 ChangesetStatusModel().set_status(
555 ChangesetStatusModel().set_status(
543 pull_request.target_repo.repo_id,
556 pull_request.target_repo.repo_id,
544 status,
557 status,
545 apiuser.user_id,
558 apiuser.user_id,
546 comment,
559 comment,
547 pull_request=pull_request.pull_request_id
560 pull_request=pull_request.pull_request_id
548 )
561 )
549 Session().flush()
562 Session().flush()
550
563
551 Session().commit()
564 Session().commit()
552 data = {
565 data = {
553 'pull_request_id': pull_request.pull_request_id,
566 'pull_request_id': pull_request.pull_request_id,
554 'comment_id': comment.comment_id if comment else None,
567 'comment_id': comment.comment_id if comment else None,
555 'status': {'given': status, 'was_changed': status_change},
568 'status': {'given': status, 'was_changed': status_change},
556 }
569 }
557 return data
570 return data
558
571
559
572
560 @jsonrpc_method()
573 @jsonrpc_method()
561 def create_pull_request(
574 def create_pull_request(
562 request, apiuser, source_repo, target_repo, source_ref, target_ref,
575 request, apiuser, source_repo, target_repo, source_ref, target_ref,
563 title, description=Optional(''), reviewers=Optional(None)):
576 title, description=Optional(''), reviewers=Optional(None)):
564 """
577 """
565 Creates a new pull request.
578 Creates a new pull request.
566
579
567 Accepts refs in the following formats:
580 Accepts refs in the following formats:
568
581
569 * branch:<branch_name>:<sha>
582 * branch:<branch_name>:<sha>
570 * branch:<branch_name>
583 * branch:<branch_name>
571 * bookmark:<bookmark_name>:<sha> (Mercurial only)
584 * bookmark:<bookmark_name>:<sha> (Mercurial only)
572 * bookmark:<bookmark_name> (Mercurial only)
585 * bookmark:<bookmark_name> (Mercurial only)
573
586
574 :param apiuser: This is filled automatically from the |authtoken|.
587 :param apiuser: This is filled automatically from the |authtoken|.
575 :type apiuser: AuthUser
588 :type apiuser: AuthUser
576 :param source_repo: Set the source repository name.
589 :param source_repo: Set the source repository name.
577 :type source_repo: str
590 :type source_repo: str
578 :param target_repo: Set the target repository name.
591 :param target_repo: Set the target repository name.
579 :type target_repo: str
592 :type target_repo: str
580 :param source_ref: Set the source ref name.
593 :param source_ref: Set the source ref name.
581 :type source_ref: str
594 :type source_ref: str
582 :param target_ref: Set the target ref name.
595 :param target_ref: Set the target ref name.
583 :type target_ref: str
596 :type target_ref: str
584 :param title: Set the pull request title.
597 :param title: Set the pull request title.
585 :type title: str
598 :type title: str
586 :param description: Set the pull request description.
599 :param description: Set the pull request description.
587 :type description: Optional(str)
600 :type description: Optional(str)
588 :param reviewers: Set the new pull request reviewers list.
601 :param reviewers: Set the new pull request reviewers list.
589 Reviewer defined by review rules will be added automatically to the
602 Reviewer defined by review rules will be added automatically to the
590 defined list.
603 defined list.
591 :type reviewers: Optional(list)
604 :type reviewers: Optional(list)
592 Accepts username strings or objects of the format:
605 Accepts username strings or objects of the format:
593
606
594 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
607 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
595 """
608 """
596
609
597 source_db_repo = get_repo_or_error(source_repo)
610 source_db_repo = get_repo_or_error(source_repo)
598 target_db_repo = get_repo_or_error(target_repo)
611 target_db_repo = get_repo_or_error(target_repo)
599 if not has_superadmin_permission(apiuser):
612 if not has_superadmin_permission(apiuser):
600 _perms = ('repository.admin', 'repository.write', 'repository.read',)
613 _perms = ('repository.admin', 'repository.write', 'repository.read',)
601 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
614 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
602
615
603 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
616 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
604 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
617 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
605 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
618 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
606 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
619 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
607 source_scm = source_db_repo.scm_instance()
620 source_scm = source_db_repo.scm_instance()
608 target_scm = target_db_repo.scm_instance()
621 target_scm = target_db_repo.scm_instance()
609
622
610 commit_ranges = target_scm.compare(
623 commit_ranges = target_scm.compare(
611 target_commit.raw_id, source_commit.raw_id, source_scm,
624 target_commit.raw_id, source_commit.raw_id, source_scm,
612 merge=True, pre_load=[])
625 merge=True, pre_load=[])
613
626
614 ancestor = target_scm.get_common_ancestor(
627 ancestor = target_scm.get_common_ancestor(
615 target_commit.raw_id, source_commit.raw_id, source_scm)
628 target_commit.raw_id, source_commit.raw_id, source_scm)
616
629
617 if not commit_ranges:
630 if not commit_ranges:
618 raise JSONRPCError('no commits found')
631 raise JSONRPCError('no commits found')
619
632
620 if not ancestor:
633 if not ancestor:
621 raise JSONRPCError('no common ancestor found')
634 raise JSONRPCError('no common ancestor found')
622
635
623 reviewer_objects = Optional.extract(reviewers) or []
636 reviewer_objects = Optional.extract(reviewers) or []
624
637
625 if reviewer_objects:
638 if reviewer_objects:
626 schema = ReviewerListSchema()
639 schema = ReviewerListSchema()
627 try:
640 try:
628 reviewer_objects = schema.deserialize(reviewer_objects)
641 reviewer_objects = schema.deserialize(reviewer_objects)
629 except Invalid as err:
642 except Invalid as err:
630 raise JSONRPCValidationError(colander_exc=err)
643 raise JSONRPCValidationError(colander_exc=err)
631
644
632 # validate users
645 # validate users
633 for reviewer_object in reviewer_objects:
646 for reviewer_object in reviewer_objects:
634 user = get_user_or_error(reviewer_object['username'])
647 user = get_user_or_error(reviewer_object['username'])
635 reviewer_object['user_id'] = user.user_id
648 reviewer_object['user_id'] = user.user_id
636
649
637 get_default_reviewers_data, get_validated_reviewers = \
650 get_default_reviewers_data, get_validated_reviewers = \
638 PullRequestModel().get_reviewer_functions()
651 PullRequestModel().get_reviewer_functions()
639
652
640 reviewer_rules = get_default_reviewers_data(
653 reviewer_rules = get_default_reviewers_data(
641 apiuser.get_instance(), source_db_repo,
654 apiuser.get_instance(), source_db_repo,
642 source_commit, target_db_repo, target_commit)
655 source_commit, target_db_repo, target_commit)
643
656
644 # specified rules are later re-validated, thus we can assume users will
657 # specified rules are later re-validated, thus we can assume users will
645 # eventually provide those that meet the reviewer criteria.
658 # eventually provide those that meet the reviewer criteria.
646 if not reviewer_objects:
659 if not reviewer_objects:
647 reviewer_objects = reviewer_rules['reviewers']
660 reviewer_objects = reviewer_rules['reviewers']
648
661
649 try:
662 try:
650 reviewers = get_validated_reviewers(
663 reviewers = get_validated_reviewers(
651 reviewer_objects, reviewer_rules)
664 reviewer_objects, reviewer_rules)
652 except ValueError as e:
665 except ValueError as e:
653 raise JSONRPCError('Reviewers Validation: {}'.format(e))
666 raise JSONRPCError('Reviewers Validation: {}'.format(e))
654
667
655 pull_request_model = PullRequestModel()
668 pull_request_model = PullRequestModel()
656 pull_request = pull_request_model.create(
669 pull_request = pull_request_model.create(
657 created_by=apiuser.user_id,
670 created_by=apiuser.user_id,
658 source_repo=source_repo,
671 source_repo=source_repo,
659 source_ref=full_source_ref,
672 source_ref=full_source_ref,
660 target_repo=target_repo,
673 target_repo=target_repo,
661 target_ref=full_target_ref,
674 target_ref=full_target_ref,
662 revisions=reversed(
675 revisions=reversed(
663 [commit.raw_id for commit in reversed(commit_ranges)]),
676 [commit.raw_id for commit in reversed(commit_ranges)]),
664 reviewers=reviewers,
677 reviewers=reviewers,
665 title=title,
678 title=title,
666 description=Optional.extract(description)
679 description=Optional.extract(description)
667 )
680 )
668
681
669 Session().commit()
682 Session().commit()
670 data = {
683 data = {
671 'msg': 'Created new pull request `{}`'.format(title),
684 'msg': 'Created new pull request `{}`'.format(title),
672 'pull_request_id': pull_request.pull_request_id,
685 'pull_request_id': pull_request.pull_request_id,
673 }
686 }
674 return data
687 return data
675
688
676
689
677 @jsonrpc_method()
690 @jsonrpc_method()
678 def update_pull_request(
691 def update_pull_request(
679 request, apiuser, repoid, pullrequestid, title=Optional(''),
692 request, apiuser, pullrequestid, repoid=Optional(None),
680 description=Optional(''), reviewers=Optional(None),
693 title=Optional(''), description=Optional(''), reviewers=Optional(None),
681 update_commits=Optional(None)):
694 update_commits=Optional(None)):
682 """
695 """
683 Updates a pull request.
696 Updates a pull request.
684
697
685 :param apiuser: This is filled automatically from the |authtoken|.
698 :param apiuser: This is filled automatically from the |authtoken|.
686 :type apiuser: AuthUser
699 :type apiuser: AuthUser
687 :param repoid: The repository name or repository ID.
700 :param repoid: Optional repository name or repository ID.
688 :type repoid: str or int
701 :type repoid: str or int
689 :param pullrequestid: The pull request ID.
702 :param pullrequestid: The pull request ID.
690 :type pullrequestid: int
703 :type pullrequestid: int
691 :param title: Set the pull request title.
704 :param title: Set the pull request title.
692 :type title: str
705 :type title: str
693 :param description: Update pull request description.
706 :param description: Update pull request description.
694 :type description: Optional(str)
707 :type description: Optional(str)
695 :param reviewers: Update pull request reviewers list with new value.
708 :param reviewers: Update pull request reviewers list with new value.
696 :type reviewers: Optional(list)
709 :type reviewers: Optional(list)
697 Accepts username strings or objects of the format:
710 Accepts username strings or objects of the format:
698
711
699 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
712 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
700
713
701 :param update_commits: Trigger update of commits for this pull request
714 :param update_commits: Trigger update of commits for this pull request
702 :type: update_commits: Optional(bool)
715 :type: update_commits: Optional(bool)
703
716
704 Example output:
717 Example output:
705
718
706 .. code-block:: bash
719 .. code-block:: bash
707
720
708 id : <id_given_in_input>
721 id : <id_given_in_input>
709 result : {
722 result : {
710 "msg": "Updated pull request `63`",
723 "msg": "Updated pull request `63`",
711 "pull_request": <pull_request_object>,
724 "pull_request": <pull_request_object>,
712 "updated_reviewers": {
725 "updated_reviewers": {
713 "added": [
726 "added": [
714 "username"
727 "username"
715 ],
728 ],
716 "removed": []
729 "removed": []
717 },
730 },
718 "updated_commits": {
731 "updated_commits": {
719 "added": [
732 "added": [
720 "<sha1_hash>"
733 "<sha1_hash>"
721 ],
734 ],
722 "common": [
735 "common": [
723 "<sha1_hash>",
736 "<sha1_hash>",
724 "<sha1_hash>",
737 "<sha1_hash>",
725 ],
738 ],
726 "removed": []
739 "removed": []
727 }
740 }
728 }
741 }
729 error : null
742 error : null
730 """
743 """
731
744
732 repo = get_repo_or_error(repoid)
733 pull_request = get_pull_request_or_error(pullrequestid)
745 pull_request = get_pull_request_or_error(pullrequestid)
746 if Optional.extract(repoid):
747 repo = get_repo_or_error(repoid)
748 else:
749 repo = pull_request.target_repo
750
734 if not PullRequestModel().check_user_update(
751 if not PullRequestModel().check_user_update(
735 pull_request, apiuser, api=True):
752 pull_request, apiuser, api=True):
736 raise JSONRPCError(
753 raise JSONRPCError(
737 'pull request `%s` update failed, no permission to update.' % (
754 'pull request `%s` update failed, no permission to update.' % (
738 pullrequestid,))
755 pullrequestid,))
739 if pull_request.is_closed():
756 if pull_request.is_closed():
740 raise JSONRPCError(
757 raise JSONRPCError(
741 'pull request `%s` update failed, pull request is closed' % (
758 'pull request `%s` update failed, pull request is closed' % (
742 pullrequestid,))
759 pullrequestid,))
743
760
744 reviewer_objects = Optional.extract(reviewers) or []
761 reviewer_objects = Optional.extract(reviewers) or []
745
762
746 if reviewer_objects:
763 if reviewer_objects:
747 schema = ReviewerListSchema()
764 schema = ReviewerListSchema()
748 try:
765 try:
749 reviewer_objects = schema.deserialize(reviewer_objects)
766 reviewer_objects = schema.deserialize(reviewer_objects)
750 except Invalid as err:
767 except Invalid as err:
751 raise JSONRPCValidationError(colander_exc=err)
768 raise JSONRPCValidationError(colander_exc=err)
752
769
753 # validate users
770 # validate users
754 for reviewer_object in reviewer_objects:
771 for reviewer_object in reviewer_objects:
755 user = get_user_or_error(reviewer_object['username'])
772 user = get_user_or_error(reviewer_object['username'])
756 reviewer_object['user_id'] = user.user_id
773 reviewer_object['user_id'] = user.user_id
757
774
758 get_default_reviewers_data, get_validated_reviewers = \
775 get_default_reviewers_data, get_validated_reviewers = \
759 PullRequestModel().get_reviewer_functions()
776 PullRequestModel().get_reviewer_functions()
760
777
761 # re-use stored rules
778 # re-use stored rules
762 reviewer_rules = pull_request.reviewer_data
779 reviewer_rules = pull_request.reviewer_data
763 try:
780 try:
764 reviewers = get_validated_reviewers(
781 reviewers = get_validated_reviewers(
765 reviewer_objects, reviewer_rules)
782 reviewer_objects, reviewer_rules)
766 except ValueError as e:
783 except ValueError as e:
767 raise JSONRPCError('Reviewers Validation: {}'.format(e))
784 raise JSONRPCError('Reviewers Validation: {}'.format(e))
768 else:
785 else:
769 reviewers = []
786 reviewers = []
770
787
771 title = Optional.extract(title)
788 title = Optional.extract(title)
772 description = Optional.extract(description)
789 description = Optional.extract(description)
773 if title or description:
790 if title or description:
774 PullRequestModel().edit(
791 PullRequestModel().edit(
775 pull_request, title or pull_request.title,
792 pull_request, title or pull_request.title,
776 description or pull_request.description, apiuser)
793 description or pull_request.description, apiuser)
777 Session().commit()
794 Session().commit()
778
795
779 commit_changes = {"added": [], "common": [], "removed": []}
796 commit_changes = {"added": [], "common": [], "removed": []}
780 if str2bool(Optional.extract(update_commits)):
797 if str2bool(Optional.extract(update_commits)):
781 if PullRequestModel().has_valid_update_type(pull_request):
798 if PullRequestModel().has_valid_update_type(pull_request):
782 update_response = PullRequestModel().update_commits(
799 update_response = PullRequestModel().update_commits(
783 pull_request)
800 pull_request)
784 commit_changes = update_response.changes or commit_changes
801 commit_changes = update_response.changes or commit_changes
785 Session().commit()
802 Session().commit()
786
803
787 reviewers_changes = {"added": [], "removed": []}
804 reviewers_changes = {"added": [], "removed": []}
788 if reviewers:
805 if reviewers:
789 added_reviewers, removed_reviewers = \
806 added_reviewers, removed_reviewers = \
790 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
807 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
791
808
792 reviewers_changes['added'] = sorted(
809 reviewers_changes['added'] = sorted(
793 [get_user_or_error(n).username for n in added_reviewers])
810 [get_user_or_error(n).username for n in added_reviewers])
794 reviewers_changes['removed'] = sorted(
811 reviewers_changes['removed'] = sorted(
795 [get_user_or_error(n).username for n in removed_reviewers])
812 [get_user_or_error(n).username for n in removed_reviewers])
796 Session().commit()
813 Session().commit()
797
814
798 data = {
815 data = {
799 'msg': 'Updated pull request `{}`'.format(
816 'msg': 'Updated pull request `{}`'.format(
800 pull_request.pull_request_id),
817 pull_request.pull_request_id),
801 'pull_request': pull_request.get_api_data(),
818 'pull_request': pull_request.get_api_data(),
802 'updated_commits': commit_changes,
819 'updated_commits': commit_changes,
803 'updated_reviewers': reviewers_changes
820 'updated_reviewers': reviewers_changes
804 }
821 }
805
822
806 return data
823 return data
807
824
808
825
809 @jsonrpc_method()
826 @jsonrpc_method()
810 def close_pull_request(
827 def close_pull_request(
811 request, apiuser, repoid, pullrequestid,
828 request, apiuser, pullrequestid, repoid=Optional(None),
812 userid=Optional(OAttr('apiuser')), message=Optional('')):
829 userid=Optional(OAttr('apiuser')), message=Optional('')):
813 """
830 """
814 Close the pull request specified by `pullrequestid`.
831 Close the pull request specified by `pullrequestid`.
815
832
816 :param apiuser: This is filled automatically from the |authtoken|.
833 :param apiuser: This is filled automatically from the |authtoken|.
817 :type apiuser: AuthUser
834 :type apiuser: AuthUser
818 :param repoid: Repository name or repository ID to which the pull
835 :param repoid: Repository name or repository ID to which the pull
819 request belongs.
836 request belongs.
820 :type repoid: str or int
837 :type repoid: str or int
821 :param pullrequestid: ID of the pull request to be closed.
838 :param pullrequestid: ID of the pull request to be closed.
822 :type pullrequestid: int
839 :type pullrequestid: int
823 :param userid: Close the pull request as this user.
840 :param userid: Close the pull request as this user.
824 :type userid: Optional(str or int)
841 :type userid: Optional(str or int)
825 :param message: Optional message to close the Pull Request with. If not
842 :param message: Optional message to close the Pull Request with. If not
826 specified it will be generated automatically.
843 specified it will be generated automatically.
827 :type message: Optional(str)
844 :type message: Optional(str)
828
845
829 Example output:
846 Example output:
830
847
831 .. code-block:: bash
848 .. code-block:: bash
832
849
833 "id": <id_given_in_input>,
850 "id": <id_given_in_input>,
834 "result": {
851 "result": {
835 "pull_request_id": "<int>",
852 "pull_request_id": "<int>",
836 "close_status": "<str:status_lbl>,
853 "close_status": "<str:status_lbl>,
837 "closed": "<bool>"
854 "closed": "<bool>"
838 },
855 },
839 "error": null
856 "error": null
840
857
841 """
858 """
842 _ = request.translate
859 _ = request.translate
843
860
844 repo = get_repo_or_error(repoid)
861 pull_request = get_pull_request_or_error(pullrequestid)
862 if Optional.extract(repoid):
863 repo = get_repo_or_error(repoid)
864 else:
865 repo = pull_request.target_repo
866
845 if not isinstance(userid, Optional):
867 if not isinstance(userid, Optional):
846 if (has_superadmin_permission(apiuser) or
868 if (has_superadmin_permission(apiuser) or
847 HasRepoPermissionAnyApi('repository.admin')(
869 HasRepoPermissionAnyApi('repository.admin')(
848 user=apiuser, repo_name=repo.repo_name)):
870 user=apiuser, repo_name=repo.repo_name)):
849 apiuser = get_user_or_error(userid)
871 apiuser = get_user_or_error(userid)
850 else:
872 else:
851 raise JSONRPCError('userid is not the same as your user')
873 raise JSONRPCError('userid is not the same as your user')
852
874
853 pull_request = get_pull_request_or_error(pullrequestid)
854
855 if pull_request.is_closed():
875 if pull_request.is_closed():
856 raise JSONRPCError(
876 raise JSONRPCError(
857 'pull request `%s` is already closed' % (pullrequestid,))
877 'pull request `%s` is already closed' % (pullrequestid,))
858
878
859 # only owner or admin or person with write permissions
879 # only owner or admin or person with write permissions
860 allowed_to_close = PullRequestModel().check_user_update(
880 allowed_to_close = PullRequestModel().check_user_update(
861 pull_request, apiuser, api=True)
881 pull_request, apiuser, api=True)
862
882
863 if not allowed_to_close:
883 if not allowed_to_close:
864 raise JSONRPCError(
884 raise JSONRPCError(
865 'pull request `%s` close failed, no permission to close.' % (
885 'pull request `%s` close failed, no permission to close.' % (
866 pullrequestid,))
886 pullrequestid,))
867
887
868 # message we're using to close the PR, else it's automatically generated
888 # message we're using to close the PR, else it's automatically generated
869 message = Optional.extract(message)
889 message = Optional.extract(message)
870
890
871 # finally close the PR, with proper message comment
891 # finally close the PR, with proper message comment
872 comment, status = PullRequestModel().close_pull_request_with_comment(
892 comment, status = PullRequestModel().close_pull_request_with_comment(
873 pull_request, apiuser, repo, message=message)
893 pull_request, apiuser, repo, message=message)
874 status_lbl = ChangesetStatus.get_status_lbl(status)
894 status_lbl = ChangesetStatus.get_status_lbl(status)
875
895
876 Session().commit()
896 Session().commit()
877
897
878 data = {
898 data = {
879 'pull_request_id': pull_request.pull_request_id,
899 'pull_request_id': pull_request.pull_request_id,
880 'close_status': status_lbl,
900 'close_status': status_lbl,
881 'closed': True,
901 'closed': True,
882 }
902 }
883 return data
903 return data
General Comments 0
You need to be logged in to leave comments. Login now