##// END OF EJS Templates
test: fixes for tests for PR versions.
marcink -
r1373:0a5c49a9 default
parent child Browse files
Show More
@@ -1,207 +1,205 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 (build_data, api_call)
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_ok, assert_error)
28
29
29
30
30 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestUpdatePullRequest(object):
32 class TestUpdatePullRequest(object):
32
33
33 @pytest.mark.backends("git", "hg")
34 @pytest.mark.backends("git", "hg")
34 def test_api_update_pull_request_title_or_description(
35 def test_api_update_pull_request_title_or_description(
35 self, pr_util, silence_action_logger, no_notifications):
36 self, pr_util, silence_action_logger, no_notifications):
36 pull_request = pr_util.create_pull_request()
37 pull_request = pr_util.create_pull_request()
37
38
38 id_, params = build_data(
39 id_, params = build_data(
39 self.apikey, 'update_pull_request',
40 self.apikey, 'update_pull_request',
40 repoid=pull_request.target_repo.repo_name,
41 repoid=pull_request.target_repo.repo_name,
41 pullrequestid=pull_request.pull_request_id,
42 pullrequestid=pull_request.pull_request_id,
42 title='New TITLE OF A PR',
43 title='New TITLE OF A PR',
43 description='New DESC OF A PR',
44 description='New DESC OF A PR',
44 )
45 )
45 response = api_call(self.app, params)
46 response = api_call(self.app, params)
46
47
47 expected = {
48 expected = {
48 "msg": "Updated pull request `{}`".format(
49 "msg": "Updated pull request `{}`".format(
49 pull_request.pull_request_id),
50 pull_request.pull_request_id),
50 "pull_request": response.json['result']['pull_request'],
51 "pull_request": response.json['result']['pull_request'],
51 "updated_commits": {"added": [], "common": [], "removed": []},
52 "updated_commits": {"added": [], "common": [], "removed": []},
52 "updated_reviewers": {"added": [], "removed": []},
53 "updated_reviewers": {"added": [], "removed": []},
53 }
54 }
54
55
55 response_json = response.json['result']
56 response_json = response.json['result']
56 assert response_json == expected
57 assert response_json == expected
57 pr = response_json['pull_request']
58 pr = response_json['pull_request']
58 assert pr['title'] == 'New TITLE OF A PR'
59 assert pr['title'] == 'New TITLE OF A PR'
59 assert pr['description'] == 'New DESC OF A PR'
60 assert pr['description'] == 'New DESC OF A PR'
60
61
61 @pytest.mark.backends("git", "hg")
62 @pytest.mark.backends("git", "hg")
62 def test_api_try_update_closed_pull_request(
63 def test_api_try_update_closed_pull_request(
63 self, pr_util, silence_action_logger, no_notifications):
64 self, pr_util, silence_action_logger, no_notifications):
64 pull_request = pr_util.create_pull_request()
65 pull_request = pr_util.create_pull_request()
65 PullRequestModel().close_pull_request(
66 PullRequestModel().close_pull_request(
66 pull_request, TEST_USER_ADMIN_LOGIN)
67 pull_request, TEST_USER_ADMIN_LOGIN)
67
68
68 id_, params = build_data(
69 id_, params = build_data(
69 self.apikey, 'update_pull_request',
70 self.apikey, 'update_pull_request',
70 repoid=pull_request.target_repo.repo_name,
71 repoid=pull_request.target_repo.repo_name,
71 pullrequestid=pull_request.pull_request_id)
72 pullrequestid=pull_request.pull_request_id)
72 response = api_call(self.app, params)
73 response = api_call(self.app, params)
73
74
74 expected = 'pull request `{}` update failed, pull request ' \
75 expected = 'pull request `{}` update failed, pull request ' \
75 'is closed'.format(pull_request.pull_request_id)
76 'is closed'.format(pull_request.pull_request_id)
76
77
77 response_json = response.json['error']
78 assert_error(id_, expected, response.body)
78 assert response_json == expected
79
79
80 @pytest.mark.backends("git", "hg")
80 @pytest.mark.backends("git", "hg")
81 def test_api_update_update_commits(
81 def test_api_update_update_commits(
82 self, pr_util, silence_action_logger, no_notifications):
82 self, pr_util, silence_action_logger, no_notifications):
83 commits = [
83 commits = [
84 {'message': 'a'},
84 {'message': 'a'},
85 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
85 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
86 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
86 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
87 ]
87 ]
88 pull_request = pr_util.create_pull_request(
88 pull_request = pr_util.create_pull_request(
89 commits=commits, target_head='a', source_head='b', revisions=['b'])
89 commits=commits, target_head='a', source_head='b', revisions=['b'])
90 pr_util.update_source_repository(head='c')
90 pr_util.update_source_repository(head='c')
91 repo = pull_request.source_repo.scm_instance()
91 repo = pull_request.source_repo.scm_instance()
92 commits = [x for x in repo.get_commits()]
92 commits = [x for x in repo.get_commits()]
93 print commits
93
94
94 added_commit_id = commits[-1].raw_id # c commit
95 added_commit_id = commits[-1].raw_id # c commit
95 common_commits = commits[1].raw_id # b commit is common ancestor
96 common_commit_id = commits[1].raw_id # b commit is common ancestor
97 total_commits = [added_commit_id, common_commit_id]
96
98
97 id_, params = build_data(
99 id_, params = build_data(
98 self.apikey, 'update_pull_request',
100 self.apikey, 'update_pull_request',
99 repoid=pull_request.target_repo.repo_name,
101 repoid=pull_request.target_repo.repo_name,
100 pullrequestid=pull_request.pull_request_id,
102 pullrequestid=pull_request.pull_request_id,
101 update_commits=True
103 update_commits=True
102 )
104 )
103 response = api_call(self.app, params)
105 response = api_call(self.app, params)
104
106
105 expected = {
107 expected = {
106 "msg": "Updated pull request `{}`".format(
108 "msg": "Updated pull request `{}`".format(
107 pull_request.pull_request_id),
109 pull_request.pull_request_id),
108 "pull_request": response.json['result']['pull_request'],
110 "pull_request": response.json['result']['pull_request'],
109 "updated_commits": {"added": [added_commit_id],
111 "updated_commits": {"added": [added_commit_id],
110 "common": [common_commits], "removed": []},
112 "common": [common_commit_id],
113 "total": total_commits,
114 "removed": []},
111 "updated_reviewers": {"added": [], "removed": []},
115 "updated_reviewers": {"added": [], "removed": []},
112 }
116 }
113
117
114 response_json = response.json['result']
118 assert_ok(id_, expected, response.body)
115 assert response_json == expected
116
119
117 @pytest.mark.backends("git", "hg")
120 @pytest.mark.backends("git", "hg")
118 def test_api_update_change_reviewers(
121 def test_api_update_change_reviewers(
119 self, pr_util, silence_action_logger, no_notifications):
122 self, pr_util, silence_action_logger, no_notifications):
120
123
121 users = [x.username for x in User.get_all()]
124 users = [x.username for x in User.get_all()]
122 new = [users.pop(0)]
125 new = [users.pop(0)]
123 removed = sorted(new)
126 removed = sorted(new)
124 added = sorted(users)
127 added = sorted(users)
125
128
126 pull_request = pr_util.create_pull_request(reviewers=new)
129 pull_request = pr_util.create_pull_request(reviewers=new)
127
130
128 id_, params = build_data(
131 id_, params = build_data(
129 self.apikey, 'update_pull_request',
132 self.apikey, 'update_pull_request',
130 repoid=pull_request.target_repo.repo_name,
133 repoid=pull_request.target_repo.repo_name,
131 pullrequestid=pull_request.pull_request_id,
134 pullrequestid=pull_request.pull_request_id,
132 reviewers=added)
135 reviewers=added)
133 response = api_call(self.app, params)
136 response = api_call(self.app, params)
134 expected = {
137 expected = {
135 "msg": "Updated pull request `{}`".format(
138 "msg": "Updated pull request `{}`".format(
136 pull_request.pull_request_id),
139 pull_request.pull_request_id),
137 "pull_request": response.json['result']['pull_request'],
140 "pull_request": response.json['result']['pull_request'],
138 "updated_commits": {"added": [], "common": [], "removed": []},
141 "updated_commits": {"added": [], "common": [], "removed": []},
139 "updated_reviewers": {"added": added, "removed": removed},
142 "updated_reviewers": {"added": added, "removed": removed},
140 }
143 }
141
144
142 response_json = response.json['result']
145 assert_ok(id_, expected, response.body)
143 assert response_json == expected
144
146
145 @pytest.mark.backends("git", "hg")
147 @pytest.mark.backends("git", "hg")
146 def test_api_update_bad_user_in_reviewers(self, pr_util):
148 def test_api_update_bad_user_in_reviewers(self, pr_util):
147 pull_request = pr_util.create_pull_request()
149 pull_request = pr_util.create_pull_request()
148
150
149 id_, params = build_data(
151 id_, params = build_data(
150 self.apikey, 'update_pull_request',
152 self.apikey, 'update_pull_request',
151 repoid=pull_request.target_repo.repo_name,
153 repoid=pull_request.target_repo.repo_name,
152 pullrequestid=pull_request.pull_request_id,
154 pullrequestid=pull_request.pull_request_id,
153 reviewers=['bad_name'])
155 reviewers=['bad_name'])
154 response = api_call(self.app, params)
156 response = api_call(self.app, params)
155
157
156 expected = 'user `bad_name` does not exist'
158 expected = 'user `bad_name` does not exist'
157
159
158 response_json = response.json['error']
160 assert_error(id_, expected, response.body)
159 assert response_json == expected
160
161
161 @pytest.mark.backends("git", "hg")
162 @pytest.mark.backends("git", "hg")
162 def test_api_update_repo_error(self, pr_util):
163 def test_api_update_repo_error(self, pr_util):
163 id_, params = build_data(
164 id_, params = build_data(
164 self.apikey, 'update_pull_request',
165 self.apikey, 'update_pull_request',
165 repoid='fake',
166 repoid='fake',
166 pullrequestid='fake',
167 pullrequestid='fake',
167 reviewers=['bad_name'])
168 reviewers=['bad_name'])
168 response = api_call(self.app, params)
169 response = api_call(self.app, params)
169
170
170 expected = 'repository `fake` does not exist'
171 expected = 'repository `fake` does not exist'
171
172
172 response_json = response.json['error']
173 response_json = response.json['error']
173 assert response_json == expected
174 assert response_json == expected
174
175
175 @pytest.mark.backends("git", "hg")
176 @pytest.mark.backends("git", "hg")
176 def test_api_update_pull_request_error(self, pr_util):
177 def test_api_update_pull_request_error(self, pr_util):
177 pull_request = pr_util.create_pull_request()
178 pull_request = pr_util.create_pull_request()
178
179
179 id_, params = build_data(
180 id_, params = build_data(
180 self.apikey, 'update_pull_request',
181 self.apikey, 'update_pull_request',
181 repoid=pull_request.target_repo.repo_name,
182 repoid=pull_request.target_repo.repo_name,
182 pullrequestid=999999,
183 pullrequestid=999999,
183 reviewers=['bad_name'])
184 reviewers=['bad_name'])
184 response = api_call(self.app, params)
185 response = api_call(self.app, params)
185
186
186 expected = 'pull request `999999` does not exist'
187 expected = 'pull request `999999` does not exist'
187
188 assert_error(id_, expected, response.body)
188 response_json = response.json['error']
189 assert response_json == expected
190
189
191 @pytest.mark.backends("git", "hg")
190 @pytest.mark.backends("git", "hg")
192 def test_api_update_pull_request_no_perms_to_update(
191 def test_api_update_pull_request_no_perms_to_update(
193 self, user_util, pr_util):
192 self, user_util, pr_util):
194 user = user_util.create_user()
193 user = user_util.create_user()
195 pull_request = pr_util.create_pull_request()
194 pull_request = pr_util.create_pull_request()
196
195
197 id_, params = build_data(
196 id_, params = build_data(
198 user.api_key, 'update_pull_request',
197 user.api_key, 'update_pull_request',
199 repoid=pull_request.target_repo.repo_name,
198 repoid=pull_request.target_repo.repo_name,
200 pullrequestid=pull_request.pull_request_id,)
199 pullrequestid=pull_request.pull_request_id,)
201 response = api_call(self.app, params)
200 response = api_call(self.app, params)
202
201
203 expected = ('pull request `%s` update failed, '
202 expected = ('pull request `%s` update failed, '
204 'no permission to update.') % pull_request.pull_request_id
203 'no permission to update.') % pull_request.pull_request_id
205
204
206 response_json = response.json['error']
205 assert_error(id_, expected, response.body)
207 assert response_json == expected
@@ -1,846 +1,852 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 mock
21 import mock
22 import pytest
22 import pytest
23 import textwrap
23 import textwrap
24
24
25 import rhodecode
25 import rhodecode
26 from rhodecode.lib.utils2 import safe_unicode
26 from rhodecode.lib.utils2 import safe_unicode
27 from rhodecode.lib.vcs.backends import get_backend
27 from rhodecode.lib.vcs.backends import get_backend
28 from rhodecode.lib.vcs.backends.base import (
28 from rhodecode.lib.vcs.backends.base import (
29 MergeResponse, MergeFailureReason, Reference)
29 MergeResponse, MergeFailureReason, Reference)
30 from rhodecode.lib.vcs.exceptions import RepositoryError
30 from rhodecode.lib.vcs.exceptions import RepositoryError
31 from rhodecode.lib.vcs.nodes import FileNode
31 from rhodecode.lib.vcs.nodes import FileNode
32 from rhodecode.model.comment import CommentsModel
32 from rhodecode.model.comment import CommentsModel
33 from rhodecode.model.db import PullRequest, Session
33 from rhodecode.model.db import PullRequest, Session
34 from rhodecode.model.pull_request import PullRequestModel
34 from rhodecode.model.pull_request import PullRequestModel
35 from rhodecode.model.user import UserModel
35 from rhodecode.model.user import UserModel
36 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
36 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
37
37
38
38
39 pytestmark = [
39 pytestmark = [
40 pytest.mark.backends("git", "hg"),
40 pytest.mark.backends("git", "hg"),
41 ]
41 ]
42
42
43
43
44 class TestPullRequestModel:
44 class TestPullRequestModel:
45
45
46 @pytest.fixture
46 @pytest.fixture
47 def pull_request(self, request, backend, pr_util):
47 def pull_request(self, request, backend, pr_util):
48 """
48 """
49 A pull request combined with multiples patches.
49 A pull request combined with multiples patches.
50 """
50 """
51 BackendClass = get_backend(backend.alias)
51 BackendClass = get_backend(backend.alias)
52 self.merge_patcher = mock.patch.object(BackendClass, 'merge')
52 self.merge_patcher = mock.patch.object(BackendClass, 'merge')
53 self.workspace_remove_patcher = mock.patch.object(
53 self.workspace_remove_patcher = mock.patch.object(
54 BackendClass, 'cleanup_merge_workspace')
54 BackendClass, 'cleanup_merge_workspace')
55
55
56 self.workspace_remove_mock = self.workspace_remove_patcher.start()
56 self.workspace_remove_mock = self.workspace_remove_patcher.start()
57 self.merge_mock = self.merge_patcher.start()
57 self.merge_mock = self.merge_patcher.start()
58 self.comment_patcher = mock.patch(
58 self.comment_patcher = mock.patch(
59 'rhodecode.model.changeset_status.ChangesetStatusModel.set_status')
59 'rhodecode.model.changeset_status.ChangesetStatusModel.set_status')
60 self.comment_patcher.start()
60 self.comment_patcher.start()
61 self.notification_patcher = mock.patch(
61 self.notification_patcher = mock.patch(
62 'rhodecode.model.notification.NotificationModel.create')
62 'rhodecode.model.notification.NotificationModel.create')
63 self.notification_patcher.start()
63 self.notification_patcher.start()
64 self.helper_patcher = mock.patch(
64 self.helper_patcher = mock.patch(
65 'rhodecode.lib.helpers.url')
65 'rhodecode.lib.helpers.url')
66 self.helper_patcher.start()
66 self.helper_patcher.start()
67
67
68 self.hook_patcher = mock.patch.object(PullRequestModel,
68 self.hook_patcher = mock.patch.object(PullRequestModel,
69 '_trigger_pull_request_hook')
69 '_trigger_pull_request_hook')
70 self.hook_mock = self.hook_patcher.start()
70 self.hook_mock = self.hook_patcher.start()
71
71
72 self.invalidation_patcher = mock.patch(
72 self.invalidation_patcher = mock.patch(
73 'rhodecode.model.pull_request.ScmModel.mark_for_invalidation')
73 'rhodecode.model.pull_request.ScmModel.mark_for_invalidation')
74 self.invalidation_mock = self.invalidation_patcher.start()
74 self.invalidation_mock = self.invalidation_patcher.start()
75
75
76 self.pull_request = pr_util.create_pull_request(
76 self.pull_request = pr_util.create_pull_request(
77 mergeable=True, name_suffix=u'Δ…Δ‡')
77 mergeable=True, name_suffix=u'Δ…Δ‡')
78 self.source_commit = self.pull_request.source_ref_parts.commit_id
78 self.source_commit = self.pull_request.source_ref_parts.commit_id
79 self.target_commit = self.pull_request.target_ref_parts.commit_id
79 self.target_commit = self.pull_request.target_ref_parts.commit_id
80 self.workspace_id = 'pr-%s' % self.pull_request.pull_request_id
80 self.workspace_id = 'pr-%s' % self.pull_request.pull_request_id
81
81
82 @request.addfinalizer
82 @request.addfinalizer
83 def cleanup_pull_request():
83 def cleanup_pull_request():
84 calls = [mock.call(
84 calls = [mock.call(
85 self.pull_request, self.pull_request.author, 'create')]
85 self.pull_request, self.pull_request.author, 'create')]
86 self.hook_mock.assert_has_calls(calls)
86 self.hook_mock.assert_has_calls(calls)
87
87
88 self.workspace_remove_patcher.stop()
88 self.workspace_remove_patcher.stop()
89 self.merge_patcher.stop()
89 self.merge_patcher.stop()
90 self.comment_patcher.stop()
90 self.comment_patcher.stop()
91 self.notification_patcher.stop()
91 self.notification_patcher.stop()
92 self.helper_patcher.stop()
92 self.helper_patcher.stop()
93 self.hook_patcher.stop()
93 self.hook_patcher.stop()
94 self.invalidation_patcher.stop()
94 self.invalidation_patcher.stop()
95
95
96 return self.pull_request
96 return self.pull_request
97
97
98 def test_get_all(self, pull_request):
98 def test_get_all(self, pull_request):
99 prs = PullRequestModel().get_all(pull_request.target_repo)
99 prs = PullRequestModel().get_all(pull_request.target_repo)
100 assert isinstance(prs, list)
100 assert isinstance(prs, list)
101 assert len(prs) == 1
101 assert len(prs) == 1
102
102
103 def test_count_all(self, pull_request):
103 def test_count_all(self, pull_request):
104 pr_count = PullRequestModel().count_all(pull_request.target_repo)
104 pr_count = PullRequestModel().count_all(pull_request.target_repo)
105 assert pr_count == 1
105 assert pr_count == 1
106
106
107 def test_get_awaiting_review(self, pull_request):
107 def test_get_awaiting_review(self, pull_request):
108 prs = PullRequestModel().get_awaiting_review(pull_request.target_repo)
108 prs = PullRequestModel().get_awaiting_review(pull_request.target_repo)
109 assert isinstance(prs, list)
109 assert isinstance(prs, list)
110 assert len(prs) == 1
110 assert len(prs) == 1
111
111
112 def test_count_awaiting_review(self, pull_request):
112 def test_count_awaiting_review(self, pull_request):
113 pr_count = PullRequestModel().count_awaiting_review(
113 pr_count = PullRequestModel().count_awaiting_review(
114 pull_request.target_repo)
114 pull_request.target_repo)
115 assert pr_count == 1
115 assert pr_count == 1
116
116
117 def test_get_awaiting_my_review(self, pull_request):
117 def test_get_awaiting_my_review(self, pull_request):
118 PullRequestModel().update_reviewers(
118 PullRequestModel().update_reviewers(
119 pull_request, [(pull_request.author, ['author'])])
119 pull_request, [(pull_request.author, ['author'])])
120 prs = PullRequestModel().get_awaiting_my_review(
120 prs = PullRequestModel().get_awaiting_my_review(
121 pull_request.target_repo, user_id=pull_request.author.user_id)
121 pull_request.target_repo, user_id=pull_request.author.user_id)
122 assert isinstance(prs, list)
122 assert isinstance(prs, list)
123 assert len(prs) == 1
123 assert len(prs) == 1
124
124
125 def test_count_awaiting_my_review(self, pull_request):
125 def test_count_awaiting_my_review(self, pull_request):
126 PullRequestModel().update_reviewers(
126 PullRequestModel().update_reviewers(
127 pull_request, [(pull_request.author, ['author'])])
127 pull_request, [(pull_request.author, ['author'])])
128 pr_count = PullRequestModel().count_awaiting_my_review(
128 pr_count = PullRequestModel().count_awaiting_my_review(
129 pull_request.target_repo, user_id=pull_request.author.user_id)
129 pull_request.target_repo, user_id=pull_request.author.user_id)
130 assert pr_count == 1
130 assert pr_count == 1
131
131
132 def test_delete_calls_cleanup_merge(self, pull_request):
132 def test_delete_calls_cleanup_merge(self, pull_request):
133 PullRequestModel().delete(pull_request)
133 PullRequestModel().delete(pull_request)
134
134
135 self.workspace_remove_mock.assert_called_once_with(
135 self.workspace_remove_mock.assert_called_once_with(
136 self.workspace_id)
136 self.workspace_id)
137
137
138 def test_close_calls_cleanup_and_hook(self, pull_request):
138 def test_close_calls_cleanup_and_hook(self, pull_request):
139 PullRequestModel().close_pull_request(
139 PullRequestModel().close_pull_request(
140 pull_request, pull_request.author)
140 pull_request, pull_request.author)
141
141
142 self.workspace_remove_mock.assert_called_once_with(
142 self.workspace_remove_mock.assert_called_once_with(
143 self.workspace_id)
143 self.workspace_id)
144 self.hook_mock.assert_called_with(
144 self.hook_mock.assert_called_with(
145 self.pull_request, self.pull_request.author, 'close')
145 self.pull_request, self.pull_request.author, 'close')
146
146
147 def test_merge_status(self, pull_request):
147 def test_merge_status(self, pull_request):
148 self.merge_mock.return_value = MergeResponse(
148 self.merge_mock.return_value = MergeResponse(
149 True, False, None, MergeFailureReason.NONE)
149 True, False, None, MergeFailureReason.NONE)
150
150
151 assert pull_request._last_merge_source_rev is None
151 assert pull_request._last_merge_source_rev is None
152 assert pull_request._last_merge_target_rev is None
152 assert pull_request._last_merge_target_rev is None
153 assert pull_request._last_merge_status is None
153 assert pull_request._last_merge_status is None
154
154
155 status, msg = PullRequestModel().merge_status(pull_request)
155 status, msg = PullRequestModel().merge_status(pull_request)
156 assert status is True
156 assert status is True
157 assert msg.eval() == 'This pull request can be automatically merged.'
157 assert msg.eval() == 'This pull request can be automatically merged.'
158 self.merge_mock.assert_called_once_with(
158 self.merge_mock.assert_called_once_with(
159 pull_request.target_ref_parts,
159 pull_request.target_ref_parts,
160 pull_request.source_repo.scm_instance(),
160 pull_request.source_repo.scm_instance(),
161 pull_request.source_ref_parts, self.workspace_id, dry_run=True,
161 pull_request.source_ref_parts, self.workspace_id, dry_run=True,
162 use_rebase=False)
162 use_rebase=False)
163
163
164 assert pull_request._last_merge_source_rev == self.source_commit
164 assert pull_request._last_merge_source_rev == self.source_commit
165 assert pull_request._last_merge_target_rev == self.target_commit
165 assert pull_request._last_merge_target_rev == self.target_commit
166 assert pull_request._last_merge_status is MergeFailureReason.NONE
166 assert pull_request._last_merge_status is MergeFailureReason.NONE
167
167
168 self.merge_mock.reset_mock()
168 self.merge_mock.reset_mock()
169 status, msg = PullRequestModel().merge_status(pull_request)
169 status, msg = PullRequestModel().merge_status(pull_request)
170 assert status is True
170 assert status is True
171 assert msg.eval() == 'This pull request can be automatically merged.'
171 assert msg.eval() == 'This pull request can be automatically merged.'
172 assert self.merge_mock.called is False
172 assert self.merge_mock.called is False
173
173
174 def test_merge_status_known_failure(self, pull_request):
174 def test_merge_status_known_failure(self, pull_request):
175 self.merge_mock.return_value = MergeResponse(
175 self.merge_mock.return_value = MergeResponse(
176 False, False, None, MergeFailureReason.MERGE_FAILED)
176 False, False, None, MergeFailureReason.MERGE_FAILED)
177
177
178 assert pull_request._last_merge_source_rev is None
178 assert pull_request._last_merge_source_rev is None
179 assert pull_request._last_merge_target_rev is None
179 assert pull_request._last_merge_target_rev is None
180 assert pull_request._last_merge_status is None
180 assert pull_request._last_merge_status is None
181
181
182 status, msg = PullRequestModel().merge_status(pull_request)
182 status, msg = PullRequestModel().merge_status(pull_request)
183 assert status is False
183 assert status is False
184 assert (
184 assert (
185 msg.eval() ==
185 msg.eval() ==
186 'This pull request cannot be merged because of merge conflicts.')
186 'This pull request cannot be merged because of merge conflicts.')
187 self.merge_mock.assert_called_once_with(
187 self.merge_mock.assert_called_once_with(
188 pull_request.target_ref_parts,
188 pull_request.target_ref_parts,
189 pull_request.source_repo.scm_instance(),
189 pull_request.source_repo.scm_instance(),
190 pull_request.source_ref_parts, self.workspace_id, dry_run=True,
190 pull_request.source_ref_parts, self.workspace_id, dry_run=True,
191 use_rebase=False)
191 use_rebase=False)
192
192
193 assert pull_request._last_merge_source_rev == self.source_commit
193 assert pull_request._last_merge_source_rev == self.source_commit
194 assert pull_request._last_merge_target_rev == self.target_commit
194 assert pull_request._last_merge_target_rev == self.target_commit
195 assert (
195 assert (
196 pull_request._last_merge_status is MergeFailureReason.MERGE_FAILED)
196 pull_request._last_merge_status is MergeFailureReason.MERGE_FAILED)
197
197
198 self.merge_mock.reset_mock()
198 self.merge_mock.reset_mock()
199 status, msg = PullRequestModel().merge_status(pull_request)
199 status, msg = PullRequestModel().merge_status(pull_request)
200 assert status is False
200 assert status is False
201 assert (
201 assert (
202 msg.eval() ==
202 msg.eval() ==
203 'This pull request cannot be merged because of merge conflicts.')
203 'This pull request cannot be merged because of merge conflicts.')
204 assert self.merge_mock.called is False
204 assert self.merge_mock.called is False
205
205
206 def test_merge_status_unknown_failure(self, pull_request):
206 def test_merge_status_unknown_failure(self, pull_request):
207 self.merge_mock.return_value = MergeResponse(
207 self.merge_mock.return_value = MergeResponse(
208 False, False, None, MergeFailureReason.UNKNOWN)
208 False, False, None, MergeFailureReason.UNKNOWN)
209
209
210 assert pull_request._last_merge_source_rev is None
210 assert pull_request._last_merge_source_rev is None
211 assert pull_request._last_merge_target_rev is None
211 assert pull_request._last_merge_target_rev is None
212 assert pull_request._last_merge_status is None
212 assert pull_request._last_merge_status is None
213
213
214 status, msg = PullRequestModel().merge_status(pull_request)
214 status, msg = PullRequestModel().merge_status(pull_request)
215 assert status is False
215 assert status is False
216 assert msg.eval() == (
216 assert msg.eval() == (
217 'This pull request cannot be merged because of an unhandled'
217 'This pull request cannot be merged because of an unhandled'
218 ' exception.')
218 ' exception.')
219 self.merge_mock.assert_called_once_with(
219 self.merge_mock.assert_called_once_with(
220 pull_request.target_ref_parts,
220 pull_request.target_ref_parts,
221 pull_request.source_repo.scm_instance(),
221 pull_request.source_repo.scm_instance(),
222 pull_request.source_ref_parts, self.workspace_id, dry_run=True,
222 pull_request.source_ref_parts, self.workspace_id, dry_run=True,
223 use_rebase=False)
223 use_rebase=False)
224
224
225 assert pull_request._last_merge_source_rev is None
225 assert pull_request._last_merge_source_rev is None
226 assert pull_request._last_merge_target_rev is None
226 assert pull_request._last_merge_target_rev is None
227 assert pull_request._last_merge_status is None
227 assert pull_request._last_merge_status is None
228
228
229 self.merge_mock.reset_mock()
229 self.merge_mock.reset_mock()
230 status, msg = PullRequestModel().merge_status(pull_request)
230 status, msg = PullRequestModel().merge_status(pull_request)
231 assert status is False
231 assert status is False
232 assert msg.eval() == (
232 assert msg.eval() == (
233 'This pull request cannot be merged because of an unhandled'
233 'This pull request cannot be merged because of an unhandled'
234 ' exception.')
234 ' exception.')
235 assert self.merge_mock.called is True
235 assert self.merge_mock.called is True
236
236
237 def test_merge_status_when_target_is_locked(self, pull_request):
237 def test_merge_status_when_target_is_locked(self, pull_request):
238 pull_request.target_repo.locked = [1, u'12345.50', 'lock_web']
238 pull_request.target_repo.locked = [1, u'12345.50', 'lock_web']
239 status, msg = PullRequestModel().merge_status(pull_request)
239 status, msg = PullRequestModel().merge_status(pull_request)
240 assert status is False
240 assert status is False
241 assert msg.eval() == (
241 assert msg.eval() == (
242 'This pull request cannot be merged because the target repository'
242 'This pull request cannot be merged because the target repository'
243 ' is locked.')
243 ' is locked.')
244
244
245 def test_merge_status_requirements_check_target(self, pull_request):
245 def test_merge_status_requirements_check_target(self, pull_request):
246
246
247 def has_largefiles(self, repo):
247 def has_largefiles(self, repo):
248 return repo == pull_request.source_repo
248 return repo == pull_request.source_repo
249
249
250 patcher = mock.patch.object(
250 patcher = mock.patch.object(
251 PullRequestModel, '_has_largefiles', has_largefiles)
251 PullRequestModel, '_has_largefiles', has_largefiles)
252 with patcher:
252 with patcher:
253 status, msg = PullRequestModel().merge_status(pull_request)
253 status, msg = PullRequestModel().merge_status(pull_request)
254
254
255 assert status is False
255 assert status is False
256 assert msg == 'Target repository large files support is disabled.'
256 assert msg == 'Target repository large files support is disabled.'
257
257
258 def test_merge_status_requirements_check_source(self, pull_request):
258 def test_merge_status_requirements_check_source(self, pull_request):
259
259
260 def has_largefiles(self, repo):
260 def has_largefiles(self, repo):
261 return repo == pull_request.target_repo
261 return repo == pull_request.target_repo
262
262
263 patcher = mock.patch.object(
263 patcher = mock.patch.object(
264 PullRequestModel, '_has_largefiles', has_largefiles)
264 PullRequestModel, '_has_largefiles', has_largefiles)
265 with patcher:
265 with patcher:
266 status, msg = PullRequestModel().merge_status(pull_request)
266 status, msg = PullRequestModel().merge_status(pull_request)
267
267
268 assert status is False
268 assert status is False
269 assert msg == 'Source repository large files support is disabled.'
269 assert msg == 'Source repository large files support is disabled.'
270
270
271 def test_merge(self, pull_request, merge_extras):
271 def test_merge(self, pull_request, merge_extras):
272 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
272 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
273 merge_ref = Reference(
273 merge_ref = Reference(
274 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
274 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
275 self.merge_mock.return_value = MergeResponse(
275 self.merge_mock.return_value = MergeResponse(
276 True, True, merge_ref, MergeFailureReason.NONE)
276 True, True, merge_ref, MergeFailureReason.NONE)
277
277
278 merge_extras['repository'] = pull_request.target_repo.repo_name
278 merge_extras['repository'] = pull_request.target_repo.repo_name
279 PullRequestModel().merge(
279 PullRequestModel().merge(
280 pull_request, pull_request.author, extras=merge_extras)
280 pull_request, pull_request.author, extras=merge_extras)
281
281
282 message = (
282 message = (
283 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
283 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
284 u'\n\n {pr_title}'.format(
284 u'\n\n {pr_title}'.format(
285 pr_id=pull_request.pull_request_id,
285 pr_id=pull_request.pull_request_id,
286 source_repo=safe_unicode(
286 source_repo=safe_unicode(
287 pull_request.source_repo.scm_instance().name),
287 pull_request.source_repo.scm_instance().name),
288 source_ref_name=pull_request.source_ref_parts.name,
288 source_ref_name=pull_request.source_ref_parts.name,
289 pr_title=safe_unicode(pull_request.title)
289 pr_title=safe_unicode(pull_request.title)
290 )
290 )
291 )
291 )
292 self.merge_mock.assert_called_once_with(
292 self.merge_mock.assert_called_once_with(
293 pull_request.target_ref_parts,
293 pull_request.target_ref_parts,
294 pull_request.source_repo.scm_instance(),
294 pull_request.source_repo.scm_instance(),
295 pull_request.source_ref_parts, self.workspace_id,
295 pull_request.source_ref_parts, self.workspace_id,
296 user_name=user.username, user_email=user.email, message=message,
296 user_name=user.username, user_email=user.email, message=message,
297 use_rebase=False
297 use_rebase=False
298 )
298 )
299 self.invalidation_mock.assert_called_once_with(
299 self.invalidation_mock.assert_called_once_with(
300 pull_request.target_repo.repo_name)
300 pull_request.target_repo.repo_name)
301
301
302 self.hook_mock.assert_called_with(
302 self.hook_mock.assert_called_with(
303 self.pull_request, self.pull_request.author, 'merge')
303 self.pull_request, self.pull_request.author, 'merge')
304
304
305 pull_request = PullRequest.get(pull_request.pull_request_id)
305 pull_request = PullRequest.get(pull_request.pull_request_id)
306 assert (
306 assert (
307 pull_request.merge_rev ==
307 pull_request.merge_rev ==
308 '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
308 '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
309
309
310 def test_merge_failed(self, pull_request, merge_extras):
310 def test_merge_failed(self, pull_request, merge_extras):
311 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
311 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
312 merge_ref = Reference(
312 merge_ref = Reference(
313 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
313 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
314 self.merge_mock.return_value = MergeResponse(
314 self.merge_mock.return_value = MergeResponse(
315 False, False, merge_ref, MergeFailureReason.MERGE_FAILED)
315 False, False, merge_ref, MergeFailureReason.MERGE_FAILED)
316
316
317 merge_extras['repository'] = pull_request.target_repo.repo_name
317 merge_extras['repository'] = pull_request.target_repo.repo_name
318 PullRequestModel().merge(
318 PullRequestModel().merge(
319 pull_request, pull_request.author, extras=merge_extras)
319 pull_request, pull_request.author, extras=merge_extras)
320
320
321 message = (
321 message = (
322 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
322 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
323 u'\n\n {pr_title}'.format(
323 u'\n\n {pr_title}'.format(
324 pr_id=pull_request.pull_request_id,
324 pr_id=pull_request.pull_request_id,
325 source_repo=safe_unicode(
325 source_repo=safe_unicode(
326 pull_request.source_repo.scm_instance().name),
326 pull_request.source_repo.scm_instance().name),
327 source_ref_name=pull_request.source_ref_parts.name,
327 source_ref_name=pull_request.source_ref_parts.name,
328 pr_title=safe_unicode(pull_request.title)
328 pr_title=safe_unicode(pull_request.title)
329 )
329 )
330 )
330 )
331 self.merge_mock.assert_called_once_with(
331 self.merge_mock.assert_called_once_with(
332 pull_request.target_ref_parts,
332 pull_request.target_ref_parts,
333 pull_request.source_repo.scm_instance(),
333 pull_request.source_repo.scm_instance(),
334 pull_request.source_ref_parts, self.workspace_id,
334 pull_request.source_ref_parts, self.workspace_id,
335 user_name=user.username, user_email=user.email, message=message,
335 user_name=user.username, user_email=user.email, message=message,
336 use_rebase=False
336 use_rebase=False
337 )
337 )
338
338
339 pull_request = PullRequest.get(pull_request.pull_request_id)
339 pull_request = PullRequest.get(pull_request.pull_request_id)
340 assert self.invalidation_mock.called is False
340 assert self.invalidation_mock.called is False
341 assert pull_request.merge_rev is None
341 assert pull_request.merge_rev is None
342
342
343 def test_get_commit_ids(self, pull_request):
343 def test_get_commit_ids(self, pull_request):
344 # The PR has been not merget yet, so expect an exception
344 # The PR has been not merget yet, so expect an exception
345 with pytest.raises(ValueError):
345 with pytest.raises(ValueError):
346 PullRequestModel()._get_commit_ids(pull_request)
346 PullRequestModel()._get_commit_ids(pull_request)
347
347
348 # Merge revision is in the revisions list
348 # Merge revision is in the revisions list
349 pull_request.merge_rev = pull_request.revisions[0]
349 pull_request.merge_rev = pull_request.revisions[0]
350 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
350 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
351 assert commit_ids == pull_request.revisions
351 assert commit_ids == pull_request.revisions
352
352
353 # Merge revision is not in the revisions list
353 # Merge revision is not in the revisions list
354 pull_request.merge_rev = 'f000' * 10
354 pull_request.merge_rev = 'f000' * 10
355 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
355 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
356 assert commit_ids == pull_request.revisions + [pull_request.merge_rev]
356 assert commit_ids == pull_request.revisions + [pull_request.merge_rev]
357
357
358 def test_get_diff_from_pr_version(self, pull_request):
358 def test_get_diff_from_pr_version(self, pull_request):
359 source_repo = pull_request.source_repo
360 source_ref_id = pull_request.source_ref_parts.commit_id
361 target_ref_id = pull_request.target_ref_parts.commit_id
359 diff = PullRequestModel()._get_diff_from_pr_or_version(
362 diff = PullRequestModel()._get_diff_from_pr_or_version(
360 pull_request, context=6)
363 source_repo, source_ref_id, target_ref_id, context=6)
361 assert 'file_1' in diff.raw
364 assert 'file_1' in diff.raw
362
365
363 def test_generate_title_returns_unicode(self):
366 def test_generate_title_returns_unicode(self):
364 title = PullRequestModel().generate_pullrequest_title(
367 title = PullRequestModel().generate_pullrequest_title(
365 source='source-dummy',
368 source='source-dummy',
366 source_ref='source-ref-dummy',
369 source_ref='source-ref-dummy',
367 target='target-dummy',
370 target='target-dummy',
368 )
371 )
369 assert type(title) == unicode
372 assert type(title) == unicode
370
373
371
374
372 class TestIntegrationMerge(object):
375 class TestIntegrationMerge(object):
373 @pytest.mark.parametrize('extra_config', (
376 @pytest.mark.parametrize('extra_config', (
374 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
377 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
375 {'vcs.hooks.protocol': 'Pyro4', 'vcs.hooks.direct_calls': False},
378 {'vcs.hooks.protocol': 'Pyro4', 'vcs.hooks.direct_calls': False},
376 ))
379 ))
377 def test_merge_triggers_push_hooks(
380 def test_merge_triggers_push_hooks(
378 self, pr_util, user_admin, capture_rcextensions, merge_extras,
381 self, pr_util, user_admin, capture_rcextensions, merge_extras,
379 extra_config):
382 extra_config):
380 pull_request = pr_util.create_pull_request(
383 pull_request = pr_util.create_pull_request(
381 approved=True, mergeable=True)
384 approved=True, mergeable=True)
382 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
385 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
383 merge_extras['repository'] = pull_request.target_repo.repo_name
386 merge_extras['repository'] = pull_request.target_repo.repo_name
384 Session().commit()
387 Session().commit()
385
388
386 with mock.patch.dict(rhodecode.CONFIG, extra_config, clear=False):
389 with mock.patch.dict(rhodecode.CONFIG, extra_config, clear=False):
387 merge_state = PullRequestModel().merge(
390 merge_state = PullRequestModel().merge(
388 pull_request, user_admin, extras=merge_extras)
391 pull_request, user_admin, extras=merge_extras)
389
392
390 assert merge_state.executed
393 assert merge_state.executed
391 assert 'pre_push' in capture_rcextensions
394 assert 'pre_push' in capture_rcextensions
392 assert 'post_push' in capture_rcextensions
395 assert 'post_push' in capture_rcextensions
393
396
394 def test_merge_can_be_rejected_by_pre_push_hook(
397 def test_merge_can_be_rejected_by_pre_push_hook(
395 self, pr_util, user_admin, capture_rcextensions, merge_extras):
398 self, pr_util, user_admin, capture_rcextensions, merge_extras):
396 pull_request = pr_util.create_pull_request(
399 pull_request = pr_util.create_pull_request(
397 approved=True, mergeable=True)
400 approved=True, mergeable=True)
398 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
401 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
399 merge_extras['repository'] = pull_request.target_repo.repo_name
402 merge_extras['repository'] = pull_request.target_repo.repo_name
400 Session().commit()
403 Session().commit()
401
404
402 with mock.patch('rhodecode.EXTENSIONS.PRE_PUSH_HOOK') as pre_pull:
405 with mock.patch('rhodecode.EXTENSIONS.PRE_PUSH_HOOK') as pre_pull:
403 pre_pull.side_effect = RepositoryError("Disallow push!")
406 pre_pull.side_effect = RepositoryError("Disallow push!")
404 merge_status = PullRequestModel().merge(
407 merge_status = PullRequestModel().merge(
405 pull_request, user_admin, extras=merge_extras)
408 pull_request, user_admin, extras=merge_extras)
406
409
407 assert not merge_status.executed
410 assert not merge_status.executed
408 assert 'pre_push' not in capture_rcextensions
411 assert 'pre_push' not in capture_rcextensions
409 assert 'post_push' not in capture_rcextensions
412 assert 'post_push' not in capture_rcextensions
410
413
411 def test_merge_fails_if_target_is_locked(
414 def test_merge_fails_if_target_is_locked(
412 self, pr_util, user_regular, merge_extras):
415 self, pr_util, user_regular, merge_extras):
413 pull_request = pr_util.create_pull_request(
416 pull_request = pr_util.create_pull_request(
414 approved=True, mergeable=True)
417 approved=True, mergeable=True)
415 locked_by = [user_regular.user_id + 1, 12345.50, 'lock_web']
418 locked_by = [user_regular.user_id + 1, 12345.50, 'lock_web']
416 pull_request.target_repo.locked = locked_by
419 pull_request.target_repo.locked = locked_by
417 # TODO: johbo: Check if this can work based on the database, currently
420 # TODO: johbo: Check if this can work based on the database, currently
418 # all data is pre-computed, that's why just updating the DB is not
421 # all data is pre-computed, that's why just updating the DB is not
419 # enough.
422 # enough.
420 merge_extras['locked_by'] = locked_by
423 merge_extras['locked_by'] = locked_by
421 merge_extras['repository'] = pull_request.target_repo.repo_name
424 merge_extras['repository'] = pull_request.target_repo.repo_name
422 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
425 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
423 Session().commit()
426 Session().commit()
424 merge_status = PullRequestModel().merge(
427 merge_status = PullRequestModel().merge(
425 pull_request, user_regular, extras=merge_extras)
428 pull_request, user_regular, extras=merge_extras)
426 assert not merge_status.executed
429 assert not merge_status.executed
427
430
428
431
429 @pytest.mark.parametrize('use_outdated, inlines_count, outdated_count', [
432 @pytest.mark.parametrize('use_outdated, inlines_count, outdated_count', [
430 (False, 1, 0),
433 (False, 1, 0),
431 (True, 0, 1),
434 (True, 0, 1),
432 ])
435 ])
433 def test_outdated_comments(
436 def test_outdated_comments(
434 pr_util, use_outdated, inlines_count, outdated_count):
437 pr_util, use_outdated, inlines_count, outdated_count):
435 pull_request = pr_util.create_pull_request()
438 pull_request = pr_util.create_pull_request()
436 pr_util.create_inline_comment(file_path='not_in_updated_diff')
439 pr_util.create_inline_comment(file_path='not_in_updated_diff')
437
440
438 with outdated_comments_patcher(use_outdated) as outdated_comment_mock:
441 with outdated_comments_patcher(use_outdated) as outdated_comment_mock:
439 pr_util.add_one_commit()
442 pr_util.add_one_commit()
440 assert_inline_comments(
443 assert_inline_comments(
441 pull_request, visible=inlines_count, outdated=outdated_count)
444 pull_request, visible=inlines_count, outdated=outdated_count)
442 outdated_comment_mock.assert_called_with(pull_request)
445 outdated_comment_mock.assert_called_with(pull_request)
443
446
444
447
445 @pytest.fixture
448 @pytest.fixture
446 def merge_extras(user_regular):
449 def merge_extras(user_regular):
447 """
450 """
448 Context for the vcs operation when running a merge.
451 Context for the vcs operation when running a merge.
449 """
452 """
450 extras = {
453 extras = {
451 'ip': '127.0.0.1',
454 'ip': '127.0.0.1',
452 'username': user_regular.username,
455 'username': user_regular.username,
453 'action': 'push',
456 'action': 'push',
454 'repository': 'fake_target_repo_name',
457 'repository': 'fake_target_repo_name',
455 'scm': 'git',
458 'scm': 'git',
456 'config': 'fake_config_ini_path',
459 'config': 'fake_config_ini_path',
457 'make_lock': None,
460 'make_lock': None,
458 'locked_by': [None, None, None],
461 'locked_by': [None, None, None],
459 'server_url': 'http://test.example.com:5000',
462 'server_url': 'http://test.example.com:5000',
460 'hooks': ['push', 'pull'],
463 'hooks': ['push', 'pull'],
461 'is_shadow_repo': False,
464 'is_shadow_repo': False,
462 }
465 }
463 return extras
466 return extras
464
467
465
468
466 class TestUpdateCommentHandling(object):
469 class TestUpdateCommentHandling(object):
467
470
468 @pytest.fixture(autouse=True, scope='class')
471 @pytest.fixture(autouse=True, scope='class')
469 def enable_outdated_comments(self, request, pylonsapp):
472 def enable_outdated_comments(self, request, pylonsapp):
470 config_patch = mock.patch.dict(
473 config_patch = mock.patch.dict(
471 'rhodecode.CONFIG', {'rhodecode_use_outdated_comments': True})
474 'rhodecode.CONFIG', {'rhodecode_use_outdated_comments': True})
472 config_patch.start()
475 config_patch.start()
473
476
474 @request.addfinalizer
477 @request.addfinalizer
475 def cleanup():
478 def cleanup():
476 config_patch.stop()
479 config_patch.stop()
477
480
478 def test_comment_stays_unflagged_on_unchanged_diff(self, pr_util):
481 def test_comment_stays_unflagged_on_unchanged_diff(self, pr_util):
479 commits = [
482 commits = [
480 {'message': 'a'},
483 {'message': 'a'},
481 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
484 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
482 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
485 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
483 ]
486 ]
484 pull_request = pr_util.create_pull_request(
487 pull_request = pr_util.create_pull_request(
485 commits=commits, target_head='a', source_head='b', revisions=['b'])
488 commits=commits, target_head='a', source_head='b', revisions=['b'])
486 pr_util.create_inline_comment(file_path='file_b')
489 pr_util.create_inline_comment(file_path='file_b')
487 pr_util.add_one_commit(head='c')
490 pr_util.add_one_commit(head='c')
488
491
489 assert_inline_comments(pull_request, visible=1, outdated=0)
492 assert_inline_comments(pull_request, visible=1, outdated=0)
490
493
491 def test_comment_stays_unflagged_on_change_above(self, pr_util):
494 def test_comment_stays_unflagged_on_change_above(self, pr_util):
492 original_content = ''.join(
495 original_content = ''.join(
493 ['line {}\n'.format(x) for x in range(1, 11)])
496 ['line {}\n'.format(x) for x in range(1, 11)])
494 updated_content = 'new_line_at_top\n' + original_content
497 updated_content = 'new_line_at_top\n' + original_content
495 commits = [
498 commits = [
496 {'message': 'a'},
499 {'message': 'a'},
497 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
500 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
498 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
501 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
499 ]
502 ]
500 pull_request = pr_util.create_pull_request(
503 pull_request = pr_util.create_pull_request(
501 commits=commits, target_head='a', source_head='b', revisions=['b'])
504 commits=commits, target_head='a', source_head='b', revisions=['b'])
502
505
503 with outdated_comments_patcher():
506 with outdated_comments_patcher():
504 comment = pr_util.create_inline_comment(
507 comment = pr_util.create_inline_comment(
505 line_no=u'n8', file_path='file_b')
508 line_no=u'n8', file_path='file_b')
506 pr_util.add_one_commit(head='c')
509 pr_util.add_one_commit(head='c')
507
510
508 assert_inline_comments(pull_request, visible=1, outdated=0)
511 assert_inline_comments(pull_request, visible=1, outdated=0)
509 assert comment.line_no == u'n9'
512 assert comment.line_no == u'n9'
510
513
511 def test_comment_stays_unflagged_on_change_below(self, pr_util):
514 def test_comment_stays_unflagged_on_change_below(self, pr_util):
512 original_content = ''.join(['line {}\n'.format(x) for x in range(10)])
515 original_content = ''.join(['line {}\n'.format(x) for x in range(10)])
513 updated_content = original_content + 'new_line_at_end\n'
516 updated_content = original_content + 'new_line_at_end\n'
514 commits = [
517 commits = [
515 {'message': 'a'},
518 {'message': 'a'},
516 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
519 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
517 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
520 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
518 ]
521 ]
519 pull_request = pr_util.create_pull_request(
522 pull_request = pr_util.create_pull_request(
520 commits=commits, target_head='a', source_head='b', revisions=['b'])
523 commits=commits, target_head='a', source_head='b', revisions=['b'])
521 pr_util.create_inline_comment(file_path='file_b')
524 pr_util.create_inline_comment(file_path='file_b')
522 pr_util.add_one_commit(head='c')
525 pr_util.add_one_commit(head='c')
523
526
524 assert_inline_comments(pull_request, visible=1, outdated=0)
527 assert_inline_comments(pull_request, visible=1, outdated=0)
525
528
526 @pytest.mark.parametrize('line_no', ['n4', 'o4', 'n10', 'o9'])
529 @pytest.mark.parametrize('line_no', ['n4', 'o4', 'n10', 'o9'])
527 def test_comment_flagged_on_change_around_context(self, pr_util, line_no):
530 def test_comment_flagged_on_change_around_context(self, pr_util, line_no):
528 base_lines = ['line {}\n'.format(x) for x in range(1, 13)]
531 base_lines = ['line {}\n'.format(x) for x in range(1, 13)]
529 change_lines = list(base_lines)
532 change_lines = list(base_lines)
530 change_lines.insert(6, 'line 6a added\n')
533 change_lines.insert(6, 'line 6a added\n')
531
534
532 # Changes on the last line of sight
535 # Changes on the last line of sight
533 update_lines = list(change_lines)
536 update_lines = list(change_lines)
534 update_lines[0] = 'line 1 changed\n'
537 update_lines[0] = 'line 1 changed\n'
535 update_lines[-1] = 'line 12 changed\n'
538 update_lines[-1] = 'line 12 changed\n'
536
539
537 def file_b(lines):
540 def file_b(lines):
538 return FileNode('file_b', ''.join(lines))
541 return FileNode('file_b', ''.join(lines))
539
542
540 commits = [
543 commits = [
541 {'message': 'a', 'added': [file_b(base_lines)]},
544 {'message': 'a', 'added': [file_b(base_lines)]},
542 {'message': 'b', 'changed': [file_b(change_lines)]},
545 {'message': 'b', 'changed': [file_b(change_lines)]},
543 {'message': 'c', 'changed': [file_b(update_lines)]},
546 {'message': 'c', 'changed': [file_b(update_lines)]},
544 ]
547 ]
545
548
546 pull_request = pr_util.create_pull_request(
549 pull_request = pr_util.create_pull_request(
547 commits=commits, target_head='a', source_head='b', revisions=['b'])
550 commits=commits, target_head='a', source_head='b', revisions=['b'])
548 pr_util.create_inline_comment(line_no=line_no, file_path='file_b')
551 pr_util.create_inline_comment(line_no=line_no, file_path='file_b')
549
552
550 with outdated_comments_patcher():
553 with outdated_comments_patcher():
551 pr_util.add_one_commit(head='c')
554 pr_util.add_one_commit(head='c')
552 assert_inline_comments(pull_request, visible=0, outdated=1)
555 assert_inline_comments(pull_request, visible=0, outdated=1)
553
556
554 @pytest.mark.parametrize("change, content", [
557 @pytest.mark.parametrize("change, content", [
555 ('changed', 'changed\n'),
558 ('changed', 'changed\n'),
556 ('removed', ''),
559 ('removed', ''),
557 ], ids=['changed', 'removed'])
560 ], ids=['changed', 'removed'])
558 def test_comment_flagged_on_change(self, pr_util, change, content):
561 def test_comment_flagged_on_change(self, pr_util, change, content):
559 commits = [
562 commits = [
560 {'message': 'a'},
563 {'message': 'a'},
561 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
564 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
562 {'message': 'c', change: [FileNode('file_b', content)]},
565 {'message': 'c', change: [FileNode('file_b', content)]},
563 ]
566 ]
564 pull_request = pr_util.create_pull_request(
567 pull_request = pr_util.create_pull_request(
565 commits=commits, target_head='a', source_head='b', revisions=['b'])
568 commits=commits, target_head='a', source_head='b', revisions=['b'])
566 pr_util.create_inline_comment(file_path='file_b')
569 pr_util.create_inline_comment(file_path='file_b')
567
570
568 with outdated_comments_patcher():
571 with outdated_comments_patcher():
569 pr_util.add_one_commit(head='c')
572 pr_util.add_one_commit(head='c')
570 assert_inline_comments(pull_request, visible=0, outdated=1)
573 assert_inline_comments(pull_request, visible=0, outdated=1)
571
574
572
575
573 class TestUpdateChangedFiles(object):
576 class TestUpdateChangedFiles(object):
574
577
575 def test_no_changes_on_unchanged_diff(self, pr_util):
578 def test_no_changes_on_unchanged_diff(self, pr_util):
576 commits = [
579 commits = [
577 {'message': 'a'},
580 {'message': 'a'},
578 {'message': 'b',
581 {'message': 'b',
579 'added': [FileNode('file_b', 'test_content b\n')]},
582 'added': [FileNode('file_b', 'test_content b\n')]},
580 {'message': 'c',
583 {'message': 'c',
581 'added': [FileNode('file_c', 'test_content c\n')]},
584 'added': [FileNode('file_c', 'test_content c\n')]},
582 ]
585 ]
583 # open a PR from a to b, adding file_b
586 # open a PR from a to b, adding file_b
584 pull_request = pr_util.create_pull_request(
587 pull_request = pr_util.create_pull_request(
585 commits=commits, target_head='a', source_head='b', revisions=['b'],
588 commits=commits, target_head='a', source_head='b', revisions=['b'],
586 name_suffix='per-file-review')
589 name_suffix='per-file-review')
587
590
588 # modify PR adding new file file_c
591 # modify PR adding new file file_c
589 pr_util.add_one_commit(head='c')
592 pr_util.add_one_commit(head='c')
590
593
591 assert_pr_file_changes(
594 assert_pr_file_changes(
592 pull_request,
595 pull_request,
593 added=['file_c'],
596 added=['file_c'],
594 modified=[],
597 modified=[],
595 removed=[])
598 removed=[])
596
599
597 def test_modify_and_undo_modification_diff(self, pr_util):
600 def test_modify_and_undo_modification_diff(self, pr_util):
598 commits = [
601 commits = [
599 {'message': 'a'},
602 {'message': 'a'},
600 {'message': 'b',
603 {'message': 'b',
601 'added': [FileNode('file_b', 'test_content b\n')]},
604 'added': [FileNode('file_b', 'test_content b\n')]},
602 {'message': 'c',
605 {'message': 'c',
603 'changed': [FileNode('file_b', 'test_content b modified\n')]},
606 'changed': [FileNode('file_b', 'test_content b modified\n')]},
604 {'message': 'd',
607 {'message': 'd',
605 'changed': [FileNode('file_b', 'test_content b\n')]},
608 'changed': [FileNode('file_b', 'test_content b\n')]},
606 ]
609 ]
607 # open a PR from a to b, adding file_b
610 # open a PR from a to b, adding file_b
608 pull_request = pr_util.create_pull_request(
611 pull_request = pr_util.create_pull_request(
609 commits=commits, target_head='a', source_head='b', revisions=['b'],
612 commits=commits, target_head='a', source_head='b', revisions=['b'],
610 name_suffix='per-file-review')
613 name_suffix='per-file-review')
611
614
612 # modify PR modifying file file_b
615 # modify PR modifying file file_b
613 pr_util.add_one_commit(head='c')
616 pr_util.add_one_commit(head='c')
614
617
615 assert_pr_file_changes(
618 assert_pr_file_changes(
616 pull_request,
619 pull_request,
617 added=[],
620 added=[],
618 modified=['file_b'],
621 modified=['file_b'],
619 removed=[])
622 removed=[])
620
623
621 # move the head again to d, which rollbacks change,
624 # move the head again to d, which rollbacks change,
622 # meaning we should indicate no changes
625 # meaning we should indicate no changes
623 pr_util.add_one_commit(head='d')
626 pr_util.add_one_commit(head='d')
624
627
625 assert_pr_file_changes(
628 assert_pr_file_changes(
626 pull_request,
629 pull_request,
627 added=[],
630 added=[],
628 modified=[],
631 modified=[],
629 removed=[])
632 removed=[])
630
633
631 def test_updated_all_files_in_pr(self, pr_util):
634 def test_updated_all_files_in_pr(self, pr_util):
632 commits = [
635 commits = [
633 {'message': 'a'},
636 {'message': 'a'},
634 {'message': 'b', 'added': [
637 {'message': 'b', 'added': [
635 FileNode('file_a', 'test_content a\n'),
638 FileNode('file_a', 'test_content a\n'),
636 FileNode('file_b', 'test_content b\n'),
639 FileNode('file_b', 'test_content b\n'),
637 FileNode('file_c', 'test_content c\n')]},
640 FileNode('file_c', 'test_content c\n')]},
638 {'message': 'c', 'changed': [
641 {'message': 'c', 'changed': [
639 FileNode('file_a', 'test_content a changed\n'),
642 FileNode('file_a', 'test_content a changed\n'),
640 FileNode('file_b', 'test_content b changed\n'),
643 FileNode('file_b', 'test_content b changed\n'),
641 FileNode('file_c', 'test_content c changed\n')]},
644 FileNode('file_c', 'test_content c changed\n')]},
642 ]
645 ]
643 # open a PR from a to b, changing 3 files
646 # open a PR from a to b, changing 3 files
644 pull_request = pr_util.create_pull_request(
647 pull_request = pr_util.create_pull_request(
645 commits=commits, target_head='a', source_head='b', revisions=['b'],
648 commits=commits, target_head='a', source_head='b', revisions=['b'],
646 name_suffix='per-file-review')
649 name_suffix='per-file-review')
647
650
648 pr_util.add_one_commit(head='c')
651 pr_util.add_one_commit(head='c')
649
652
650 assert_pr_file_changes(
653 assert_pr_file_changes(
651 pull_request,
654 pull_request,
652 added=[],
655 added=[],
653 modified=['file_a', 'file_b', 'file_c'],
656 modified=['file_a', 'file_b', 'file_c'],
654 removed=[])
657 removed=[])
655
658
656 def test_updated_and_removed_all_files_in_pr(self, pr_util):
659 def test_updated_and_removed_all_files_in_pr(self, pr_util):
657 commits = [
660 commits = [
658 {'message': 'a'},
661 {'message': 'a'},
659 {'message': 'b', 'added': [
662 {'message': 'b', 'added': [
660 FileNode('file_a', 'test_content a\n'),
663 FileNode('file_a', 'test_content a\n'),
661 FileNode('file_b', 'test_content b\n'),
664 FileNode('file_b', 'test_content b\n'),
662 FileNode('file_c', 'test_content c\n')]},
665 FileNode('file_c', 'test_content c\n')]},
663 {'message': 'c', 'removed': [
666 {'message': 'c', 'removed': [
664 FileNode('file_a', 'test_content a changed\n'),
667 FileNode('file_a', 'test_content a changed\n'),
665 FileNode('file_b', 'test_content b changed\n'),
668 FileNode('file_b', 'test_content b changed\n'),
666 FileNode('file_c', 'test_content c changed\n')]},
669 FileNode('file_c', 'test_content c changed\n')]},
667 ]
670 ]
668 # open a PR from a to b, removing 3 files
671 # open a PR from a to b, removing 3 files
669 pull_request = pr_util.create_pull_request(
672 pull_request = pr_util.create_pull_request(
670 commits=commits, target_head='a', source_head='b', revisions=['b'],
673 commits=commits, target_head='a', source_head='b', revisions=['b'],
671 name_suffix='per-file-review')
674 name_suffix='per-file-review')
672
675
673 pr_util.add_one_commit(head='c')
676 pr_util.add_one_commit(head='c')
674
677
675 assert_pr_file_changes(
678 assert_pr_file_changes(
676 pull_request,
679 pull_request,
677 added=[],
680 added=[],
678 modified=[],
681 modified=[],
679 removed=['file_a', 'file_b', 'file_c'])
682 removed=['file_a', 'file_b', 'file_c'])
680
683
681
684
682 def test_update_writes_snapshot_into_pull_request_version(pr_util):
685 def test_update_writes_snapshot_into_pull_request_version(pr_util):
683 model = PullRequestModel()
686 model = PullRequestModel()
684 pull_request = pr_util.create_pull_request()
687 pull_request = pr_util.create_pull_request()
685 pr_util.update_source_repository()
688 pr_util.update_source_repository()
686
689
687 model.update_commits(pull_request)
690 model.update_commits(pull_request)
688
691
689 # Expect that it has a version entry now
692 # Expect that it has a version entry now
690 assert len(model.get_versions(pull_request)) == 1
693 assert len(model.get_versions(pull_request)) == 1
691
694
692
695
693 def test_update_skips_new_version_if_unchanged(pr_util):
696 def test_update_skips_new_version_if_unchanged(pr_util):
694 pull_request = pr_util.create_pull_request()
697 pull_request = pr_util.create_pull_request()
695 model = PullRequestModel()
698 model = PullRequestModel()
696 model.update_commits(pull_request)
699 model.update_commits(pull_request)
697
700
698 # Expect that it still has no versions
701 # Expect that it still has no versions
699 assert len(model.get_versions(pull_request)) == 0
702 assert len(model.get_versions(pull_request)) == 0
700
703
701
704
702 def test_update_assigns_comments_to_the_new_version(pr_util):
705 def test_update_assigns_comments_to_the_new_version(pr_util):
703 model = PullRequestModel()
706 model = PullRequestModel()
704 pull_request = pr_util.create_pull_request()
707 pull_request = pr_util.create_pull_request()
705 comment = pr_util.create_comment()
708 comment = pr_util.create_comment()
706 pr_util.update_source_repository()
709 pr_util.update_source_repository()
707
710
708 model.update_commits(pull_request)
711 model.update_commits(pull_request)
709
712
710 # Expect that the comment is linked to the pr version now
713 # Expect that the comment is linked to the pr version now
711 assert comment.pull_request_version == model.get_versions(pull_request)[0]
714 assert comment.pull_request_version == model.get_versions(pull_request)[0]
712
715
713
716
714 def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util):
717 def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util):
715 model = PullRequestModel()
718 model = PullRequestModel()
716 pull_request = pr_util.create_pull_request()
719 pull_request = pr_util.create_pull_request()
717 pr_util.update_source_repository()
720 pr_util.update_source_repository()
718 pr_util.update_source_repository()
721 pr_util.update_source_repository()
719
722
720 model.update_commits(pull_request)
723 model.update_commits(pull_request)
721
724
722 # Expect to find a new comment about the change
725 # Expect to find a new comment about the change
723 expected_message = textwrap.dedent(
726 expected_message = textwrap.dedent(
724 """\
727 """\
725 Pull request updated. Auto status change to |under_review|
728 Pull request updated. Auto status change to |under_review|
726
729
727 .. role:: added
730 .. role:: added
728 .. role:: removed
731 .. role:: removed
729 .. parsed-literal::
732 .. parsed-literal::
730
733
731 Changed commits:
734 Changed commits:
732 * :added:`1 added`
735 * :added:`1 added`
733 * :removed:`0 removed`
736 * :removed:`0 removed`
734
737
735 Changed files:
738 Changed files:
736 * `A file_2 <#a_c--92ed3b5f07b4>`_
739 * `A file_2 <#a_c--92ed3b5f07b4>`_
737
740
738 .. |under_review| replace:: *"Under Review"*"""
741 .. |under_review| replace:: *"Under Review"*"""
739 )
742 )
740 pull_request_comments = sorted(
743 pull_request_comments = sorted(
741 pull_request.comments, key=lambda c: c.modified_at)
744 pull_request.comments, key=lambda c: c.modified_at)
742 update_comment = pull_request_comments[-1]
745 update_comment = pull_request_comments[-1]
743 assert update_comment.text == expected_message
746 assert update_comment.text == expected_message
744
747
745
748
746 def test_create_version_from_snapshot_updates_attributes(pr_util):
749 def test_create_version_from_snapshot_updates_attributes(pr_util):
747 pull_request = pr_util.create_pull_request()
750 pull_request = pr_util.create_pull_request()
748
751
749 # Avoiding default values
752 # Avoiding default values
750 pull_request.status = PullRequest.STATUS_CLOSED
753 pull_request.status = PullRequest.STATUS_CLOSED
751 pull_request._last_merge_source_rev = "0" * 40
754 pull_request._last_merge_source_rev = "0" * 40
752 pull_request._last_merge_target_rev = "1" * 40
755 pull_request._last_merge_target_rev = "1" * 40
753 pull_request._last_merge_status = 1
756 pull_request._last_merge_status = 1
754 pull_request.merge_rev = "2" * 40
757 pull_request.merge_rev = "2" * 40
755
758
756 # Remember automatic values
759 # Remember automatic values
757 created_on = pull_request.created_on
760 created_on = pull_request.created_on
758 updated_on = pull_request.updated_on
761 updated_on = pull_request.updated_on
759
762
760 # Create a new version of the pull request
763 # Create a new version of the pull request
761 version = PullRequestModel()._create_version_from_snapshot(pull_request)
764 version = PullRequestModel()._create_version_from_snapshot(pull_request)
762
765
763 # Check attributes
766 # Check attributes
764 assert version.title == pr_util.create_parameters['title']
767 assert version.title == pr_util.create_parameters['title']
765 assert version.description == pr_util.create_parameters['description']
768 assert version.description == pr_util.create_parameters['description']
766 assert version.status == PullRequest.STATUS_CLOSED
769 assert version.status == PullRequest.STATUS_CLOSED
767
770
768 # versions get updated created_on
771 # versions get updated created_on
769 assert version.created_on != created_on
772 assert version.created_on != created_on
770
773
771 assert version.updated_on == updated_on
774 assert version.updated_on == updated_on
772 assert version.user_id == pull_request.user_id
775 assert version.user_id == pull_request.user_id
773 assert version.revisions == pr_util.create_parameters['revisions']
776 assert version.revisions == pr_util.create_parameters['revisions']
774 assert version.source_repo == pr_util.source_repository
777 assert version.source_repo == pr_util.source_repository
775 assert version.source_ref == pr_util.create_parameters['source_ref']
778 assert version.source_ref == pr_util.create_parameters['source_ref']
776 assert version.target_repo == pr_util.target_repository
779 assert version.target_repo == pr_util.target_repository
777 assert version.target_ref == pr_util.create_parameters['target_ref']
780 assert version.target_ref == pr_util.create_parameters['target_ref']
778 assert version._last_merge_source_rev == pull_request._last_merge_source_rev
781 assert version._last_merge_source_rev == pull_request._last_merge_source_rev
779 assert version._last_merge_target_rev == pull_request._last_merge_target_rev
782 assert version._last_merge_target_rev == pull_request._last_merge_target_rev
780 assert version._last_merge_status == pull_request._last_merge_status
783 assert version._last_merge_status == pull_request._last_merge_status
781 assert version.merge_rev == pull_request.merge_rev
784 assert version.merge_rev == pull_request.merge_rev
782 assert version.pull_request == pull_request
785 assert version.pull_request == pull_request
783
786
784
787
785 def test_link_comments_to_version_only_updates_unlinked_comments(pr_util):
788 def test_link_comments_to_version_only_updates_unlinked_comments(pr_util):
786 version1 = pr_util.create_version_of_pull_request()
789 version1 = pr_util.create_version_of_pull_request()
787 comment_linked = pr_util.create_comment(linked_to=version1)
790 comment_linked = pr_util.create_comment(linked_to=version1)
788 comment_unlinked = pr_util.create_comment()
791 comment_unlinked = pr_util.create_comment()
789 version2 = pr_util.create_version_of_pull_request()
792 version2 = pr_util.create_version_of_pull_request()
790
793
791 PullRequestModel()._link_comments_to_version(version2)
794 PullRequestModel()._link_comments_to_version(version2)
792
795
793 # Expect that only the new comment is linked to version2
796 # Expect that only the new comment is linked to version2
794 assert (
797 assert (
795 comment_unlinked.pull_request_version_id ==
798 comment_unlinked.pull_request_version_id ==
796 version2.pull_request_version_id)
799 version2.pull_request_version_id)
797 assert (
800 assert (
798 comment_linked.pull_request_version_id ==
801 comment_linked.pull_request_version_id ==
799 version1.pull_request_version_id)
802 version1.pull_request_version_id)
800 assert (
803 assert (
801 comment_unlinked.pull_request_version_id !=
804 comment_unlinked.pull_request_version_id !=
802 comment_linked.pull_request_version_id)
805 comment_linked.pull_request_version_id)
803
806
804
807
805 def test_calculate_commits():
808 def test_calculate_commits():
806 change = PullRequestModel()._calculate_commit_id_changes(
809 old_ids = [1, 2, 3]
807 set([1, 2, 3]), set([1, 3, 4, 5]))
810 new_ids = [1, 3, 4, 5]
808 assert (set([4, 5]), set([1, 3]), set([2])) == (
811 change = PullRequestModel()._calculate_commit_id_changes(old_ids, new_ids)
809 change.added, change.common, change.removed)
812 assert change.added == [4, 5]
813 assert change.common == [1, 3]
814 assert change.removed == [2]
815 assert change.total == [1, 3, 4, 5]
810
816
811
817
812 def assert_inline_comments(pull_request, visible=None, outdated=None):
818 def assert_inline_comments(pull_request, visible=None, outdated=None):
813 if visible is not None:
819 if visible is not None:
814 inline_comments = CommentsModel().get_inline_comments(
820 inline_comments = CommentsModel().get_inline_comments(
815 pull_request.target_repo.repo_id, pull_request=pull_request)
821 pull_request.target_repo.repo_id, pull_request=pull_request)
816 inline_cnt = CommentsModel().get_inline_comments_count(
822 inline_cnt = CommentsModel().get_inline_comments_count(
817 inline_comments)
823 inline_comments)
818 assert inline_cnt == visible
824 assert inline_cnt == visible
819 if outdated is not None:
825 if outdated is not None:
820 outdated_comments = CommentsModel().get_outdated_comments(
826 outdated_comments = CommentsModel().get_outdated_comments(
821 pull_request.target_repo.repo_id, pull_request)
827 pull_request.target_repo.repo_id, pull_request)
822 assert len(outdated_comments) == outdated
828 assert len(outdated_comments) == outdated
823
829
824
830
825 def assert_pr_file_changes(
831 def assert_pr_file_changes(
826 pull_request, added=None, modified=None, removed=None):
832 pull_request, added=None, modified=None, removed=None):
827 pr_versions = PullRequestModel().get_versions(pull_request)
833 pr_versions = PullRequestModel().get_versions(pull_request)
828 # always use first version, ie original PR to calculate changes
834 # always use first version, ie original PR to calculate changes
829 pull_request_version = pr_versions[0]
835 pull_request_version = pr_versions[0]
830 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
836 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
831 pull_request, pull_request_version)
837 pull_request, pull_request_version)
832 file_changes = PullRequestModel()._calculate_file_changes(
838 file_changes = PullRequestModel()._calculate_file_changes(
833 old_diff_data, new_diff_data)
839 old_diff_data, new_diff_data)
834
840
835 assert added == file_changes.added, \
841 assert added == file_changes.added, \
836 'expected added:%s vs value:%s' % (added, file_changes.added)
842 'expected added:%s vs value:%s' % (added, file_changes.added)
837 assert modified == file_changes.modified, \
843 assert modified == file_changes.modified, \
838 'expected modified:%s vs value:%s' % (modified, file_changes.modified)
844 'expected modified:%s vs value:%s' % (modified, file_changes.modified)
839 assert removed == file_changes.removed, \
845 assert removed == file_changes.removed, \
840 'expected removed:%s vs value:%s' % (removed, file_changes.removed)
846 'expected removed:%s vs value:%s' % (removed, file_changes.removed)
841
847
842
848
843 def outdated_comments_patcher(use_outdated=True):
849 def outdated_comments_patcher(use_outdated=True):
844 return mock.patch.object(
850 return mock.patch.object(
845 CommentsModel, 'use_outdated_comments',
851 CommentsModel, 'use_outdated_comments',
846 return_value=use_outdated)
852 return_value=use_outdated)
@@ -1,66 +1,69 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.lib.vcs.exceptions import CommitDoesNotExistError
24 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26
26
27
27
28 @pytest.mark.backends('git')
28 @pytest.mark.backends('git')
29 class TestGetDiffForPrOrVersion(object):
29 class TestGetDiffForPrOrVersion(object):
30
30
31 def test_works_for_missing_git_references(self, pr_util):
31 def test_works_for_missing_git_references(self, pr_util):
32 pull_request = self._prepare_pull_request(pr_util)
32 pull_request = self._prepare_pull_request(pr_util)
33 removed_commit_id = pr_util.remove_one_commit()
33 removed_commit_id = pr_util.remove_one_commit()
34 self.assert_commit_cannot_be_accessed(removed_commit_id, pull_request)
34 self.assert_commit_cannot_be_accessed(removed_commit_id, pull_request)
35
35
36 self.assert_diff_can_be_fetched(pull_request)
36 self.assert_diff_can_be_fetched(pull_request)
37
37
38 def test_works_for_missing_git_references_during_update(self, pr_util):
38 def test_works_for_missing_git_references_during_update(self, pr_util):
39 pull_request = self._prepare_pull_request(pr_util)
39 pull_request = self._prepare_pull_request(pr_util)
40 removed_commit_id = pr_util.remove_one_commit()
40 removed_commit_id = pr_util.remove_one_commit()
41 self.assert_commit_cannot_be_accessed(removed_commit_id, pull_request)
41 self.assert_commit_cannot_be_accessed(removed_commit_id, pull_request)
42
42
43 pr_version = PullRequestModel().get_versions(pull_request)[0]
43 pr_version = PullRequestModel().get_versions(pull_request)[0]
44 self.assert_diff_can_be_fetched(pr_version)
44 self.assert_diff_can_be_fetched(pr_version)
45
45
46 def _prepare_pull_request(self, pr_util):
46 def _prepare_pull_request(self, pr_util):
47 commits = [
47 commits = [
48 {'message': 'a'},
48 {'message': 'a'},
49 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
49 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
50 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
50 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
51 ]
51 ]
52 pull_request = pr_util.create_pull_request(
52 pull_request = pr_util.create_pull_request(
53 commits=commits, target_head='a', source_head='c',
53 commits=commits, target_head='a', source_head='c',
54 revisions=['b', 'c'])
54 revisions=['b', 'c'])
55 return pull_request
55 return pull_request
56
56
57 def assert_diff_can_be_fetched(self, pr_or_version):
57 def assert_diff_can_be_fetched(self, pr_or_version):
58 source_repo = pr_or_version.source_repo
59 source_ref_id = pr_or_version.source_ref_parts.commit_id
60 target_ref_id = pr_or_version.target_ref_parts.commit_id
58 diff = PullRequestModel()._get_diff_from_pr_or_version(
61 diff = PullRequestModel()._get_diff_from_pr_or_version(
59 pr_or_version, context=6)
62 source_repo, source_ref_id, target_ref_id, context=6)
60 assert 'file_b' in diff.raw
63 assert 'file_b' in diff.raw
61
64
62 def assert_commit_cannot_be_accessed(
65 def assert_commit_cannot_be_accessed(
63 self, removed_commit_id, pull_request):
66 self, removed_commit_id, pull_request):
64 source_vcs = pull_request.source_repo.scm_instance()
67 source_vcs = pull_request.source_repo.scm_instance()
65 with pytest.raises(CommitDoesNotExistError):
68 with pytest.raises(CommitDoesNotExistError):
66 source_vcs.get_commit(commit_id=removed_commit_id)
69 source_vcs.get_commit(commit_id=removed_commit_id)
General Comments 0
You need to be logged in to leave comments. Login now