##// END OF EJS Templates
tests: fixed some tests
super-admin -
r4704:fa2afbc9 stable
parent child Browse files
Show More
@@ -1,170 +1,171 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.events import UserPermissionsChange
23 from rhodecode.lib.utils2 import StrictAttributeDict
24 from rhodecode.lib.utils2 import StrictAttributeDict
24 from rhodecode.tests.events.conftest import EventCatcher
25 from rhodecode.tests.events.conftest import EventCatcher
25
26
26 from rhodecode.lib import hooks_base, utils2
27 from rhodecode.lib import hooks_base, utils2
27 from rhodecode.model.repo import RepoModel
28 from rhodecode.model.repo import RepoModel
28 from rhodecode.events.repo import (
29 from rhodecode.events.repo import (
29 RepoPrePullEvent, RepoPullEvent,
30 RepoPrePullEvent, RepoPullEvent,
30 RepoPrePushEvent, RepoPushEvent,
31 RepoPrePushEvent, RepoPushEvent,
31 RepoPreCreateEvent, RepoCreateEvent,
32 RepoPreCreateEvent, RepoCreateEvent,
32 RepoPreDeleteEvent, RepoDeleteEvent,
33 RepoPreDeleteEvent, RepoDeleteEvent,
33 RepoCommitCommentEvent, RepoCommitCommentEditEvent
34 RepoCommitCommentEvent, RepoCommitCommentEditEvent
34 )
35 )
35
36
36
37
37 @pytest.fixture()
38 @pytest.fixture()
38 def scm_extras(user_regular, repo_stub):
39 def scm_extras(user_regular, repo_stub):
39 extras = utils2.AttributeDict({
40 extras = utils2.AttributeDict({
40 'ip': '127.0.0.1',
41 'ip': '127.0.0.1',
41 'username': user_regular.username,
42 'username': user_regular.username,
42 'user_id': user_regular.user_id,
43 'user_id': user_regular.user_id,
43 'action': '',
44 'action': '',
44 'repository': repo_stub.repo_name,
45 'repository': repo_stub.repo_name,
45 'scm': repo_stub.scm_instance().alias,
46 'scm': repo_stub.scm_instance().alias,
46 'config': '',
47 'config': '',
47 'repo_store': '',
48 'repo_store': '',
48 'server_url': 'http://example.com',
49 'server_url': 'http://example.com',
49 'make_lock': None,
50 'make_lock': None,
50 'user_agent': 'some-client',
51 'user_agent': 'some-client',
51 'locked_by': [None],
52 'locked_by': [None],
52 'commit_ids': ['a' * 40] * 3,
53 'commit_ids': ['a' * 40] * 3,
53 'hook_type': 'scm_extras_test',
54 'hook_type': 'scm_extras_test',
54 'is_shadow_repo': False,
55 'is_shadow_repo': False,
55 })
56 })
56 return extras
57 return extras
57
58
58
59
59 # TODO: dan: make the serialization tests complete json comparisons
60 # TODO: dan: make the serialization tests complete json comparisons
60 @pytest.mark.parametrize('EventClass', [
61 @pytest.mark.parametrize('EventClass', [
61 RepoPreCreateEvent, RepoCreateEvent,
62 RepoPreCreateEvent, RepoCreateEvent,
62 RepoPreDeleteEvent, RepoDeleteEvent,
63 RepoPreDeleteEvent, RepoDeleteEvent,
63 ])
64 ])
64 def test_repo_events_serialized(config_stub, repo_stub, EventClass):
65 def test_repo_events_serialized(config_stub, repo_stub, EventClass):
65 event = EventClass(repo_stub)
66 event = EventClass(repo_stub)
66 data = event.as_dict()
67 data = event.as_dict()
67 assert data['name'] == EventClass.name
68 assert data['name'] == EventClass.name
68 assert data['repo']['repo_name'] == repo_stub.repo_name
69 assert data['repo']['repo_name'] == repo_stub.repo_name
69 assert data['repo']['url']
70 assert data['repo']['url']
70 assert data['repo']['permalink_url']
71 assert data['repo']['permalink_url']
71
72
72
73
73 @pytest.mark.parametrize('EventClass', [
74 @pytest.mark.parametrize('EventClass', [
74 RepoPrePullEvent, RepoPullEvent, RepoPrePushEvent
75 RepoPrePullEvent, RepoPullEvent, RepoPrePushEvent
75 ])
76 ])
76 def test_vcs_repo_events_serialize(config_stub, repo_stub, scm_extras, EventClass):
77 def test_vcs_repo_events_serialize(config_stub, repo_stub, scm_extras, EventClass):
77 event = EventClass(repo_name=repo_stub.repo_name, extras=scm_extras)
78 event = EventClass(repo_name=repo_stub.repo_name, extras=scm_extras)
78 data = event.as_dict()
79 data = event.as_dict()
79 assert data['name'] == EventClass.name
80 assert data['name'] == EventClass.name
80 assert data['repo']['repo_name'] == repo_stub.repo_name
81 assert data['repo']['repo_name'] == repo_stub.repo_name
81 assert data['repo']['url']
82 assert data['repo']['url']
82 assert data['repo']['permalink_url']
83 assert data['repo']['permalink_url']
83
84
84
85
85 @pytest.mark.parametrize('EventClass', [RepoPushEvent])
86 @pytest.mark.parametrize('EventClass', [RepoPushEvent])
86 def test_vcs_repo_push_event_serialize(config_stub, repo_stub, scm_extras, EventClass):
87 def test_vcs_repo_push_event_serialize(config_stub, repo_stub, scm_extras, EventClass):
87 event = EventClass(repo_name=repo_stub.repo_name,
88 event = EventClass(repo_name=repo_stub.repo_name,
88 pushed_commit_ids=scm_extras['commit_ids'],
89 pushed_commit_ids=scm_extras['commit_ids'],
89 extras=scm_extras)
90 extras=scm_extras)
90 data = event.as_dict()
91 data = event.as_dict()
91 assert data['name'] == EventClass.name
92 assert data['name'] == EventClass.name
92 assert data['repo']['repo_name'] == repo_stub.repo_name
93 assert data['repo']['repo_name'] == repo_stub.repo_name
93 assert data['repo']['url']
94 assert data['repo']['url']
94 assert data['repo']['permalink_url']
95 assert data['repo']['permalink_url']
95
96
96
97
97 def test_create_delete_repo_fires_events(backend):
98 def test_create_delete_repo_fires_events(backend):
98 with EventCatcher() as event_catcher:
99 with EventCatcher() as event_catcher:
99 repo = backend.create_repo()
100 repo = backend.create_repo()
100 assert event_catcher.events_types == [RepoPreCreateEvent, RepoCreateEvent]
101 assert event_catcher.events_types == [RepoPreCreateEvent, RepoCreateEvent, UserPermissionsChange]
101
102
102 with EventCatcher() as event_catcher:
103 with EventCatcher() as event_catcher:
103 RepoModel().delete(repo)
104 RepoModel().delete(repo)
104 assert event_catcher.events_types == [RepoPreDeleteEvent, RepoDeleteEvent]
105 assert event_catcher.events_types == [RepoPreDeleteEvent, RepoDeleteEvent]
105
106
106
107
107 def test_pull_fires_events(scm_extras):
108 def test_pull_fires_events(scm_extras):
108 with EventCatcher() as event_catcher:
109 with EventCatcher() as event_catcher:
109 hooks_base.pre_push(scm_extras)
110 hooks_base.pre_push(scm_extras)
110 assert event_catcher.events_types == [RepoPrePushEvent]
111 assert event_catcher.events_types == [RepoPrePushEvent]
111
112
112 with EventCatcher() as event_catcher:
113 with EventCatcher() as event_catcher:
113 hooks_base.post_push(scm_extras)
114 hooks_base.post_push(scm_extras)
114 assert event_catcher.events_types == [RepoPushEvent]
115 assert event_catcher.events_types == [RepoPushEvent]
115
116
116
117
117 def test_push_fires_events(scm_extras):
118 def test_push_fires_events(scm_extras):
118 with EventCatcher() as event_catcher:
119 with EventCatcher() as event_catcher:
119 hooks_base.pre_pull(scm_extras)
120 hooks_base.pre_pull(scm_extras)
120 assert event_catcher.events_types == [RepoPrePullEvent]
121 assert event_catcher.events_types == [RepoPrePullEvent]
121
122
122 with EventCatcher() as event_catcher:
123 with EventCatcher() as event_catcher:
123 hooks_base.post_pull(scm_extras)
124 hooks_base.post_pull(scm_extras)
124 assert event_catcher.events_types == [RepoPullEvent]
125 assert event_catcher.events_types == [RepoPullEvent]
125
126
126
127
127 @pytest.mark.parametrize('EventClass', [RepoCommitCommentEvent])
128 @pytest.mark.parametrize('EventClass', [RepoCommitCommentEvent])
128 def test_repo_commit_event(config_stub, repo_stub, EventClass):
129 def test_repo_commit_event(config_stub, repo_stub, EventClass):
129
130
130 commit = StrictAttributeDict({
131 commit = StrictAttributeDict({
131 'raw_id': 'raw_id',
132 'raw_id': 'raw_id',
132 'message': 'message',
133 'message': 'message',
133 'branch': 'branch',
134 'branch': 'branch',
134 })
135 })
135
136
136 comment = StrictAttributeDict({
137 comment = StrictAttributeDict({
137 'comment_id': 'comment_id',
138 'comment_id': 'comment_id',
138 'text': 'text',
139 'text': 'text',
139 'comment_type': 'comment_type',
140 'comment_type': 'comment_type',
140 'f_path': 'f_path',
141 'f_path': 'f_path',
141 'line_no': 'line_no',
142 'line_no': 'line_no',
142 'last_version': 0,
143 'last_version': 0,
143 })
144 })
144 event = EventClass(repo=repo_stub, commit=commit, comment=comment)
145 event = EventClass(repo=repo_stub, commit=commit, comment=comment)
145 data = event.as_dict()
146 data = event.as_dict()
146 assert data['commit']['commit_id']
147 assert data['commit']['commit_id']
147 assert data['comment']['comment_id']
148 assert data['comment']['comment_id']
148
149
149
150
150 @pytest.mark.parametrize('EventClass', [RepoCommitCommentEditEvent])
151 @pytest.mark.parametrize('EventClass', [RepoCommitCommentEditEvent])
151 def test_repo_commit_edit_event(config_stub, repo_stub, EventClass):
152 def test_repo_commit_edit_event(config_stub, repo_stub, EventClass):
152
153
153 commit = StrictAttributeDict({
154 commit = StrictAttributeDict({
154 'raw_id': 'raw_id',
155 'raw_id': 'raw_id',
155 'message': 'message',
156 'message': 'message',
156 'branch': 'branch',
157 'branch': 'branch',
157 })
158 })
158
159
159 comment = StrictAttributeDict({
160 comment = StrictAttributeDict({
160 'comment_id': 'comment_id',
161 'comment_id': 'comment_id',
161 'text': 'text',
162 'text': 'text',
162 'comment_type': 'comment_type',
163 'comment_type': 'comment_type',
163 'f_path': 'f_path',
164 'f_path': 'f_path',
164 'line_no': 'line_no',
165 'line_no': 'line_no',
165 'last_version': 0,
166 'last_version': 0,
166 })
167 })
167 event = EventClass(repo=repo_stub, commit=commit, comment=comment)
168 event = EventClass(repo=repo_stub, commit=commit, comment=comment)
168 data = event.as_dict()
169 data = event.as_dict()
169 assert data['commit']['commit_id']
170 assert data['commit']['commit_id']
170 assert data['comment']['comment_id']
171 assert data['comment']['comment_id']
@@ -1,981 +1,981 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23 import 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 @pytest.mark.usefixtures('config_stub')
44 @pytest.mark.usefixtures('config_stub')
45 class TestPullRequestModel(object):
45 class TestPullRequestModel(object):
46
46
47 @pytest.fixture()
47 @pytest.fixture()
48 def pull_request(self, request, backend, pr_util):
48 def pull_request(self, request, backend, pr_util):
49 """
49 """
50 A pull request combined with multiples patches.
50 A pull request combined with multiples patches.
51 """
51 """
52 BackendClass = get_backend(backend.alias)
52 BackendClass = get_backend(backend.alias)
53 merge_resp = MergeResponse(
53 merge_resp = MergeResponse(
54 False, False, None, MergeFailureReason.UNKNOWN,
54 False, False, None, MergeFailureReason.UNKNOWN,
55 metadata={'exception': 'MockError'})
55 metadata={'exception': 'MockError'})
56 self.merge_patcher = mock.patch.object(
56 self.merge_patcher = mock.patch.object(
57 BackendClass, 'merge', return_value=merge_resp)
57 BackendClass, 'merge', return_value=merge_resp)
58 self.workspace_remove_patcher = mock.patch.object(
58 self.workspace_remove_patcher = mock.patch.object(
59 BackendClass, 'cleanup_merge_workspace')
59 BackendClass, 'cleanup_merge_workspace')
60
60
61 self.workspace_remove_mock = self.workspace_remove_patcher.start()
61 self.workspace_remove_mock = self.workspace_remove_patcher.start()
62 self.merge_mock = self.merge_patcher.start()
62 self.merge_mock = self.merge_patcher.start()
63 self.comment_patcher = mock.patch(
63 self.comment_patcher = mock.patch(
64 'rhodecode.model.changeset_status.ChangesetStatusModel.set_status')
64 'rhodecode.model.changeset_status.ChangesetStatusModel.set_status')
65 self.comment_patcher.start()
65 self.comment_patcher.start()
66 self.notification_patcher = mock.patch(
66 self.notification_patcher = mock.patch(
67 'rhodecode.model.notification.NotificationModel.create')
67 'rhodecode.model.notification.NotificationModel.create')
68 self.notification_patcher.start()
68 self.notification_patcher.start()
69 self.helper_patcher = mock.patch(
69 self.helper_patcher = mock.patch(
70 'rhodecode.lib.helpers.route_path')
70 'rhodecode.lib.helpers.route_path')
71 self.helper_patcher.start()
71 self.helper_patcher.start()
72
72
73 self.hook_patcher = mock.patch.object(PullRequestModel,
73 self.hook_patcher = mock.patch.object(PullRequestModel,
74 'trigger_pull_request_hook')
74 'trigger_pull_request_hook')
75 self.hook_mock = self.hook_patcher.start()
75 self.hook_mock = self.hook_patcher.start()
76
76
77 self.invalidation_patcher = mock.patch(
77 self.invalidation_patcher = mock.patch(
78 'rhodecode.model.pull_request.ScmModel.mark_for_invalidation')
78 'rhodecode.model.pull_request.ScmModel.mark_for_invalidation')
79 self.invalidation_mock = self.invalidation_patcher.start()
79 self.invalidation_mock = self.invalidation_patcher.start()
80
80
81 self.pull_request = pr_util.create_pull_request(
81 self.pull_request = pr_util.create_pull_request(
82 mergeable=True, name_suffix=u'Δ…Δ‡')
82 mergeable=True, name_suffix=u'Δ…Δ‡')
83 self.source_commit = self.pull_request.source_ref_parts.commit_id
83 self.source_commit = self.pull_request.source_ref_parts.commit_id
84 self.target_commit = self.pull_request.target_ref_parts.commit_id
84 self.target_commit = self.pull_request.target_ref_parts.commit_id
85 self.workspace_id = 'pr-%s' % self.pull_request.pull_request_id
85 self.workspace_id = 'pr-%s' % self.pull_request.pull_request_id
86 self.repo_id = self.pull_request.target_repo.repo_id
86 self.repo_id = self.pull_request.target_repo.repo_id
87
87
88 @request.addfinalizer
88 @request.addfinalizer
89 def cleanup_pull_request():
89 def cleanup_pull_request():
90 calls = [mock.call(
90 calls = [mock.call(
91 self.pull_request, self.pull_request.author, 'create')]
91 self.pull_request, self.pull_request.author, 'create')]
92 self.hook_mock.assert_has_calls(calls)
92 self.hook_mock.assert_has_calls(calls)
93
93
94 self.workspace_remove_patcher.stop()
94 self.workspace_remove_patcher.stop()
95 self.merge_patcher.stop()
95 self.merge_patcher.stop()
96 self.comment_patcher.stop()
96 self.comment_patcher.stop()
97 self.notification_patcher.stop()
97 self.notification_patcher.stop()
98 self.helper_patcher.stop()
98 self.helper_patcher.stop()
99 self.hook_patcher.stop()
99 self.hook_patcher.stop()
100 self.invalidation_patcher.stop()
100 self.invalidation_patcher.stop()
101
101
102 return self.pull_request
102 return self.pull_request
103
103
104 def test_get_all(self, pull_request):
104 def test_get_all(self, pull_request):
105 prs = PullRequestModel().get_all(pull_request.target_repo)
105 prs = PullRequestModel().get_all(pull_request.target_repo)
106 assert isinstance(prs, list)
106 assert isinstance(prs, list)
107 assert len(prs) == 1
107 assert len(prs) == 1
108
108
109 def test_count_all(self, pull_request):
109 def test_count_all(self, pull_request):
110 pr_count = PullRequestModel().count_all(pull_request.target_repo)
110 pr_count = PullRequestModel().count_all(pull_request.target_repo)
111 assert pr_count == 1
111 assert pr_count == 1
112
112
113 def test_get_awaiting_review(self, pull_request):
113 def test_get_awaiting_review(self, pull_request):
114 prs = PullRequestModel().get_awaiting_review(pull_request.target_repo)
114 prs = PullRequestModel().get_awaiting_review(pull_request.target_repo)
115 assert isinstance(prs, list)
115 assert isinstance(prs, list)
116 assert len(prs) == 1
116 assert len(prs) == 1
117
117
118 def test_count_awaiting_review(self, pull_request):
118 def test_count_awaiting_review(self, pull_request):
119 pr_count = PullRequestModel().count_awaiting_review(
119 pr_count = PullRequestModel().count_awaiting_review(
120 pull_request.target_repo)
120 pull_request.target_repo)
121 assert pr_count == 1
121 assert pr_count == 1
122
122
123 def test_get_awaiting_my_review(self, pull_request):
123 def test_get_awaiting_my_review(self, pull_request):
124 PullRequestModel().update_reviewers(
124 PullRequestModel().update_reviewers(
125 pull_request, [(pull_request.author, ['author'], False, 'reviewer', [])],
125 pull_request, [(pull_request.author, ['author'], False, 'reviewer', [])],
126 pull_request.author)
126 pull_request.author)
127 Session().commit()
127 Session().commit()
128
128
129 prs = PullRequestModel().get_awaiting_my_review(
129 prs = PullRequestModel().get_awaiting_my_review(
130 pull_request.target_repo, user_id=pull_request.author.user_id)
130 pull_request.target_repo.repo_name, user_id=pull_request.author.user_id)
131 assert isinstance(prs, list)
131 assert isinstance(prs, list)
132 assert len(prs) == 1
132 assert len(prs) == 1
133
133
134 def test_count_awaiting_my_review(self, pull_request):
134 def test_count_awaiting_my_review(self, pull_request):
135 PullRequestModel().update_reviewers(
135 PullRequestModel().update_reviewers(
136 pull_request, [(pull_request.author, ['author'], False, 'reviewer', [])],
136 pull_request, [(pull_request.author, ['author'], False, 'reviewer', [])],
137 pull_request.author)
137 pull_request.author)
138 Session().commit()
138 Session().commit()
139
139
140 pr_count = PullRequestModel().count_awaiting_my_review(
140 pr_count = PullRequestModel().count_awaiting_my_review(
141 pull_request.target_repo, user_id=pull_request.author.user_id)
141 pull_request.target_repo.repo_name, user_id=pull_request.author.user_id)
142 assert pr_count == 1
142 assert pr_count == 1
143
143
144 def test_delete_calls_cleanup_merge(self, pull_request):
144 def test_delete_calls_cleanup_merge(self, pull_request):
145 repo_id = pull_request.target_repo.repo_id
145 repo_id = pull_request.target_repo.repo_id
146 PullRequestModel().delete(pull_request, pull_request.author)
146 PullRequestModel().delete(pull_request, pull_request.author)
147 Session().commit()
147 Session().commit()
148
148
149 self.workspace_remove_mock.assert_called_once_with(
149 self.workspace_remove_mock.assert_called_once_with(
150 repo_id, self.workspace_id)
150 repo_id, self.workspace_id)
151
151
152 def test_close_calls_cleanup_and_hook(self, pull_request):
152 def test_close_calls_cleanup_and_hook(self, pull_request):
153 PullRequestModel().close_pull_request(
153 PullRequestModel().close_pull_request(
154 pull_request, pull_request.author)
154 pull_request, pull_request.author)
155 Session().commit()
155 Session().commit()
156
156
157 repo_id = pull_request.target_repo.repo_id
157 repo_id = pull_request.target_repo.repo_id
158
158
159 self.workspace_remove_mock.assert_called_once_with(
159 self.workspace_remove_mock.assert_called_once_with(
160 repo_id, self.workspace_id)
160 repo_id, self.workspace_id)
161 self.hook_mock.assert_called_with(
161 self.hook_mock.assert_called_with(
162 self.pull_request, self.pull_request.author, 'close')
162 self.pull_request, self.pull_request.author, 'close')
163
163
164 def test_merge_status(self, pull_request):
164 def test_merge_status(self, pull_request):
165 self.merge_mock.return_value = MergeResponse(
165 self.merge_mock.return_value = MergeResponse(
166 True, False, None, MergeFailureReason.NONE)
166 True, False, None, MergeFailureReason.NONE)
167
167
168 assert pull_request._last_merge_source_rev is None
168 assert pull_request._last_merge_source_rev is None
169 assert pull_request._last_merge_target_rev is None
169 assert pull_request._last_merge_target_rev is None
170 assert pull_request.last_merge_status is None
170 assert pull_request.last_merge_status is None
171
171
172 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
172 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
173 assert status is True
173 assert status is True
174 assert msg == 'This pull request can be automatically merged.'
174 assert msg == 'This pull request can be automatically merged.'
175 self.merge_mock.assert_called_with(
175 self.merge_mock.assert_called_with(
176 self.repo_id, self.workspace_id,
176 self.repo_id, self.workspace_id,
177 pull_request.target_ref_parts,
177 pull_request.target_ref_parts,
178 pull_request.source_repo.scm_instance(),
178 pull_request.source_repo.scm_instance(),
179 pull_request.source_ref_parts, dry_run=True,
179 pull_request.source_ref_parts, dry_run=True,
180 use_rebase=False, close_branch=False)
180 use_rebase=False, close_branch=False)
181
181
182 assert pull_request._last_merge_source_rev == self.source_commit
182 assert pull_request._last_merge_source_rev == self.source_commit
183 assert pull_request._last_merge_target_rev == self.target_commit
183 assert pull_request._last_merge_target_rev == self.target_commit
184 assert pull_request.last_merge_status is MergeFailureReason.NONE
184 assert pull_request.last_merge_status is MergeFailureReason.NONE
185
185
186 self.merge_mock.reset_mock()
186 self.merge_mock.reset_mock()
187 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
187 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
188 assert status is True
188 assert status is True
189 assert msg == 'This pull request can be automatically merged.'
189 assert msg == 'This pull request can be automatically merged.'
190 assert self.merge_mock.called is False
190 assert self.merge_mock.called is False
191
191
192 def test_merge_status_known_failure(self, pull_request):
192 def test_merge_status_known_failure(self, pull_request):
193 self.merge_mock.return_value = MergeResponse(
193 self.merge_mock.return_value = MergeResponse(
194 False, False, None, MergeFailureReason.MERGE_FAILED,
194 False, False, None, MergeFailureReason.MERGE_FAILED,
195 metadata={'unresolved_files': 'file1'})
195 metadata={'unresolved_files': 'file1'})
196
196
197 assert pull_request._last_merge_source_rev is None
197 assert pull_request._last_merge_source_rev is None
198 assert pull_request._last_merge_target_rev is None
198 assert pull_request._last_merge_target_rev is None
199 assert pull_request.last_merge_status is None
199 assert pull_request.last_merge_status is None
200
200
201 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
201 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
202 assert status is False
202 assert status is False
203 assert msg == 'This pull request cannot be merged because of merge conflicts. file1'
203 assert msg == 'This pull request cannot be merged because of merge conflicts. file1'
204 self.merge_mock.assert_called_with(
204 self.merge_mock.assert_called_with(
205 self.repo_id, self.workspace_id,
205 self.repo_id, self.workspace_id,
206 pull_request.target_ref_parts,
206 pull_request.target_ref_parts,
207 pull_request.source_repo.scm_instance(),
207 pull_request.source_repo.scm_instance(),
208 pull_request.source_ref_parts, dry_run=True,
208 pull_request.source_ref_parts, dry_run=True,
209 use_rebase=False, close_branch=False)
209 use_rebase=False, close_branch=False)
210
210
211 assert pull_request._last_merge_source_rev == self.source_commit
211 assert pull_request._last_merge_source_rev == self.source_commit
212 assert pull_request._last_merge_target_rev == self.target_commit
212 assert pull_request._last_merge_target_rev == self.target_commit
213 assert pull_request.last_merge_status is MergeFailureReason.MERGE_FAILED
213 assert pull_request.last_merge_status is MergeFailureReason.MERGE_FAILED
214
214
215 self.merge_mock.reset_mock()
215 self.merge_mock.reset_mock()
216 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
216 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
217 assert status is False
217 assert status is False
218 assert msg == 'This pull request cannot be merged because of merge conflicts. file1'
218 assert msg == 'This pull request cannot be merged because of merge conflicts. file1'
219 assert self.merge_mock.called is False
219 assert self.merge_mock.called is False
220
220
221 def test_merge_status_unknown_failure(self, pull_request):
221 def test_merge_status_unknown_failure(self, pull_request):
222 self.merge_mock.return_value = MergeResponse(
222 self.merge_mock.return_value = MergeResponse(
223 False, False, None, MergeFailureReason.UNKNOWN,
223 False, False, None, MergeFailureReason.UNKNOWN,
224 metadata={'exception': 'MockError'})
224 metadata={'exception': 'MockError'})
225
225
226 assert pull_request._last_merge_source_rev is None
226 assert pull_request._last_merge_source_rev is None
227 assert pull_request._last_merge_target_rev is None
227 assert pull_request._last_merge_target_rev is None
228 assert pull_request.last_merge_status is None
228 assert pull_request.last_merge_status is None
229
229
230 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
230 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
231 assert status is False
231 assert status is False
232 assert msg == (
232 assert msg == (
233 'This pull request cannot be merged because of an unhandled exception. '
233 'This pull request cannot be merged because of an unhandled exception. '
234 'MockError')
234 'MockError')
235 self.merge_mock.assert_called_with(
235 self.merge_mock.assert_called_with(
236 self.repo_id, self.workspace_id,
236 self.repo_id, self.workspace_id,
237 pull_request.target_ref_parts,
237 pull_request.target_ref_parts,
238 pull_request.source_repo.scm_instance(),
238 pull_request.source_repo.scm_instance(),
239 pull_request.source_ref_parts, dry_run=True,
239 pull_request.source_ref_parts, dry_run=True,
240 use_rebase=False, close_branch=False)
240 use_rebase=False, close_branch=False)
241
241
242 assert pull_request._last_merge_source_rev is None
242 assert pull_request._last_merge_source_rev is None
243 assert pull_request._last_merge_target_rev is None
243 assert pull_request._last_merge_target_rev is None
244 assert pull_request.last_merge_status is None
244 assert pull_request.last_merge_status is None
245
245
246 self.merge_mock.reset_mock()
246 self.merge_mock.reset_mock()
247 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
247 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
248 assert status is False
248 assert status is False
249 assert msg == (
249 assert msg == (
250 'This pull request cannot be merged because of an unhandled exception. '
250 'This pull request cannot be merged because of an unhandled exception. '
251 'MockError')
251 'MockError')
252 assert self.merge_mock.called is True
252 assert self.merge_mock.called is True
253
253
254 def test_merge_status_when_target_is_locked(self, pull_request):
254 def test_merge_status_when_target_is_locked(self, pull_request):
255 pull_request.target_repo.locked = [1, u'12345.50', 'lock_web']
255 pull_request.target_repo.locked = [1, u'12345.50', 'lock_web']
256 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
256 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
257 assert status is False
257 assert status is False
258 assert msg == (
258 assert msg == (
259 'This pull request cannot be merged because the target repository '
259 'This pull request cannot be merged because the target repository '
260 'is locked by user:1.')
260 'is locked by user:1.')
261
261
262 def test_merge_status_requirements_check_target(self, pull_request):
262 def test_merge_status_requirements_check_target(self, pull_request):
263
263
264 def has_largefiles(self, repo):
264 def has_largefiles(self, repo):
265 return repo == pull_request.source_repo
265 return repo == pull_request.source_repo
266
266
267 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
267 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
268 with patcher:
268 with patcher:
269 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
269 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
270
270
271 assert status is False
271 assert status is False
272 assert msg == 'Target repository large files support is disabled.'
272 assert msg == 'Target repository large files support is disabled.'
273
273
274 def test_merge_status_requirements_check_source(self, pull_request):
274 def test_merge_status_requirements_check_source(self, pull_request):
275
275
276 def has_largefiles(self, repo):
276 def has_largefiles(self, repo):
277 return repo == pull_request.target_repo
277 return repo == pull_request.target_repo
278
278
279 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
279 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
280 with patcher:
280 with patcher:
281 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
281 merge_response, status, msg = PullRequestModel().merge_status(pull_request)
282
282
283 assert status is False
283 assert status is False
284 assert msg == 'Source repository large files support is disabled.'
284 assert msg == 'Source repository large files support is disabled.'
285
285
286 def test_merge(self, pull_request, merge_extras):
286 def test_merge(self, pull_request, merge_extras):
287 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
287 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
288 merge_ref = Reference(
288 merge_ref = Reference(
289 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
289 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
290 self.merge_mock.return_value = MergeResponse(
290 self.merge_mock.return_value = MergeResponse(
291 True, True, merge_ref, MergeFailureReason.NONE)
291 True, True, merge_ref, MergeFailureReason.NONE)
292
292
293 merge_extras['repository'] = pull_request.target_repo.repo_name
293 merge_extras['repository'] = pull_request.target_repo.repo_name
294 PullRequestModel().merge_repo(
294 PullRequestModel().merge_repo(
295 pull_request, pull_request.author, extras=merge_extras)
295 pull_request, pull_request.author, extras=merge_extras)
296 Session().commit()
296 Session().commit()
297
297
298 message = (
298 message = (
299 u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}'
299 u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}'
300 u'\n\n {pr_title}'.format(
300 u'\n\n {pr_title}'.format(
301 pr_id=pull_request.pull_request_id,
301 pr_id=pull_request.pull_request_id,
302 source_repo=safe_unicode(
302 source_repo=safe_unicode(
303 pull_request.source_repo.scm_instance().name),
303 pull_request.source_repo.scm_instance().name),
304 source_ref_name=pull_request.source_ref_parts.name,
304 source_ref_name=pull_request.source_ref_parts.name,
305 pr_title=safe_unicode(pull_request.title)
305 pr_title=safe_unicode(pull_request.title)
306 )
306 )
307 )
307 )
308 self.merge_mock.assert_called_with(
308 self.merge_mock.assert_called_with(
309 self.repo_id, self.workspace_id,
309 self.repo_id, self.workspace_id,
310 pull_request.target_ref_parts,
310 pull_request.target_ref_parts,
311 pull_request.source_repo.scm_instance(),
311 pull_request.source_repo.scm_instance(),
312 pull_request.source_ref_parts,
312 pull_request.source_ref_parts,
313 user_name=user.short_contact, user_email=user.email, message=message,
313 user_name=user.short_contact, user_email=user.email, message=message,
314 use_rebase=False, close_branch=False
314 use_rebase=False, close_branch=False
315 )
315 )
316 self.invalidation_mock.assert_called_once_with(
316 self.invalidation_mock.assert_called_once_with(
317 pull_request.target_repo.repo_name)
317 pull_request.target_repo.repo_name)
318
318
319 self.hook_mock.assert_called_with(
319 self.hook_mock.assert_called_with(
320 self.pull_request, self.pull_request.author, 'merge')
320 self.pull_request, self.pull_request.author, 'merge')
321
321
322 pull_request = PullRequest.get(pull_request.pull_request_id)
322 pull_request = PullRequest.get(pull_request.pull_request_id)
323 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
323 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
324
324
325 def test_merge_with_status_lock(self, pull_request, merge_extras):
325 def test_merge_with_status_lock(self, pull_request, merge_extras):
326 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
326 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
327 merge_ref = Reference(
327 merge_ref = Reference(
328 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
328 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
329 self.merge_mock.return_value = MergeResponse(
329 self.merge_mock.return_value = MergeResponse(
330 True, True, merge_ref, MergeFailureReason.NONE)
330 True, True, merge_ref, MergeFailureReason.NONE)
331
331
332 merge_extras['repository'] = pull_request.target_repo.repo_name
332 merge_extras['repository'] = pull_request.target_repo.repo_name
333
333
334 with pull_request.set_state(PullRequest.STATE_UPDATING):
334 with pull_request.set_state(PullRequest.STATE_UPDATING):
335 assert pull_request.pull_request_state == PullRequest.STATE_UPDATING
335 assert pull_request.pull_request_state == PullRequest.STATE_UPDATING
336 PullRequestModel().merge_repo(
336 PullRequestModel().merge_repo(
337 pull_request, pull_request.author, extras=merge_extras)
337 pull_request, pull_request.author, extras=merge_extras)
338 Session().commit()
338 Session().commit()
339
339
340 assert pull_request.pull_request_state == PullRequest.STATE_CREATED
340 assert pull_request.pull_request_state == PullRequest.STATE_CREATED
341
341
342 message = (
342 message = (
343 u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}'
343 u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}'
344 u'\n\n {pr_title}'.format(
344 u'\n\n {pr_title}'.format(
345 pr_id=pull_request.pull_request_id,
345 pr_id=pull_request.pull_request_id,
346 source_repo=safe_unicode(
346 source_repo=safe_unicode(
347 pull_request.source_repo.scm_instance().name),
347 pull_request.source_repo.scm_instance().name),
348 source_ref_name=pull_request.source_ref_parts.name,
348 source_ref_name=pull_request.source_ref_parts.name,
349 pr_title=safe_unicode(pull_request.title)
349 pr_title=safe_unicode(pull_request.title)
350 )
350 )
351 )
351 )
352 self.merge_mock.assert_called_with(
352 self.merge_mock.assert_called_with(
353 self.repo_id, self.workspace_id,
353 self.repo_id, self.workspace_id,
354 pull_request.target_ref_parts,
354 pull_request.target_ref_parts,
355 pull_request.source_repo.scm_instance(),
355 pull_request.source_repo.scm_instance(),
356 pull_request.source_ref_parts,
356 pull_request.source_ref_parts,
357 user_name=user.short_contact, user_email=user.email, message=message,
357 user_name=user.short_contact, user_email=user.email, message=message,
358 use_rebase=False, close_branch=False
358 use_rebase=False, close_branch=False
359 )
359 )
360 self.invalidation_mock.assert_called_once_with(
360 self.invalidation_mock.assert_called_once_with(
361 pull_request.target_repo.repo_name)
361 pull_request.target_repo.repo_name)
362
362
363 self.hook_mock.assert_called_with(
363 self.hook_mock.assert_called_with(
364 self.pull_request, self.pull_request.author, 'merge')
364 self.pull_request, self.pull_request.author, 'merge')
365
365
366 pull_request = PullRequest.get(pull_request.pull_request_id)
366 pull_request = PullRequest.get(pull_request.pull_request_id)
367 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
367 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
368
368
369 def test_merge_failed(self, pull_request, merge_extras):
369 def test_merge_failed(self, pull_request, merge_extras):
370 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
370 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
371 merge_ref = Reference(
371 merge_ref = Reference(
372 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
372 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
373 self.merge_mock.return_value = MergeResponse(
373 self.merge_mock.return_value = MergeResponse(
374 False, False, merge_ref, MergeFailureReason.MERGE_FAILED)
374 False, False, merge_ref, MergeFailureReason.MERGE_FAILED)
375
375
376 merge_extras['repository'] = pull_request.target_repo.repo_name
376 merge_extras['repository'] = pull_request.target_repo.repo_name
377 PullRequestModel().merge_repo(
377 PullRequestModel().merge_repo(
378 pull_request, pull_request.author, extras=merge_extras)
378 pull_request, pull_request.author, extras=merge_extras)
379 Session().commit()
379 Session().commit()
380
380
381 message = (
381 message = (
382 u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}'
382 u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}'
383 u'\n\n {pr_title}'.format(
383 u'\n\n {pr_title}'.format(
384 pr_id=pull_request.pull_request_id,
384 pr_id=pull_request.pull_request_id,
385 source_repo=safe_unicode(
385 source_repo=safe_unicode(
386 pull_request.source_repo.scm_instance().name),
386 pull_request.source_repo.scm_instance().name),
387 source_ref_name=pull_request.source_ref_parts.name,
387 source_ref_name=pull_request.source_ref_parts.name,
388 pr_title=safe_unicode(pull_request.title)
388 pr_title=safe_unicode(pull_request.title)
389 )
389 )
390 )
390 )
391 self.merge_mock.assert_called_with(
391 self.merge_mock.assert_called_with(
392 self.repo_id, self.workspace_id,
392 self.repo_id, self.workspace_id,
393 pull_request.target_ref_parts,
393 pull_request.target_ref_parts,
394 pull_request.source_repo.scm_instance(),
394 pull_request.source_repo.scm_instance(),
395 pull_request.source_ref_parts,
395 pull_request.source_ref_parts,
396 user_name=user.short_contact, user_email=user.email, message=message,
396 user_name=user.short_contact, user_email=user.email, message=message,
397 use_rebase=False, close_branch=False
397 use_rebase=False, close_branch=False
398 )
398 )
399
399
400 pull_request = PullRequest.get(pull_request.pull_request_id)
400 pull_request = PullRequest.get(pull_request.pull_request_id)
401 assert self.invalidation_mock.called is False
401 assert self.invalidation_mock.called is False
402 assert pull_request.merge_rev is None
402 assert pull_request.merge_rev is None
403
403
404 def test_get_commit_ids(self, pull_request):
404 def test_get_commit_ids(self, pull_request):
405 # The PR has been not merged yet, so expect an exception
405 # The PR has been not merged yet, so expect an exception
406 with pytest.raises(ValueError):
406 with pytest.raises(ValueError):
407 PullRequestModel()._get_commit_ids(pull_request)
407 PullRequestModel()._get_commit_ids(pull_request)
408
408
409 # Merge revision is in the revisions list
409 # Merge revision is in the revisions list
410 pull_request.merge_rev = pull_request.revisions[0]
410 pull_request.merge_rev = pull_request.revisions[0]
411 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
411 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
412 assert commit_ids == pull_request.revisions
412 assert commit_ids == pull_request.revisions
413
413
414 # Merge revision is not in the revisions list
414 # Merge revision is not in the revisions list
415 pull_request.merge_rev = 'f000' * 10
415 pull_request.merge_rev = 'f000' * 10
416 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
416 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
417 assert commit_ids == pull_request.revisions + [pull_request.merge_rev]
417 assert commit_ids == pull_request.revisions + [pull_request.merge_rev]
418
418
419 def test_get_diff_from_pr_version(self, pull_request):
419 def test_get_diff_from_pr_version(self, pull_request):
420 source_repo = pull_request.source_repo
420 source_repo = pull_request.source_repo
421 source_ref_id = pull_request.source_ref_parts.commit_id
421 source_ref_id = pull_request.source_ref_parts.commit_id
422 target_ref_id = pull_request.target_ref_parts.commit_id
422 target_ref_id = pull_request.target_ref_parts.commit_id
423 diff = PullRequestModel()._get_diff_from_pr_or_version(
423 diff = PullRequestModel()._get_diff_from_pr_or_version(
424 source_repo, source_ref_id, target_ref_id,
424 source_repo, source_ref_id, target_ref_id,
425 hide_whitespace_changes=False, diff_context=6)
425 hide_whitespace_changes=False, diff_context=6)
426 assert 'file_1' in diff.raw
426 assert 'file_1' in diff.raw
427
427
428 def test_generate_title_returns_unicode(self):
428 def test_generate_title_returns_unicode(self):
429 title = PullRequestModel().generate_pullrequest_title(
429 title = PullRequestModel().generate_pullrequest_title(
430 source='source-dummy',
430 source='source-dummy',
431 source_ref='source-ref-dummy',
431 source_ref='source-ref-dummy',
432 target='target-dummy',
432 target='target-dummy',
433 )
433 )
434 assert type(title) == unicode
434 assert type(title) == unicode
435
435
436 @pytest.mark.parametrize('title, has_wip', [
436 @pytest.mark.parametrize('title, has_wip', [
437 ('hello', False),
437 ('hello', False),
438 ('hello wip', False),
438 ('hello wip', False),
439 ('hello wip: xxx', False),
439 ('hello wip: xxx', False),
440 ('[wip] hello', True),
440 ('[wip] hello', True),
441 ('[wip] hello', True),
441 ('[wip] hello', True),
442 ('wip: hello', True),
442 ('wip: hello', True),
443 ('wip hello', True),
443 ('wip hello', True),
444
444
445 ])
445 ])
446 def test_wip_title_marker(self, pull_request, title, has_wip):
446 def test_wip_title_marker(self, pull_request, title, has_wip):
447 pull_request.title = title
447 pull_request.title = title
448 assert pull_request.work_in_progress == has_wip
448 assert pull_request.work_in_progress == has_wip
449
449
450
450
451 @pytest.mark.usefixtures('config_stub')
451 @pytest.mark.usefixtures('config_stub')
452 class TestIntegrationMerge(object):
452 class TestIntegrationMerge(object):
453 @pytest.mark.parametrize('extra_config', (
453 @pytest.mark.parametrize('extra_config', (
454 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
454 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
455 ))
455 ))
456 def test_merge_triggers_push_hooks(
456 def test_merge_triggers_push_hooks(
457 self, pr_util, user_admin, capture_rcextensions, merge_extras,
457 self, pr_util, user_admin, capture_rcextensions, merge_extras,
458 extra_config):
458 extra_config):
459
459
460 pull_request = pr_util.create_pull_request(
460 pull_request = pr_util.create_pull_request(
461 approved=True, mergeable=True)
461 approved=True, mergeable=True)
462 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
462 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
463 merge_extras['repository'] = pull_request.target_repo.repo_name
463 merge_extras['repository'] = pull_request.target_repo.repo_name
464 Session().commit()
464 Session().commit()
465
465
466 with mock.patch.dict(rhodecode.CONFIG, extra_config, clear=False):
466 with mock.patch.dict(rhodecode.CONFIG, extra_config, clear=False):
467 merge_state = PullRequestModel().merge_repo(
467 merge_state = PullRequestModel().merge_repo(
468 pull_request, user_admin, extras=merge_extras)
468 pull_request, user_admin, extras=merge_extras)
469 Session().commit()
469 Session().commit()
470
470
471 assert merge_state.executed
471 assert merge_state.executed
472 assert '_pre_push_hook' in capture_rcextensions
472 assert '_pre_push_hook' in capture_rcextensions
473 assert '_push_hook' in capture_rcextensions
473 assert '_push_hook' in capture_rcextensions
474
474
475 def test_merge_can_be_rejected_by_pre_push_hook(
475 def test_merge_can_be_rejected_by_pre_push_hook(
476 self, pr_util, user_admin, capture_rcextensions, merge_extras):
476 self, pr_util, user_admin, capture_rcextensions, merge_extras):
477 pull_request = pr_util.create_pull_request(
477 pull_request = pr_util.create_pull_request(
478 approved=True, mergeable=True)
478 approved=True, mergeable=True)
479 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
479 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
480 merge_extras['repository'] = pull_request.target_repo.repo_name
480 merge_extras['repository'] = pull_request.target_repo.repo_name
481 Session().commit()
481 Session().commit()
482
482
483 with mock.patch('rhodecode.EXTENSIONS.PRE_PUSH_HOOK') as pre_pull:
483 with mock.patch('rhodecode.EXTENSIONS.PRE_PUSH_HOOK') as pre_pull:
484 pre_pull.side_effect = RepositoryError("Disallow push!")
484 pre_pull.side_effect = RepositoryError("Disallow push!")
485 merge_status = PullRequestModel().merge_repo(
485 merge_status = PullRequestModel().merge_repo(
486 pull_request, user_admin, extras=merge_extras)
486 pull_request, user_admin, extras=merge_extras)
487 Session().commit()
487 Session().commit()
488
488
489 assert not merge_status.executed
489 assert not merge_status.executed
490 assert 'pre_push' not in capture_rcextensions
490 assert 'pre_push' not in capture_rcextensions
491 assert 'post_push' not in capture_rcextensions
491 assert 'post_push' not in capture_rcextensions
492
492
493 def test_merge_fails_if_target_is_locked(
493 def test_merge_fails_if_target_is_locked(
494 self, pr_util, user_regular, merge_extras):
494 self, pr_util, user_regular, merge_extras):
495 pull_request = pr_util.create_pull_request(
495 pull_request = pr_util.create_pull_request(
496 approved=True, mergeable=True)
496 approved=True, mergeable=True)
497 locked_by = [user_regular.user_id + 1, 12345.50, 'lock_web']
497 locked_by = [user_regular.user_id + 1, 12345.50, 'lock_web']
498 pull_request.target_repo.locked = locked_by
498 pull_request.target_repo.locked = locked_by
499 # TODO: johbo: Check if this can work based on the database, currently
499 # TODO: johbo: Check if this can work based on the database, currently
500 # all data is pre-computed, that's why just updating the DB is not
500 # all data is pre-computed, that's why just updating the DB is not
501 # enough.
501 # enough.
502 merge_extras['locked_by'] = locked_by
502 merge_extras['locked_by'] = locked_by
503 merge_extras['repository'] = pull_request.target_repo.repo_name
503 merge_extras['repository'] = pull_request.target_repo.repo_name
504 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
504 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
505 Session().commit()
505 Session().commit()
506 merge_status = PullRequestModel().merge_repo(
506 merge_status = PullRequestModel().merge_repo(
507 pull_request, user_regular, extras=merge_extras)
507 pull_request, user_regular, extras=merge_extras)
508 Session().commit()
508 Session().commit()
509
509
510 assert not merge_status.executed
510 assert not merge_status.executed
511
511
512
512
513 @pytest.mark.parametrize('use_outdated, inlines_count, outdated_count', [
513 @pytest.mark.parametrize('use_outdated, inlines_count, outdated_count', [
514 (False, 1, 0),
514 (False, 1, 0),
515 (True, 0, 1),
515 (True, 0, 1),
516 ])
516 ])
517 def test_outdated_comments(
517 def test_outdated_comments(
518 pr_util, use_outdated, inlines_count, outdated_count, config_stub):
518 pr_util, use_outdated, inlines_count, outdated_count, config_stub):
519 pull_request = pr_util.create_pull_request()
519 pull_request = pr_util.create_pull_request()
520 pr_util.create_inline_comment(file_path='not_in_updated_diff')
520 pr_util.create_inline_comment(file_path='not_in_updated_diff')
521
521
522 with outdated_comments_patcher(use_outdated) as outdated_comment_mock:
522 with outdated_comments_patcher(use_outdated) as outdated_comment_mock:
523 pr_util.add_one_commit()
523 pr_util.add_one_commit()
524 assert_inline_comments(
524 assert_inline_comments(
525 pull_request, visible=inlines_count, outdated=outdated_count)
525 pull_request, visible=inlines_count, outdated=outdated_count)
526 outdated_comment_mock.assert_called_with(pull_request)
526 outdated_comment_mock.assert_called_with(pull_request)
527
527
528
528
529 @pytest.mark.parametrize('mr_type, expected_msg', [
529 @pytest.mark.parametrize('mr_type, expected_msg', [
530 (MergeFailureReason.NONE,
530 (MergeFailureReason.NONE,
531 'This pull request can be automatically merged.'),
531 'This pull request can be automatically merged.'),
532 (MergeFailureReason.UNKNOWN,
532 (MergeFailureReason.UNKNOWN,
533 'This pull request cannot be merged because of an unhandled exception. CRASH'),
533 'This pull request cannot be merged because of an unhandled exception. CRASH'),
534 (MergeFailureReason.MERGE_FAILED,
534 (MergeFailureReason.MERGE_FAILED,
535 'This pull request cannot be merged because of merge conflicts. CONFLICT_FILE'),
535 'This pull request cannot be merged because of merge conflicts. CONFLICT_FILE'),
536 (MergeFailureReason.PUSH_FAILED,
536 (MergeFailureReason.PUSH_FAILED,
537 'This pull request could not be merged because push to target:`some-repo@merge_commit` failed.'),
537 'This pull request could not be merged because push to target:`some-repo@merge_commit` failed.'),
538 (MergeFailureReason.TARGET_IS_NOT_HEAD,
538 (MergeFailureReason.TARGET_IS_NOT_HEAD,
539 'This pull request cannot be merged because the target `ref_name` is not a head.'),
539 'This pull request cannot be merged because the target `ref_name` is not a head.'),
540 (MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES,
540 (MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES,
541 'This pull request cannot be merged because the source contains more branches than the target.'),
541 'This pull request cannot be merged because the source contains more branches than the target.'),
542 (MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS,
542 (MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS,
543 'This pull request cannot be merged because the target `ref_name` has multiple heads: `a,b,c`.'),
543 'This pull request cannot be merged because the target `ref_name` has multiple heads: `a,b,c`.'),
544 (MergeFailureReason.TARGET_IS_LOCKED,
544 (MergeFailureReason.TARGET_IS_LOCKED,
545 'This pull request cannot be merged because the target repository is locked by user:123.'),
545 'This pull request cannot be merged because the target repository is locked by user:123.'),
546 (MergeFailureReason.MISSING_TARGET_REF,
546 (MergeFailureReason.MISSING_TARGET_REF,
547 'This pull request cannot be merged because the target reference `ref_name` is missing.'),
547 'This pull request cannot be merged because the target reference `ref_name` is missing.'),
548 (MergeFailureReason.MISSING_SOURCE_REF,
548 (MergeFailureReason.MISSING_SOURCE_REF,
549 'This pull request cannot be merged because the source reference `ref_name` is missing.'),
549 'This pull request cannot be merged because the source reference `ref_name` is missing.'),
550 (MergeFailureReason.SUBREPO_MERGE_FAILED,
550 (MergeFailureReason.SUBREPO_MERGE_FAILED,
551 'This pull request cannot be merged because of conflicts related to sub repositories.'),
551 'This pull request cannot be merged because of conflicts related to sub repositories.'),
552
552
553 ])
553 ])
554 def test_merge_response_message(mr_type, expected_msg):
554 def test_merge_response_message(mr_type, expected_msg):
555 merge_ref = Reference('type', 'ref_name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
555 merge_ref = Reference('type', 'ref_name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
556 metadata = {
556 metadata = {
557 'unresolved_files': 'CONFLICT_FILE',
557 'unresolved_files': 'CONFLICT_FILE',
558 'exception': "CRASH",
558 'exception': "CRASH",
559 'target': 'some-repo',
559 'target': 'some-repo',
560 'merge_commit': 'merge_commit',
560 'merge_commit': 'merge_commit',
561 'target_ref': merge_ref,
561 'target_ref': merge_ref,
562 'source_ref': merge_ref,
562 'source_ref': merge_ref,
563 'heads': ','.join(['a', 'b', 'c']),
563 'heads': ','.join(['a', 'b', 'c']),
564 'locked_by': 'user:123'
564 'locked_by': 'user:123'
565 }
565 }
566
566
567 merge_response = MergeResponse(True, True, merge_ref, mr_type, metadata=metadata)
567 merge_response = MergeResponse(True, True, merge_ref, mr_type, metadata=metadata)
568 assert merge_response.merge_status_message == expected_msg
568 assert merge_response.merge_status_message == expected_msg
569
569
570
570
571 @pytest.fixture()
571 @pytest.fixture()
572 def merge_extras(user_regular):
572 def merge_extras(user_regular):
573 """
573 """
574 Context for the vcs operation when running a merge.
574 Context for the vcs operation when running a merge.
575 """
575 """
576 extras = {
576 extras = {
577 'ip': '127.0.0.1',
577 'ip': '127.0.0.1',
578 'username': user_regular.username,
578 'username': user_regular.username,
579 'user_id': user_regular.user_id,
579 'user_id': user_regular.user_id,
580 'action': 'push',
580 'action': 'push',
581 'repository': 'fake_target_repo_name',
581 'repository': 'fake_target_repo_name',
582 'scm': 'git',
582 'scm': 'git',
583 'config': 'fake_config_ini_path',
583 'config': 'fake_config_ini_path',
584 'repo_store': '',
584 'repo_store': '',
585 'make_lock': None,
585 'make_lock': None,
586 'locked_by': [None, None, None],
586 'locked_by': [None, None, None],
587 'server_url': 'http://test.example.com:5000',
587 'server_url': 'http://test.example.com:5000',
588 'hooks': ['push', 'pull'],
588 'hooks': ['push', 'pull'],
589 'is_shadow_repo': False,
589 'is_shadow_repo': False,
590 }
590 }
591 return extras
591 return extras
592
592
593
593
594 @pytest.mark.usefixtures('config_stub')
594 @pytest.mark.usefixtures('config_stub')
595 class TestUpdateCommentHandling(object):
595 class TestUpdateCommentHandling(object):
596
596
597 @pytest.fixture(autouse=True, scope='class')
597 @pytest.fixture(autouse=True, scope='class')
598 def enable_outdated_comments(self, request, baseapp):
598 def enable_outdated_comments(self, request, baseapp):
599 config_patch = mock.patch.dict(
599 config_patch = mock.patch.dict(
600 'rhodecode.CONFIG', {'rhodecode_use_outdated_comments': True})
600 'rhodecode.CONFIG', {'rhodecode_use_outdated_comments': True})
601 config_patch.start()
601 config_patch.start()
602
602
603 @request.addfinalizer
603 @request.addfinalizer
604 def cleanup():
604 def cleanup():
605 config_patch.stop()
605 config_patch.stop()
606
606
607 def test_comment_stays_unflagged_on_unchanged_diff(self, pr_util):
607 def test_comment_stays_unflagged_on_unchanged_diff(self, pr_util):
608 commits = [
608 commits = [
609 {'message': 'a'},
609 {'message': 'a'},
610 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
610 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
611 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
611 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
612 ]
612 ]
613 pull_request = pr_util.create_pull_request(
613 pull_request = pr_util.create_pull_request(
614 commits=commits, target_head='a', source_head='b', revisions=['b'])
614 commits=commits, target_head='a', source_head='b', revisions=['b'])
615 pr_util.create_inline_comment(file_path='file_b')
615 pr_util.create_inline_comment(file_path='file_b')
616 pr_util.add_one_commit(head='c')
616 pr_util.add_one_commit(head='c')
617
617
618 assert_inline_comments(pull_request, visible=1, outdated=0)
618 assert_inline_comments(pull_request, visible=1, outdated=0)
619
619
620 def test_comment_stays_unflagged_on_change_above(self, pr_util):
620 def test_comment_stays_unflagged_on_change_above(self, pr_util):
621 original_content = ''.join(
621 original_content = ''.join(
622 ['line {}\n'.format(x) for x in range(1, 11)])
622 ['line {}\n'.format(x) for x in range(1, 11)])
623 updated_content = 'new_line_at_top\n' + original_content
623 updated_content = 'new_line_at_top\n' + original_content
624 commits = [
624 commits = [
625 {'message': 'a'},
625 {'message': 'a'},
626 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
626 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
627 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
627 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
628 ]
628 ]
629 pull_request = pr_util.create_pull_request(
629 pull_request = pr_util.create_pull_request(
630 commits=commits, target_head='a', source_head='b', revisions=['b'])
630 commits=commits, target_head='a', source_head='b', revisions=['b'])
631
631
632 with outdated_comments_patcher():
632 with outdated_comments_patcher():
633 comment = pr_util.create_inline_comment(
633 comment = pr_util.create_inline_comment(
634 line_no=u'n8', file_path='file_b')
634 line_no=u'n8', file_path='file_b')
635 pr_util.add_one_commit(head='c')
635 pr_util.add_one_commit(head='c')
636
636
637 assert_inline_comments(pull_request, visible=1, outdated=0)
637 assert_inline_comments(pull_request, visible=1, outdated=0)
638 assert comment.line_no == u'n9'
638 assert comment.line_no == u'n9'
639
639
640 def test_comment_stays_unflagged_on_change_below(self, pr_util):
640 def test_comment_stays_unflagged_on_change_below(self, pr_util):
641 original_content = ''.join(['line {}\n'.format(x) for x in range(10)])
641 original_content = ''.join(['line {}\n'.format(x) for x in range(10)])
642 updated_content = original_content + 'new_line_at_end\n'
642 updated_content = original_content + 'new_line_at_end\n'
643 commits = [
643 commits = [
644 {'message': 'a'},
644 {'message': 'a'},
645 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
645 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
646 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
646 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
647 ]
647 ]
648 pull_request = pr_util.create_pull_request(
648 pull_request = pr_util.create_pull_request(
649 commits=commits, target_head='a', source_head='b', revisions=['b'])
649 commits=commits, target_head='a', source_head='b', revisions=['b'])
650 pr_util.create_inline_comment(file_path='file_b')
650 pr_util.create_inline_comment(file_path='file_b')
651 pr_util.add_one_commit(head='c')
651 pr_util.add_one_commit(head='c')
652
652
653 assert_inline_comments(pull_request, visible=1, outdated=0)
653 assert_inline_comments(pull_request, visible=1, outdated=0)
654
654
655 @pytest.mark.parametrize('line_no', ['n4', 'o4', 'n10', 'o9'])
655 @pytest.mark.parametrize('line_no', ['n4', 'o4', 'n10', 'o9'])
656 def test_comment_flagged_on_change_around_context(self, pr_util, line_no):
656 def test_comment_flagged_on_change_around_context(self, pr_util, line_no):
657 base_lines = ['line {}\n'.format(x) for x in range(1, 13)]
657 base_lines = ['line {}\n'.format(x) for x in range(1, 13)]
658 change_lines = list(base_lines)
658 change_lines = list(base_lines)
659 change_lines.insert(6, 'line 6a added\n')
659 change_lines.insert(6, 'line 6a added\n')
660
660
661 # Changes on the last line of sight
661 # Changes on the last line of sight
662 update_lines = list(change_lines)
662 update_lines = list(change_lines)
663 update_lines[0] = 'line 1 changed\n'
663 update_lines[0] = 'line 1 changed\n'
664 update_lines[-1] = 'line 12 changed\n'
664 update_lines[-1] = 'line 12 changed\n'
665
665
666 def file_b(lines):
666 def file_b(lines):
667 return FileNode('file_b', ''.join(lines))
667 return FileNode('file_b', ''.join(lines))
668
668
669 commits = [
669 commits = [
670 {'message': 'a', 'added': [file_b(base_lines)]},
670 {'message': 'a', 'added': [file_b(base_lines)]},
671 {'message': 'b', 'changed': [file_b(change_lines)]},
671 {'message': 'b', 'changed': [file_b(change_lines)]},
672 {'message': 'c', 'changed': [file_b(update_lines)]},
672 {'message': 'c', 'changed': [file_b(update_lines)]},
673 ]
673 ]
674
674
675 pull_request = pr_util.create_pull_request(
675 pull_request = pr_util.create_pull_request(
676 commits=commits, target_head='a', source_head='b', revisions=['b'])
676 commits=commits, target_head='a', source_head='b', revisions=['b'])
677 pr_util.create_inline_comment(line_no=line_no, file_path='file_b')
677 pr_util.create_inline_comment(line_no=line_no, file_path='file_b')
678
678
679 with outdated_comments_patcher():
679 with outdated_comments_patcher():
680 pr_util.add_one_commit(head='c')
680 pr_util.add_one_commit(head='c')
681 assert_inline_comments(pull_request, visible=0, outdated=1)
681 assert_inline_comments(pull_request, visible=0, outdated=1)
682
682
683 @pytest.mark.parametrize("change, content", [
683 @pytest.mark.parametrize("change, content", [
684 ('changed', 'changed\n'),
684 ('changed', 'changed\n'),
685 ('removed', ''),
685 ('removed', ''),
686 ], ids=['changed', 'removed'])
686 ], ids=['changed', 'removed'])
687 def test_comment_flagged_on_change(self, pr_util, change, content):
687 def test_comment_flagged_on_change(self, pr_util, change, content):
688 commits = [
688 commits = [
689 {'message': 'a'},
689 {'message': 'a'},
690 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
690 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
691 {'message': 'c', change: [FileNode('file_b', content)]},
691 {'message': 'c', change: [FileNode('file_b', content)]},
692 ]
692 ]
693 pull_request = pr_util.create_pull_request(
693 pull_request = pr_util.create_pull_request(
694 commits=commits, target_head='a', source_head='b', revisions=['b'])
694 commits=commits, target_head='a', source_head='b', revisions=['b'])
695 pr_util.create_inline_comment(file_path='file_b')
695 pr_util.create_inline_comment(file_path='file_b')
696
696
697 with outdated_comments_patcher():
697 with outdated_comments_patcher():
698 pr_util.add_one_commit(head='c')
698 pr_util.add_one_commit(head='c')
699 assert_inline_comments(pull_request, visible=0, outdated=1)
699 assert_inline_comments(pull_request, visible=0, outdated=1)
700
700
701
701
702 @pytest.mark.usefixtures('config_stub')
702 @pytest.mark.usefixtures('config_stub')
703 class TestUpdateChangedFiles(object):
703 class TestUpdateChangedFiles(object):
704
704
705 def test_no_changes_on_unchanged_diff(self, pr_util):
705 def test_no_changes_on_unchanged_diff(self, pr_util):
706 commits = [
706 commits = [
707 {'message': 'a'},
707 {'message': 'a'},
708 {'message': 'b',
708 {'message': 'b',
709 'added': [FileNode('file_b', 'test_content b\n')]},
709 'added': [FileNode('file_b', 'test_content b\n')]},
710 {'message': 'c',
710 {'message': 'c',
711 'added': [FileNode('file_c', 'test_content c\n')]},
711 'added': [FileNode('file_c', 'test_content c\n')]},
712 ]
712 ]
713 # open a PR from a to b, adding file_b
713 # open a PR from a to b, adding file_b
714 pull_request = pr_util.create_pull_request(
714 pull_request = pr_util.create_pull_request(
715 commits=commits, target_head='a', source_head='b', revisions=['b'],
715 commits=commits, target_head='a', source_head='b', revisions=['b'],
716 name_suffix='per-file-review')
716 name_suffix='per-file-review')
717
717
718 # modify PR adding new file file_c
718 # modify PR adding new file file_c
719 pr_util.add_one_commit(head='c')
719 pr_util.add_one_commit(head='c')
720
720
721 assert_pr_file_changes(
721 assert_pr_file_changes(
722 pull_request,
722 pull_request,
723 added=['file_c'],
723 added=['file_c'],
724 modified=[],
724 modified=[],
725 removed=[])
725 removed=[])
726
726
727 def test_modify_and_undo_modification_diff(self, pr_util):
727 def test_modify_and_undo_modification_diff(self, pr_util):
728 commits = [
728 commits = [
729 {'message': 'a'},
729 {'message': 'a'},
730 {'message': 'b',
730 {'message': 'b',
731 'added': [FileNode('file_b', 'test_content b\n')]},
731 'added': [FileNode('file_b', 'test_content b\n')]},
732 {'message': 'c',
732 {'message': 'c',
733 'changed': [FileNode('file_b', 'test_content b modified\n')]},
733 'changed': [FileNode('file_b', 'test_content b modified\n')]},
734 {'message': 'd',
734 {'message': 'd',
735 'changed': [FileNode('file_b', 'test_content b\n')]},
735 'changed': [FileNode('file_b', 'test_content b\n')]},
736 ]
736 ]
737 # open a PR from a to b, adding file_b
737 # open a PR from a to b, adding file_b
738 pull_request = pr_util.create_pull_request(
738 pull_request = pr_util.create_pull_request(
739 commits=commits, target_head='a', source_head='b', revisions=['b'],
739 commits=commits, target_head='a', source_head='b', revisions=['b'],
740 name_suffix='per-file-review')
740 name_suffix='per-file-review')
741
741
742 # modify PR modifying file file_b
742 # modify PR modifying file file_b
743 pr_util.add_one_commit(head='c')
743 pr_util.add_one_commit(head='c')
744
744
745 assert_pr_file_changes(
745 assert_pr_file_changes(
746 pull_request,
746 pull_request,
747 added=[],
747 added=[],
748 modified=['file_b'],
748 modified=['file_b'],
749 removed=[])
749 removed=[])
750
750
751 # move the head again to d, which rollbacks change,
751 # move the head again to d, which rollbacks change,
752 # meaning we should indicate no changes
752 # meaning we should indicate no changes
753 pr_util.add_one_commit(head='d')
753 pr_util.add_one_commit(head='d')
754
754
755 assert_pr_file_changes(
755 assert_pr_file_changes(
756 pull_request,
756 pull_request,
757 added=[],
757 added=[],
758 modified=[],
758 modified=[],
759 removed=[])
759 removed=[])
760
760
761 def test_updated_all_files_in_pr(self, pr_util):
761 def test_updated_all_files_in_pr(self, pr_util):
762 commits = [
762 commits = [
763 {'message': 'a'},
763 {'message': 'a'},
764 {'message': 'b', 'added': [
764 {'message': 'b', 'added': [
765 FileNode('file_a', 'test_content a\n'),
765 FileNode('file_a', 'test_content a\n'),
766 FileNode('file_b', 'test_content b\n'),
766 FileNode('file_b', 'test_content b\n'),
767 FileNode('file_c', 'test_content c\n')]},
767 FileNode('file_c', 'test_content c\n')]},
768 {'message': 'c', 'changed': [
768 {'message': 'c', 'changed': [
769 FileNode('file_a', 'test_content a changed\n'),
769 FileNode('file_a', 'test_content a changed\n'),
770 FileNode('file_b', 'test_content b changed\n'),
770 FileNode('file_b', 'test_content b changed\n'),
771 FileNode('file_c', 'test_content c changed\n')]},
771 FileNode('file_c', 'test_content c changed\n')]},
772 ]
772 ]
773 # open a PR from a to b, changing 3 files
773 # open a PR from a to b, changing 3 files
774 pull_request = pr_util.create_pull_request(
774 pull_request = pr_util.create_pull_request(
775 commits=commits, target_head='a', source_head='b', revisions=['b'],
775 commits=commits, target_head='a', source_head='b', revisions=['b'],
776 name_suffix='per-file-review')
776 name_suffix='per-file-review')
777
777
778 pr_util.add_one_commit(head='c')
778 pr_util.add_one_commit(head='c')
779
779
780 assert_pr_file_changes(
780 assert_pr_file_changes(
781 pull_request,
781 pull_request,
782 added=[],
782 added=[],
783 modified=['file_a', 'file_b', 'file_c'],
783 modified=['file_a', 'file_b', 'file_c'],
784 removed=[])
784 removed=[])
785
785
786 def test_updated_and_removed_all_files_in_pr(self, pr_util):
786 def test_updated_and_removed_all_files_in_pr(self, pr_util):
787 commits = [
787 commits = [
788 {'message': 'a'},
788 {'message': 'a'},
789 {'message': 'b', 'added': [
789 {'message': 'b', 'added': [
790 FileNode('file_a', 'test_content a\n'),
790 FileNode('file_a', 'test_content a\n'),
791 FileNode('file_b', 'test_content b\n'),
791 FileNode('file_b', 'test_content b\n'),
792 FileNode('file_c', 'test_content c\n')]},
792 FileNode('file_c', 'test_content c\n')]},
793 {'message': 'c', 'removed': [
793 {'message': 'c', 'removed': [
794 FileNode('file_a', 'test_content a changed\n'),
794 FileNode('file_a', 'test_content a changed\n'),
795 FileNode('file_b', 'test_content b changed\n'),
795 FileNode('file_b', 'test_content b changed\n'),
796 FileNode('file_c', 'test_content c changed\n')]},
796 FileNode('file_c', 'test_content c changed\n')]},
797 ]
797 ]
798 # open a PR from a to b, removing 3 files
798 # open a PR from a to b, removing 3 files
799 pull_request = pr_util.create_pull_request(
799 pull_request = pr_util.create_pull_request(
800 commits=commits, target_head='a', source_head='b', revisions=['b'],
800 commits=commits, target_head='a', source_head='b', revisions=['b'],
801 name_suffix='per-file-review')
801 name_suffix='per-file-review')
802
802
803 pr_util.add_one_commit(head='c')
803 pr_util.add_one_commit(head='c')
804
804
805 assert_pr_file_changes(
805 assert_pr_file_changes(
806 pull_request,
806 pull_request,
807 added=[],
807 added=[],
808 modified=[],
808 modified=[],
809 removed=['file_a', 'file_b', 'file_c'])
809 removed=['file_a', 'file_b', 'file_c'])
810
810
811
811
812 def test_update_writes_snapshot_into_pull_request_version(pr_util, config_stub):
812 def test_update_writes_snapshot_into_pull_request_version(pr_util, config_stub):
813 model = PullRequestModel()
813 model = PullRequestModel()
814 pull_request = pr_util.create_pull_request()
814 pull_request = pr_util.create_pull_request()
815 pr_util.update_source_repository()
815 pr_util.update_source_repository()
816
816
817 model.update_commits(pull_request, pull_request.author)
817 model.update_commits(pull_request, pull_request.author)
818
818
819 # Expect that it has a version entry now
819 # Expect that it has a version entry now
820 assert len(model.get_versions(pull_request)) == 1
820 assert len(model.get_versions(pull_request)) == 1
821
821
822
822
823 def test_update_skips_new_version_if_unchanged(pr_util, config_stub):
823 def test_update_skips_new_version_if_unchanged(pr_util, config_stub):
824 pull_request = pr_util.create_pull_request()
824 pull_request = pr_util.create_pull_request()
825 model = PullRequestModel()
825 model = PullRequestModel()
826 model.update_commits(pull_request, pull_request.author)
826 model.update_commits(pull_request, pull_request.author)
827
827
828 # Expect that it still has no versions
828 # Expect that it still has no versions
829 assert len(model.get_versions(pull_request)) == 0
829 assert len(model.get_versions(pull_request)) == 0
830
830
831
831
832 def test_update_assigns_comments_to_the_new_version(pr_util, config_stub):
832 def test_update_assigns_comments_to_the_new_version(pr_util, config_stub):
833 model = PullRequestModel()
833 model = PullRequestModel()
834 pull_request = pr_util.create_pull_request()
834 pull_request = pr_util.create_pull_request()
835 comment = pr_util.create_comment()
835 comment = pr_util.create_comment()
836 pr_util.update_source_repository()
836 pr_util.update_source_repository()
837
837
838 model.update_commits(pull_request, pull_request.author)
838 model.update_commits(pull_request, pull_request.author)
839
839
840 # Expect that the comment is linked to the pr version now
840 # Expect that the comment is linked to the pr version now
841 assert comment.pull_request_version == model.get_versions(pull_request)[0]
841 assert comment.pull_request_version == model.get_versions(pull_request)[0]
842
842
843
843
844 def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util, config_stub):
844 def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util, config_stub):
845 model = PullRequestModel()
845 model = PullRequestModel()
846 pull_request = pr_util.create_pull_request()
846 pull_request = pr_util.create_pull_request()
847 pr_util.update_source_repository()
847 pr_util.update_source_repository()
848 pr_util.update_source_repository()
848 pr_util.update_source_repository()
849
849
850 update_response = model.update_commits(pull_request, pull_request.author)
850 update_response = model.update_commits(pull_request, pull_request.author)
851
851
852 commit_id = update_response.common_ancestor_id
852 commit_id = update_response.common_ancestor_id
853 # Expect to find a new comment about the change
853 # Expect to find a new comment about the change
854 expected_message = textwrap.dedent(
854 expected_message = textwrap.dedent(
855 """\
855 """\
856 Pull request updated. Auto status change to |under_review|
856 Pull request updated. Auto status change to |under_review|
857
857
858 .. role:: added
858 .. role:: added
859 .. role:: removed
859 .. role:: removed
860 .. parsed-literal::
860 .. parsed-literal::
861
861
862 Changed commits:
862 Changed commits:
863 * :added:`1 added`
863 * :added:`1 added`
864 * :removed:`0 removed`
864 * :removed:`0 removed`
865
865
866 Changed files:
866 Changed files:
867 * `A file_2 <#a_c-{}-92ed3b5f07b4>`_
867 * `A file_2 <#a_c-{}-92ed3b5f07b4>`_
868
868
869 .. |under_review| replace:: *"Under Review"*"""
869 .. |under_review| replace:: *"Under Review"*"""
870 ).format(commit_id[:12])
870 ).format(commit_id[:12])
871 pull_request_comments = sorted(
871 pull_request_comments = sorted(
872 pull_request.comments, key=lambda c: c.modified_at)
872 pull_request.comments, key=lambda c: c.modified_at)
873 update_comment = pull_request_comments[-1]
873 update_comment = pull_request_comments[-1]
874 assert update_comment.text == expected_message
874 assert update_comment.text == expected_message
875
875
876
876
877 def test_create_version_from_snapshot_updates_attributes(pr_util, config_stub):
877 def test_create_version_from_snapshot_updates_attributes(pr_util, config_stub):
878 pull_request = pr_util.create_pull_request()
878 pull_request = pr_util.create_pull_request()
879
879
880 # Avoiding default values
880 # Avoiding default values
881 pull_request.status = PullRequest.STATUS_CLOSED
881 pull_request.status = PullRequest.STATUS_CLOSED
882 pull_request._last_merge_source_rev = "0" * 40
882 pull_request._last_merge_source_rev = "0" * 40
883 pull_request._last_merge_target_rev = "1" * 40
883 pull_request._last_merge_target_rev = "1" * 40
884 pull_request.last_merge_status = 1
884 pull_request.last_merge_status = 1
885 pull_request.merge_rev = "2" * 40
885 pull_request.merge_rev = "2" * 40
886
886
887 # Remember automatic values
887 # Remember automatic values
888 created_on = pull_request.created_on
888 created_on = pull_request.created_on
889 updated_on = pull_request.updated_on
889 updated_on = pull_request.updated_on
890
890
891 # Create a new version of the pull request
891 # Create a new version of the pull request
892 version = PullRequestModel()._create_version_from_snapshot(pull_request)
892 version = PullRequestModel()._create_version_from_snapshot(pull_request)
893
893
894 # Check attributes
894 # Check attributes
895 assert version.title == pr_util.create_parameters['title']
895 assert version.title == pr_util.create_parameters['title']
896 assert version.description == pr_util.create_parameters['description']
896 assert version.description == pr_util.create_parameters['description']
897 assert version.status == PullRequest.STATUS_CLOSED
897 assert version.status == PullRequest.STATUS_CLOSED
898
898
899 # versions get updated created_on
899 # versions get updated created_on
900 assert version.created_on != created_on
900 assert version.created_on != created_on
901
901
902 assert version.updated_on == updated_on
902 assert version.updated_on == updated_on
903 assert version.user_id == pull_request.user_id
903 assert version.user_id == pull_request.user_id
904 assert version.revisions == pr_util.create_parameters['revisions']
904 assert version.revisions == pr_util.create_parameters['revisions']
905 assert version.source_repo == pr_util.source_repository
905 assert version.source_repo == pr_util.source_repository
906 assert version.source_ref == pr_util.create_parameters['source_ref']
906 assert version.source_ref == pr_util.create_parameters['source_ref']
907 assert version.target_repo == pr_util.target_repository
907 assert version.target_repo == pr_util.target_repository
908 assert version.target_ref == pr_util.create_parameters['target_ref']
908 assert version.target_ref == pr_util.create_parameters['target_ref']
909 assert version._last_merge_source_rev == pull_request._last_merge_source_rev
909 assert version._last_merge_source_rev == pull_request._last_merge_source_rev
910 assert version._last_merge_target_rev == pull_request._last_merge_target_rev
910 assert version._last_merge_target_rev == pull_request._last_merge_target_rev
911 assert version.last_merge_status == pull_request.last_merge_status
911 assert version.last_merge_status == pull_request.last_merge_status
912 assert version.merge_rev == pull_request.merge_rev
912 assert version.merge_rev == pull_request.merge_rev
913 assert version.pull_request == pull_request
913 assert version.pull_request == pull_request
914
914
915
915
916 def test_link_comments_to_version_only_updates_unlinked_comments(pr_util, config_stub):
916 def test_link_comments_to_version_only_updates_unlinked_comments(pr_util, config_stub):
917 version1 = pr_util.create_version_of_pull_request()
917 version1 = pr_util.create_version_of_pull_request()
918 comment_linked = pr_util.create_comment(linked_to=version1)
918 comment_linked = pr_util.create_comment(linked_to=version1)
919 comment_unlinked = pr_util.create_comment()
919 comment_unlinked = pr_util.create_comment()
920 version2 = pr_util.create_version_of_pull_request()
920 version2 = pr_util.create_version_of_pull_request()
921
921
922 PullRequestModel()._link_comments_to_version(version2)
922 PullRequestModel()._link_comments_to_version(version2)
923 Session().commit()
923 Session().commit()
924
924
925 # Expect that only the new comment is linked to version2
925 # Expect that only the new comment is linked to version2
926 assert (
926 assert (
927 comment_unlinked.pull_request_version_id ==
927 comment_unlinked.pull_request_version_id ==
928 version2.pull_request_version_id)
928 version2.pull_request_version_id)
929 assert (
929 assert (
930 comment_linked.pull_request_version_id ==
930 comment_linked.pull_request_version_id ==
931 version1.pull_request_version_id)
931 version1.pull_request_version_id)
932 assert (
932 assert (
933 comment_unlinked.pull_request_version_id !=
933 comment_unlinked.pull_request_version_id !=
934 comment_linked.pull_request_version_id)
934 comment_linked.pull_request_version_id)
935
935
936
936
937 def test_calculate_commits():
937 def test_calculate_commits():
938 old_ids = [1, 2, 3]
938 old_ids = [1, 2, 3]
939 new_ids = [1, 3, 4, 5]
939 new_ids = [1, 3, 4, 5]
940 change = PullRequestModel()._calculate_commit_id_changes(old_ids, new_ids)
940 change = PullRequestModel()._calculate_commit_id_changes(old_ids, new_ids)
941 assert change.added == [4, 5]
941 assert change.added == [4, 5]
942 assert change.common == [1, 3]
942 assert change.common == [1, 3]
943 assert change.removed == [2]
943 assert change.removed == [2]
944 assert change.total == [1, 3, 4, 5]
944 assert change.total == [1, 3, 4, 5]
945
945
946
946
947 def assert_inline_comments(pull_request, visible=None, outdated=None):
947 def assert_inline_comments(pull_request, visible=None, outdated=None):
948 if visible is not None:
948 if visible is not None:
949 inline_comments = CommentsModel().get_inline_comments(
949 inline_comments = CommentsModel().get_inline_comments(
950 pull_request.target_repo.repo_id, pull_request=pull_request)
950 pull_request.target_repo.repo_id, pull_request=pull_request)
951 inline_cnt = len(CommentsModel().get_inline_comments_as_list(
951 inline_cnt = len(CommentsModel().get_inline_comments_as_list(
952 inline_comments))
952 inline_comments))
953 assert inline_cnt == visible
953 assert inline_cnt == visible
954 if outdated is not None:
954 if outdated is not None:
955 outdated_comments = CommentsModel().get_outdated_comments(
955 outdated_comments = CommentsModel().get_outdated_comments(
956 pull_request.target_repo.repo_id, pull_request)
956 pull_request.target_repo.repo_id, pull_request)
957 assert len(outdated_comments) == outdated
957 assert len(outdated_comments) == outdated
958
958
959
959
960 def assert_pr_file_changes(
960 def assert_pr_file_changes(
961 pull_request, added=None, modified=None, removed=None):
961 pull_request, added=None, modified=None, removed=None):
962 pr_versions = PullRequestModel().get_versions(pull_request)
962 pr_versions = PullRequestModel().get_versions(pull_request)
963 # always use first version, ie original PR to calculate changes
963 # always use first version, ie original PR to calculate changes
964 pull_request_version = pr_versions[0]
964 pull_request_version = pr_versions[0]
965 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
965 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
966 pull_request, pull_request_version)
966 pull_request, pull_request_version)
967 file_changes = PullRequestModel()._calculate_file_changes(
967 file_changes = PullRequestModel()._calculate_file_changes(
968 old_diff_data, new_diff_data)
968 old_diff_data, new_diff_data)
969
969
970 assert added == file_changes.added, \
970 assert added == file_changes.added, \
971 'expected added:%s vs value:%s' % (added, file_changes.added)
971 'expected added:%s vs value:%s' % (added, file_changes.added)
972 assert modified == file_changes.modified, \
972 assert modified == file_changes.modified, \
973 'expected modified:%s vs value:%s' % (modified, file_changes.modified)
973 'expected modified:%s vs value:%s' % (modified, file_changes.modified)
974 assert removed == file_changes.removed, \
974 assert removed == file_changes.removed, \
975 'expected removed:%s vs value:%s' % (removed, file_changes.removed)
975 'expected removed:%s vs value:%s' % (removed, file_changes.removed)
976
976
977
977
978 def outdated_comments_patcher(use_outdated=True):
978 def outdated_comments_patcher(use_outdated=True):
979 return mock.patch.object(
979 return mock.patch.object(
980 CommentsModel, 'use_outdated_comments',
980 CommentsModel, 'use_outdated_comments',
981 return_value=use_outdated)
981 return_value=use_outdated)
General Comments 0
You need to be logged in to leave comments. Login now