##// END OF EJS Templates
integrations: rewrote usage of pylons components inside integrations....
marcink -
r1990:32399c6a default
parent child Browse files
Show More
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,226 +1,230 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from rhodecode.apps._base import ADMIN_PREFIX, add_route_requirements
24 from rhodecode.lib.utils2 import safe_int
24 25 from rhodecode.model.db import Repository, Integration, RepoGroup
25 26 from rhodecode.integrations import integration_type_registry
26 27
27 28 log = logging.getLogger(__name__)
28 29
29 30
30 31 def includeme(config):
31 32
32 33 # global integrations
33
34 34 config.add_route('global_integrations_new',
35 35 ADMIN_PREFIX + '/integrations/new')
36 36 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
37 37 attr='new_integration',
38 38 renderer='rhodecode:templates/admin/integrations/new.mako',
39 39 request_method='GET',
40 40 route_name='global_integrations_new')
41 41
42 42 config.add_route('global_integrations_home',
43 43 ADMIN_PREFIX + '/integrations')
44 44 config.add_route('global_integrations_list',
45 45 ADMIN_PREFIX + '/integrations/{integration}')
46 46 for route_name in ['global_integrations_home', 'global_integrations_list']:
47 47 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
48 attr='index',
48 attr='integration_list',
49 49 renderer='rhodecode:templates/admin/integrations/list.mako',
50 50 request_method='GET',
51 51 route_name=route_name)
52 52
53 53 config.add_route('global_integrations_create',
54 54 ADMIN_PREFIX + '/integrations/{integration}/new',
55 55 custom_predicates=(valid_integration,))
56 56 config.add_route('global_integrations_edit',
57 57 ADMIN_PREFIX + '/integrations/{integration}/{integration_id}',
58 58 custom_predicates=(valid_integration,))
59 59
60
61 60 for route_name in ['global_integrations_create', 'global_integrations_edit']:
62 61 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
63 62 attr='settings_get',
64 63 renderer='rhodecode:templates/admin/integrations/form.mako',
65 64 request_method='GET',
66 65 route_name=route_name)
67 66 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
68 67 attr='settings_post',
69 68 renderer='rhodecode:templates/admin/integrations/form.mako',
70 69 request_method='POST',
71 70 route_name=route_name)
72 71
73
74 72 # repo group integrations
75 73 config.add_route('repo_group_integrations_home',
76 add_route_requirements(
77 '{repo_group_name}/settings/integrations',
78 ),
79 custom_predicates=(valid_repo_group,)
80 )
81 config.add_route('repo_group_integrations_list',
82 add_route_requirements(
83 '{repo_group_name}/settings/integrations/{integration}',
84 ),
85 custom_predicates=(valid_repo_group, valid_integration))
86 for route_name in ['repo_group_integrations_home', 'repo_group_integrations_list']:
74 add_route_requirements('/{repo_group_name}/settings/integrations'),
75 repo_group_route=True)
76
87 77 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
88 attr='index',
78 attr='integration_list',
89 79 renderer='rhodecode:templates/admin/integrations/list.mako',
90 80 request_method='GET',
91 route_name=route_name)
81 route_name='repo_group_integrations_home')
92 82
93 83 config.add_route('repo_group_integrations_new',
94 add_route_requirements(
95 '{repo_group_name}/settings/integrations/new',
96 ),
97 custom_predicates=(valid_repo_group,))
84 add_route_requirements('/{repo_group_name}/settings/integrations/new'),
85 repo_group_route=True)
98 86 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
99 87 attr='new_integration',
100 88 renderer='rhodecode:templates/admin/integrations/new.mako',
101 89 request_method='GET',
102 90 route_name='repo_group_integrations_new')
103 91
92 config.add_route('repo_group_integrations_list',
93 add_route_requirements('/{repo_group_name}/settings/integrations/{integration}'),
94 repo_group_route=True,
95 custom_predicates=(valid_integration,))
96 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
97 attr='integration_list',
98 renderer='rhodecode:templates/admin/integrations/list.mako',
99 request_method='GET',
100 route_name='repo_group_integrations_list')
101
104 102 config.add_route('repo_group_integrations_create',
105 add_route_requirements(
106 '{repo_group_name}/settings/integrations/{integration}/new',
107 ),
108 custom_predicates=(valid_repo_group, valid_integration))
109 config.add_route('repo_group_integrations_edit',
110 add_route_requirements(
111 '{repo_group_name}/settings/integrations/{integration}/{integration_id}',
112 ),
113 custom_predicates=(valid_repo_group, valid_integration))
114 for route_name in ['repo_group_integrations_edit', 'repo_group_integrations_create']:
103 add_route_requirements('/{repo_group_name}/settings/integrations/{integration}/new'),
104 repo_group_route=True,
105 custom_predicates=(valid_integration,))
115 106 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
116 107 attr='settings_get',
117 108 renderer='rhodecode:templates/admin/integrations/form.mako',
118 109 request_method='GET',
119 route_name=route_name)
110 route_name='repo_group_integrations_create')
120 111 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
121 112 attr='settings_post',
122 113 renderer='rhodecode:templates/admin/integrations/form.mako',
123 114 request_method='POST',
124 route_name=route_name)
115 route_name='repo_group_integrations_create')
116
117 config.add_route('repo_group_integrations_edit',
118 add_route_requirements('/{repo_group_name}/settings/integrations/{integration}/{integration_id}'),
119 repo_group_route=True,
120 custom_predicates=(valid_integration,))
125 121
122 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
123 attr='settings_get',
124 renderer='rhodecode:templates/admin/integrations/form.mako',
125 request_method='GET',
126 route_name='repo_group_integrations_edit')
127 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
128 attr='settings_post',
129 renderer='rhodecode:templates/admin/integrations/form.mako',
130 request_method='POST',
131 route_name='repo_group_integrations_edit')
126 132
127 133 # repo integrations
128 134 config.add_route('repo_integrations_home',
129 add_route_requirements(
130 '{repo_name}/settings/integrations',
131 ),
132 custom_predicates=(valid_repo,))
133 config.add_route('repo_integrations_list',
134 add_route_requirements(
135 '{repo_name}/settings/integrations/{integration}',
136 ),
137 custom_predicates=(valid_repo, valid_integration))
138 for route_name in ['repo_integrations_home', 'repo_integrations_list']:
135 add_route_requirements('/{repo_name}/settings/integrations'),
136 repo_route=True)
139 137 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
140 attr='index',
138 attr='integration_list',
141 139 request_method='GET',
142 140 renderer='rhodecode:templates/admin/integrations/list.mako',
143 route_name=route_name)
141 route_name='repo_integrations_home')
144 142
145 143 config.add_route('repo_integrations_new',
146 add_route_requirements(
147 '{repo_name}/settings/integrations/new',
148 ),
149 custom_predicates=(valid_repo,))
144 add_route_requirements('/{repo_name}/settings/integrations/new'),
145 repo_route=True)
150 146 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
151 147 attr='new_integration',
152 148 renderer='rhodecode:templates/admin/integrations/new.mako',
153 149 request_method='GET',
154 150 route_name='repo_integrations_new')
155 151
152 config.add_route('repo_integrations_list',
153 add_route_requirements('/{repo_name}/settings/integrations/{integration}'),
154 repo_route=True,
155 custom_predicates=(valid_integration,))
156 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
157 attr='integration_list',
158 request_method='GET',
159 renderer='rhodecode:templates/admin/integrations/list.mako',
160 route_name='repo_integrations_list')
161
156 162 config.add_route('repo_integrations_create',
157 add_route_requirements(
158 '{repo_name}/settings/integrations/{integration}/new',
159 ),
160 custom_predicates=(valid_repo, valid_integration))
161 config.add_route('repo_integrations_edit',
162 add_route_requirements(
163 '{repo_name}/settings/integrations/{integration}/{integration_id}',
164 ),
165 custom_predicates=(valid_repo, valid_integration))
166 for route_name in ['repo_integrations_edit', 'repo_integrations_create']:
163 add_route_requirements('/{repo_name}/settings/integrations/{integration}/new'),
164 repo_route=True,
165 custom_predicates=(valid_integration,))
167 166 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
168 167 attr='settings_get',
169 168 renderer='rhodecode:templates/admin/integrations/form.mako',
170 169 request_method='GET',
171 route_name=route_name)
170 route_name='repo_integrations_create')
172 171 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
173 172 attr='settings_post',
174 173 renderer='rhodecode:templates/admin/integrations/form.mako',
175 174 request_method='POST',
176 route_name=route_name)
177
175 route_name='repo_integrations_create')
178 176
179 def valid_repo(info, request):
180 repo = Repository.get_by_repo_name(info['match']['repo_name'])
181 if repo:
182 return True
183
177 config.add_route('repo_integrations_edit',
178 add_route_requirements('/{repo_name}/settings/integrations/{integration}/{integration_id}'),
179 repo_route=True,
180 custom_predicates=(valid_integration,))
181 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
182 attr='settings_get',
183 renderer='rhodecode:templates/admin/integrations/form.mako',
184 request_method='GET',
185 route_name='repo_integrations_edit')
186 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
187 attr='settings_post',
188 renderer='rhodecode:templates/admin/integrations/form.mako',
189 request_method='POST',
190 route_name='repo_integrations_edit')
184 191
185 def valid_repo_group(info, request):
186 repo_group = RepoGroup.get_by_group_name(info['match']['repo_group_name'])
187 if repo_group:
188 return True
189 return False
190 192
191 193
192 194 def valid_integration(info, request):
193 195 integration_type = info['match']['integration']
194 196 integration_id = info['match'].get('integration_id')
195 repo_name = info['match'].get('repo_name')
196 repo_group_name = info['match'].get('repo_group_name')
197 197
198 198 if integration_type not in integration_type_registry:
199 199 return False
200 200
201 if integration_id:
202 if not safe_int(integration_id):
203 return False
204
205 integration = Integration.get(integration_id)
206 if not integration:
207 return False
208 if integration.integration_type != integration_type:
209 return False
210
211 # match types to repo or repo group
212 repo_name = info['match'].get('repo_name')
213 repo_group_name = info['match'].get('repo_group_name')
201 214 repo, repo_group = None, None
202 215 if repo_name:
203 216 repo = Repository.get_by_repo_name(repo_name)
204 217 if not repo:
205 218 return False
206 219
207 220 if repo_group_name:
208 221 repo_group = RepoGroup.get_by_group_name(repo_group_name)
209 222 if not repo_group:
210 223 return False
211 224
212 if repo_name and repo_group:
213 raise Exception('Either repo or repo_group can be set, not both')
214
215 if integration_id:
216 integration = Integration.get(integration_id)
217 if not integration:
218 return False
219 if integration.integration_type != integration_type:
220 return False
221 225 if repo and repo.repo_id != integration.repo_id:
222 226 return False
223 227 if repo_group and repo_group.group_id != integration.repo_group_id:
224 228 return False
225 229
226 230 return True
@@ -1,259 +1,264 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 from rhodecode.apps._base import ADMIN_PREFIX
23 24 from rhodecode.model.db import Integration
24 25 from rhodecode.model.meta import Session
25 26 from rhodecode.integrations import integration_type_registry
26 from rhodecode.config.routing import ADMIN_PREFIX
27
28
29 def route_path(name, **kwargs):
30 return {
31 'home': '/',
32 }[name].format(**kwargs)
27 33
28 34
29 35 @pytest.mark.usefixtures('app', 'autologin_user')
30 36 class TestIntegrationsView(object):
31 37 pass
32 38
33 39
34 40 class TestGlobalIntegrationsView(TestIntegrationsView):
35 def test_index_no_integrations(self, app):
41 def test_index_no_integrations(self):
36 42 url = ADMIN_PREFIX + '/integrations'
37 response = app.get(url)
43 response = self.app.get(url)
38 44
39 45 assert response.status_code == 200
40 46 assert 'exist yet' in response.body
41 47
42 def test_index_with_integrations(self, app, global_integration_stub):
48 def test_index_with_integrations(self, global_integration_stub):
43 49 url = ADMIN_PREFIX + '/integrations'
44 response = app.get(url)
50 response = self.app.get(url)
45 51
46 52 assert response.status_code == 200
47 53 assert 'exist yet' not in response.body
48 54 assert global_integration_stub.name in response.body
49 55
50 def test_new_integration_page(self, app):
56 @pytest.mark.parametrize(
57 'IntegrationType', integration_type_registry.values())
58 def test_new_integration_page(self, IntegrationType):
51 59 url = ADMIN_PREFIX + '/integrations/new'
52 60
53 response = app.get(url)
54
55 assert response.status_code == 200
61 response = self.app.get(url, status=200)
56 62
57 for integration_key in integration_type_registry:
58 nurl = (ADMIN_PREFIX + '/integrations/{integration}/new').format(
59 integration=integration_key)
60 assert nurl in response.body
63 url = (ADMIN_PREFIX + '/integrations/{integration}/new').format(
64 integration=IntegrationType.key)
65 assert url in response.body
61 66
62 67 @pytest.mark.parametrize(
63 68 'IntegrationType', integration_type_registry.values())
64 def test_get_create_integration_page(self, app, IntegrationType):
69 def test_get_create_integration_page(self, IntegrationType):
65 70 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
66 71 integration_key=IntegrationType.key)
67 72
68 response = app.get(url)
73 response = self.app.get(url, status=200)
69 74
70 assert response.status_code == 200
71 75 assert IntegrationType.display_name in response.body
72 76
73 def test_post_integration_page(self, app, StubIntegrationType, csrf_token,
77 def test_post_integration_page(self, StubIntegrationType, csrf_token,
74 78 test_repo_group, backend_random):
75 79 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
76 80 integration_key=StubIntegrationType.key)
77 81
78 _post_integration_test_helper(app, url, csrf_token, admin_view=True,
82 _post_integration_test_helper(self.app, url, csrf_token, admin_view=True,
83 repo=backend_random.repo, repo_group=test_repo_group)
84
85
86 class TestRepoIntegrationsView(TestIntegrationsView):
87 def test_index_no_integrations(self, backend_random):
88 url = '/{repo_name}/settings/integrations'.format(
89 repo_name=backend_random.repo.repo_name)
90 response = self.app.get(url)
91
92 assert response.status_code == 200
93 assert 'exist yet' in response.body
94
95 def test_index_with_integrations(self, repo_integration_stub):
96 url = '/{repo_name}/settings/integrations'.format(
97 repo_name=repo_integration_stub.repo.repo_name)
98 stub_name = repo_integration_stub.name
99
100 response = self.app.get(url)
101
102 assert response.status_code == 200
103 assert stub_name in response.body
104 assert 'exist yet' not in response.body
105
106 @pytest.mark.parametrize(
107 'IntegrationType', integration_type_registry.values())
108 def test_new_integration_page(self, backend_random, IntegrationType):
109 repo_name = backend_random.repo.repo_name
110 url = '/{repo_name}/settings/integrations/new'.format(
111 repo_name=repo_name)
112
113 response = self.app.get(url, status=200)
114
115 url = '/{repo_name}/settings/integrations/{integration}/new'.format(
116 repo_name=repo_name,
117 integration=IntegrationType.key)
118
119 assert url in response.body
120
121 @pytest.mark.parametrize(
122 'IntegrationType', integration_type_registry.values())
123 def test_get_create_integration_page(self, backend_random, IntegrationType):
124 repo_name = backend_random.repo.repo_name
125 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
126 repo_name=repo_name, integration_key=IntegrationType.key)
127
128 response = self.app.get(url, status=200)
129
130 assert IntegrationType.display_name in response.body
131
132 def test_post_integration_page(self, backend_random, test_repo_group,
133 StubIntegrationType, csrf_token):
134 repo_name = backend_random.repo.repo_name
135 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
136 repo_name=repo_name, integration_key=StubIntegrationType.key)
137
138 _post_integration_test_helper(
139 self.app, url, csrf_token, admin_view=False,
79 140 repo=backend_random.repo, repo_group=test_repo_group)
80 141
81 142
82 143 class TestRepoGroupIntegrationsView(TestIntegrationsView):
83 def test_index_no_integrations(self, app, test_repo_group):
144 def test_index_no_integrations(self, test_repo_group):
84 145 url = '/{repo_group_name}/settings/integrations'.format(
85 146 repo_group_name=test_repo_group.group_name)
86 response = app.get(url)
147 response = self.app.get(url)
87 148
88 149 assert response.status_code == 200
89 150 assert 'exist yet' in response.body
90 151
91 def test_index_with_integrations(self, app, test_repo_group,
92 repogroup_integration_stub):
152 def test_index_with_integrations(
153 self, test_repo_group, repogroup_integration_stub):
154
93 155 url = '/{repo_group_name}/settings/integrations'.format(
94 156 repo_group_name=test_repo_group.group_name)
95 157
96 158 stub_name = repogroup_integration_stub.name
97 response = app.get(url)
159 response = self.app.get(url)
98 160
99 161 assert response.status_code == 200
100 162 assert 'exist yet' not in response.body
101 163 assert stub_name in response.body
102 164
103 def test_new_integration_page(self, app, test_repo_group):
165 def test_new_integration_page(self, test_repo_group):
104 166 repo_group_name = test_repo_group.group_name
105 167 url = '/{repo_group_name}/settings/integrations/new'.format(
106 168 repo_group_name=test_repo_group.group_name)
107 169
108 response = app.get(url)
170 response = self.app.get(url)
109 171
110 172 assert response.status_code == 200
111 173
112 174 for integration_key in integration_type_registry:
113 nurl = ('/{repo_group_name}/settings/integrations'
114 '/{integration}/new').format(
175 nurl = ('/{repo_group_name}/settings/integrations/{integration}/new').format(
115 176 repo_group_name=repo_group_name,
116 177 integration=integration_key)
117 178
118 179 assert nurl in response.body
119 180
120 181 @pytest.mark.parametrize(
121 182 'IntegrationType', integration_type_registry.values())
122 def test_get_create_integration_page(self, app, test_repo_group,
123 IntegrationType):
183 def test_get_create_integration_page(
184 self, test_repo_group, IntegrationType):
185
124 186 repo_group_name = test_repo_group.group_name
125 187 url = ('/{repo_group_name}/settings/integrations/{integration_key}/new'
126 188 ).format(repo_group_name=repo_group_name,
127 189 integration_key=IntegrationType.key)
128 190
129 response = app.get(url)
191 response = self.app.get(url)
130 192
131 193 assert response.status_code == 200
132 194 assert IntegrationType.display_name in response.body
133 195
134 def test_post_integration_page(self, app, test_repo_group, backend_random,
196 def test_post_integration_page(self, test_repo_group, backend_random,
135 197 StubIntegrationType, csrf_token):
198
136 199 repo_group_name = test_repo_group.group_name
137 200 url = ('/{repo_group_name}/settings/integrations/{integration_key}/new'
138 201 ).format(repo_group_name=repo_group_name,
139 202 integration_key=StubIntegrationType.key)
140 203
141 _post_integration_test_helper(app, url, csrf_token, admin_view=False,
142 repo=backend_random.repo, repo_group=test_repo_group)
143
144
145 class TestRepoIntegrationsView(TestIntegrationsView):
146 def test_index_no_integrations(self, app, backend_random):
147 url = '/{repo_name}/settings/integrations'.format(
148 repo_name=backend_random.repo.repo_name)
149 response = app.get(url)
150
151 assert response.status_code == 200
152 assert 'exist yet' in response.body
153
154 def test_index_with_integrations(self, app, repo_integration_stub):
155 url = '/{repo_name}/settings/integrations'.format(
156 repo_name=repo_integration_stub.repo.repo_name)
157 stub_name = repo_integration_stub.name
158
159 response = app.get(url)
160
161 assert response.status_code == 200
162 assert stub_name in response.body
163 assert 'exist yet' not in response.body
164
165 def test_new_integration_page(self, app, backend_random):
166 repo_name = backend_random.repo.repo_name
167 url = '/{repo_name}/settings/integrations/new'.format(
168 repo_name=repo_name)
169
170 response = app.get(url)
171
172 assert response.status_code == 200
173
174 for integration_key in integration_type_registry:
175 nurl = ('/{repo_name}/settings/integrations'
176 '/{integration}/new').format(
177 repo_name=repo_name,
178 integration=integration_key)
179
180 assert nurl in response.body
181
182 @pytest.mark.parametrize(
183 'IntegrationType', integration_type_registry.values())
184 def test_get_create_integration_page(self, app, backend_random,
185 IntegrationType):
186 repo_name = backend_random.repo.repo_name
187 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
188 repo_name=repo_name, integration_key=IntegrationType.key)
189
190 response = app.get(url)
191
192 assert response.status_code == 200
193 assert IntegrationType.display_name in response.body
194
195 def test_post_integration_page(self, app, backend_random, test_repo_group,
196 StubIntegrationType, csrf_token):
197 repo_name = backend_random.repo.repo_name
198 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
199 repo_name=repo_name, integration_key=StubIntegrationType.key)
200
201 _post_integration_test_helper(app, url, csrf_token, admin_view=False,
204 _post_integration_test_helper(
205 self.app, url, csrf_token, admin_view=False,
202 206 repo=backend_random.repo, repo_group=test_repo_group)
203 207
204 208
205 209 def _post_integration_test_helper(app, url, csrf_token, repo, repo_group,
206 210 admin_view):
207 211 """
208 212 Posts form data to create integration at the url given then deletes it and
209 213 checks if the redirect url is correct.
210 214 """
211
215 repo_name = repo.repo_name
216 repo_group_name = repo_group.group_name
212 217 app.post(url, params={}, status=403) # missing csrf check
213 218 response = app.post(url, params={'csrf_token': csrf_token})
214 219 assert response.status_code == 200
215 220 assert 'Errors exist' in response.body
216 221
217 222 scopes_destinations = [
218 223 ('global',
219 224 ADMIN_PREFIX + '/integrations'),
220 225 ('root-repos',
221 226 ADMIN_PREFIX + '/integrations'),
222 ('repo:%s' % repo.repo_name,
223 '/%s/settings/integrations' % repo.repo_name),
224 ('repogroup:%s' % repo_group.group_name,
225 '/%s/settings/integrations' % repo_group.group_name),
226 ('repogroup-recursive:%s' % repo_group.group_name,
227 '/%s/settings/integrations' % repo_group.group_name),
227 ('repo:%s' % repo_name,
228 '/%s/settings/integrations' % repo_name),
229 ('repogroup:%s' % repo_group_name,
230 '/%s/settings/integrations' % repo_group_name),
231 ('repogroup-recursive:%s' % repo_group_name,
232 '/%s/settings/integrations' % repo_group_name),
228 233 ]
229 234
230 235 for scope, destination in scopes_destinations:
231 236 if admin_view:
232 237 destination = ADMIN_PREFIX + '/integrations'
233 238
234 239 form_data = [
235 240 ('csrf_token', csrf_token),
236 241 ('__start__', 'options:mapping'),
237 242 ('name', 'test integration'),
238 243 ('scope', scope),
239 244 ('enabled', 'true'),
240 245 ('__end__', 'options:mapping'),
241 246 ('__start__', 'settings:mapping'),
242 247 ('test_int_field', '34'),
243 248 ('test_string_field', ''), # empty value on purpose as it's required
244 249 ('__end__', 'settings:mapping'),
245 250 ]
246 251 errors_response = app.post(url, form_data)
247 252 assert 'Errors exist' in errors_response.body
248 253
249 254 form_data[-2] = ('test_string_field', 'data!')
250 255 assert Session().query(Integration).count() == 0
251 256 created_response = app.post(url, form_data)
252 257 assert Session().query(Integration).count() == 1
253 258
254 259 delete_response = app.post(
255 260 created_response.location,
256 261 params={'csrf_token': csrf_token, 'delete': 'delete'})
257 262
258 263 assert Session().query(Integration).count() == 0
259 264 assert delete_response.location.endswith(destination)
@@ -1,390 +1,455 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import pylons
22 21 import deform
23 22 import logging
24 import colander
25 23 import peppercorn
26 24 import webhelpers.paginate
27 25
28 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
29 from pyramid.renderers import render
30 from pyramid.response import Response
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
31 27
28 from rhodecode.apps._base import BaseAppView
29 from rhodecode.integrations import integration_type_registry
32 30 from rhodecode.apps.admin.navigation import navigation_list
33 from rhodecode.lib import auth
34 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
31 from rhodecode.lib.auth import (
32 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
33 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
35 34 from rhodecode.lib.utils2 import safe_int
36 35 from rhodecode.lib.helpers import Page
37 36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
38 37 from rhodecode.model.scm import ScmModel
39 38 from rhodecode.model.integration import IntegrationModel
40 from rhodecode.translation import _
41 from rhodecode.integrations import integration_type_registry
42 39 from rhodecode.model.validation_schema.schemas.integration_schema import (
43 40 make_integration_schema, IntegrationScopeType)
44 41
45 42 log = logging.getLogger(__name__)
46 43
47 44
48 class IntegrationSettingsViewBase(object):
49 """ Base Integration settings view used by both repo / global settings """
45 class IntegrationSettingsViewBase(BaseAppView):
46 """
47 Base Integration settings view used by both repo / global settings
48 """
50 49
51 50 def __init__(self, context, request):
52 self.context = context
53 self.request = request
54 self._load_general_context()
51 super(IntegrationSettingsViewBase, self).__init__(context, request)
52 self._load_view_context()
55 53
56 if not self.perm_check(request.user):
57 raise HTTPForbidden()
58
59 def _load_general_context(self):
54 def _load_view_context(self):
60 55 """
61 56 This avoids boilerplate for repo/global+list/edit+views/templates
62 57 by doing all possible contexts at the same time however it should
63 58 be split up into separate functions once more "contexts" exist
64 59 """
65 60
66 61 self.IntegrationType = None
67 62 self.repo = None
68 63 self.repo_group = None
69 64 self.integration = None
70 65 self.integrations = {}
71 66
72 67 request = self.request
73 68
74 69 if 'repo_name' in request.matchdict: # in repo settings context
75 70 repo_name = request.matchdict['repo_name']
76 71 self.repo = Repository.get_by_repo_name(repo_name)
77 72
78 73 if 'repo_group_name' in request.matchdict: # in group settings context
79 74 repo_group_name = request.matchdict['repo_group_name']
80 75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
81 76
82 77 if 'integration' in request.matchdict: # integration type context
83 78 integration_type = request.matchdict['integration']
84 79 self.IntegrationType = integration_type_registry[integration_type]
85 80
86 81 if 'integration_id' in request.matchdict: # single integration context
87 82 integration_id = request.matchdict['integration_id']
88 83 self.integration = Integration.get(integration_id)
89 84
90 85 # extra perms check just in case
91 86 if not self._has_perms_for_integration(self.integration):
92 87 raise HTTPForbidden()
93 88
94 89 self.settings = self.integration and self.integration.settings or {}
95 90 self.admin_view = not (self.repo or self.repo_group)
96 91
97 92 def _has_perms_for_integration(self, integration):
98 93 perms = self.request.user.permissions
99 94
100 95 if 'hg.admin' in perms['global']:
101 96 return True
102 97
103 98 if integration.repo:
104 99 return perms['repositories'].get(
105 100 integration.repo.repo_name) == 'repository.admin'
106 101
107 102 if integration.repo_group:
108 103 return perms['repositories_groups'].get(
109 104 integration.repo_group.group_name) == 'group.admin'
110 105
111 106 return False
112 107
113 def _template_c_context(self):
114 # TODO: dan: this is a stopgap in order to inherit from current pylons
115 # based admin/repo settings templates - this should be removed entirely
116 # after port to pyramid
108 def _get_local_tmpl_context(self, include_app_defaults=False):
109 _ = self.request.translate
110 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
111 include_app_defaults=include_app_defaults)
117 112
118 c = pylons.tmpl_context
119 113 c.active = 'integrations'
120 c.rhodecode_user = self.request.user
121 c.repo = self.repo
122 c.repo_group = self.repo_group
123 c.repo_name = self.repo and self.repo.repo_name or None
124 c.repo_group_name = self.repo_group and self.repo_group.group_name or None
125
126 if self.repo:
127 c.repo_info = self.repo
128 c.rhodecode_db_repo = self.repo
129 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
130 else:
131 c.navlist = navigation_list(self.request)
132 114
133 115 return c
134 116
135 117 def _form_schema(self):
136 118 schema = make_integration_schema(IntegrationType=self.IntegrationType,
137 119 settings=self.settings)
138 120
139 121 # returns a clone, important if mutating the schema later
140 122 return schema.bind(
141 123 permissions=self.request.user.permissions,
142 124 no_scope=not self.admin_view)
143 125
144 126 def _form_defaults(self):
127 _ = self.request.translate
145 128 defaults = {}
146 129
147 130 if self.integration:
148 131 defaults['settings'] = self.integration.settings or {}
149 132 defaults['options'] = {
150 133 'name': self.integration.name,
151 134 'enabled': self.integration.enabled,
152 135 'scope': {
153 136 'repo': self.integration.repo,
154 137 'repo_group': self.integration.repo_group,
155 138 'child_repos_only': self.integration.child_repos_only,
156 139 },
157 140 }
158 141 else:
159 142 if self.repo:
160 143 scope = _('{repo_name} repository').format(
161 144 repo_name=self.repo.repo_name)
162 145 elif self.repo_group:
163 146 scope = _('{repo_group_name} repo group').format(
164 147 repo_group_name=self.repo_group.group_name)
165 148 else:
166 149 scope = _('Global')
167 150
168 151 defaults['options'] = {
169 152 'enabled': True,
170 153 'name': _('{name} integration').format(
171 154 name=self.IntegrationType.display_name),
172 155 }
173 156 defaults['options']['scope'] = {
174 157 'repo': self.repo,
175 158 'repo_group': self.repo_group,
176 159 }
177 160
178 161 return defaults
179 162
180 163 def _delete_integration(self, integration):
181 Session().delete(self.integration)
164 _ = self.request.translate
165 Session().delete(integration)
182 166 Session().commit()
183 167 self.request.session.flash(
184 168 _('Integration {integration_name} deleted successfully.').format(
185 integration_name=self.integration.name),
169 integration_name=integration.name),
186 170 queue='success')
187 171
188 172 if self.repo:
189 redirect_to = self.request.route_url(
173 redirect_to = self.request.route_path(
190 174 'repo_integrations_home', repo_name=self.repo.repo_name)
191 175 elif self.repo_group:
192 redirect_to = self.request.route_url(
176 redirect_to = self.request.route_path(
193 177 'repo_group_integrations_home',
194 178 repo_group_name=self.repo_group.group_name)
195 179 else:
196 redirect_to = self.request.route_url('global_integrations_home')
180 redirect_to = self.request.route_path('global_integrations_home')
197 181 raise HTTPFound(redirect_to)
198 182
199 def settings_get(self, defaults=None, form=None):
183 def _integration_list(self):
184 """ List integrations """
185
186 c = self.load_default_context()
187 if self.repo:
188 scope = self.repo
189 elif self.repo_group:
190 scope = self.repo_group
191 else:
192 scope = 'all'
193
194 integrations = []
195
196 for IntType, integration in IntegrationModel().get_integrations(
197 scope=scope, IntegrationType=self.IntegrationType):
198
199 # extra permissions check *just in case*
200 if not self._has_perms_for_integration(integration):
201 continue
202
203 integrations.append((IntType, integration))
204
205 sort_arg = self.request.GET.get('sort', 'name:asc')
206 if ':' in sort_arg:
207 sort_field, sort_dir = sort_arg.split(':')
208 else:
209 sort_field = sort_arg, 'asc'
210
211 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
212
213 integrations.sort(
214 key=lambda x: getattr(x[1], sort_field),
215 reverse=(sort_dir == 'desc'))
216
217 page_url = webhelpers.paginate.PageURL(
218 self.request.path, self.request.GET)
219 page = safe_int(self.request.GET.get('page', 1), 1)
220
221 integrations = Page(
222 integrations, page=page, items_per_page=10, url=page_url)
223
224 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
225
226 c.current_IntegrationType = self.IntegrationType
227 c.integrations_list = integrations
228 c.available_integrations = integration_type_registry
229
230 return self._get_template_context(c)
231
232 def _settings_get(self, defaults=None, form=None):
200 233 """
201 234 View that displays the integration settings as a form.
202 235 """
236 c = self.load_default_context()
203 237
204 238 defaults = defaults or self._form_defaults()
205 239 schema = self._form_schema()
206 240
207 241 if self.integration:
208 242 buttons = ('submit', 'delete')
209 243 else:
210 244 buttons = ('submit',)
211 245
212 246 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
213 247
214 template_context = {
215 'form': form,
216 'current_IntegrationType': self.IntegrationType,
217 'integration': self.integration,
218 'c': self._template_c_context(),
219 }
248 c.form = form
249 c.current_IntegrationType = self.IntegrationType
250 c.integration = self.integration
220 251
221 return template_context
252 return self._get_template_context(c)
222 253
223 @auth.CSRFRequired()
224 def settings_post(self):
254 def _settings_post(self):
225 255 """
226 256 View that validates and stores the integration settings.
227 257 """
258 _ = self.request.translate
259
228 260 controls = self.request.POST.items()
229 261 pstruct = peppercorn.parse(controls)
230 262
231 263 if self.integration and pstruct.get('delete'):
232 264 return self._delete_integration(self.integration)
233 265
234 266 schema = self._form_schema()
235 267
236 268 skip_settings_validation = False
237 269 if self.integration and 'enabled' not in pstruct.get('options', {}):
238 270 skip_settings_validation = True
239 271 schema['settings'].validator = None
240 272 for field in schema['settings'].children:
241 273 field.validator = None
242 274 field.missing = ''
243 275
244 276 if self.integration:
245 277 buttons = ('submit', 'delete')
246 278 else:
247 279 buttons = ('submit',)
248 280
249 281 form = deform.Form(schema, buttons=buttons)
250 282
251 283 if not self.admin_view:
252 284 # scope is read only field in these cases, and has to be added
253 285 options = pstruct.setdefault('options', {})
254 286 if 'scope' not in options:
255 287 options['scope'] = IntegrationScopeType().serialize(None, {
256 288 'repo': self.repo,
257 289 'repo_group': self.repo_group,
258 290 })
259 291
260 292 try:
261 293 valid_data = form.validate_pstruct(pstruct)
262 294 except deform.ValidationFailure as e:
263 295 self.request.session.flash(
264 296 _('Errors exist when saving integration settings. '
265 297 'Please check the form inputs.'),
266 298 queue='error')
267 return self.settings_get(form=e)
299 return self._settings_get(form=e)
268 300
269 301 if not self.integration:
270 302 self.integration = Integration()
271 303 self.integration.integration_type = self.IntegrationType.key
272 304 Session().add(self.integration)
273 305
274 306 scope = valid_data['options']['scope']
275 307
276 308 IntegrationModel().update_integration(self.integration,
277 309 name=valid_data['options']['name'],
278 310 enabled=valid_data['options']['enabled'],
279 311 settings=valid_data['settings'],
280 312 repo=scope['repo'],
281 313 repo_group=scope['repo_group'],
282 314 child_repos_only=scope['child_repos_only'],
283 315 )
284 316
285 317 self.integration.settings = valid_data['settings']
286 318 Session().commit()
287 319 # Display success message and redirect.
288 320 self.request.session.flash(
289 321 _('Integration {integration_name} updated successfully.').format(
290 322 integration_name=self.IntegrationType.display_name),
291 323 queue='success')
292 324
293 325 # if integration scope changes, we must redirect to the right place
294 326 # keeping in mind if the original view was for /repo/ or /_admin/
295 327 admin_view = not (self.repo or self.repo_group)
296 328
297 329 if self.integration.repo and not admin_view:
298 330 redirect_to = self.request.route_path(
299 331 'repo_integrations_edit',
300 332 repo_name=self.integration.repo.repo_name,
301 333 integration=self.integration.integration_type,
302 334 integration_id=self.integration.integration_id)
303 335 elif self.integration.repo_group and not admin_view:
304 336 redirect_to = self.request.route_path(
305 337 'repo_group_integrations_edit',
306 338 repo_group_name=self.integration.repo_group.group_name,
307 339 integration=self.integration.integration_type,
308 340 integration_id=self.integration.integration_id)
309 341 else:
310 342 redirect_to = self.request.route_path(
311 343 'global_integrations_edit',
312 344 integration=self.integration.integration_type,
313 345 integration_id=self.integration.integration_id)
314 346
315 347 return HTTPFound(redirect_to)
316 348
317 def index(self):
318 """ List integrations """
319 if self.repo:
320 scope = self.repo
321 elif self.repo_group:
322 scope = self.repo_group
323 else:
324 scope = 'all'
325
326 integrations = []
327
328 for IntType, integration in IntegrationModel().get_integrations(
329 scope=scope, IntegrationType=self.IntegrationType):
330
331 # extra permissions check *just in case*
332 if not self._has_perms_for_integration(integration):
333 continue
334
335 integrations.append((IntType, integration))
336
337 sort_arg = self.request.GET.get('sort', 'name:asc')
338 if ':' in sort_arg:
339 sort_field, sort_dir = sort_arg.split(':')
340 else:
341 sort_field = sort_arg, 'asc'
342
343 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
349 def _new_integration(self):
350 c = self.load_default_context()
351 c.available_integrations = integration_type_registry
352 return self._get_template_context(c)
344 353
345 integrations.sort(
346 key=lambda x: getattr(x[1], sort_field),
347 reverse=(sort_dir == 'desc'))
348
349 page_url = webhelpers.paginate.PageURL(
350 self.request.path, self.request.GET)
351 page = safe_int(self.request.GET.get('page', 1), 1)
352
353 integrations = Page(integrations, page=page, items_per_page=10,
354 url=page_url)
355
356 template_context = {
357 'sort_field': sort_field,
358 'rev_sort_dir': sort_dir != 'desc' and 'desc' or 'asc',
359 'current_IntegrationType': self.IntegrationType,
360 'integrations_list': integrations,
361 'available_integrations': integration_type_registry,
362 'c': self._template_c_context(),
363 'request': self.request,
364 }
365 return template_context
366
367 def new_integration(self):
368 template_context = {
369 'available_integrations': integration_type_registry,
370 'c': self._template_c_context(),
371 }
372 return template_context
354 def load_default_context(self):
355 raise NotImplementedError()
373 356
374 357
375 358 class GlobalIntegrationsView(IntegrationSettingsViewBase):
376 def perm_check(self, user):
377 return auth.HasPermissionAll('hg.admin').check_permissions(user=user)
359 def load_default_context(self):
360 c = self._get_local_tmpl_context()
361 c.repo = self.repo
362 c.repo_group = self.repo_group
363 c.navlist = navigation_list(self.request)
364 self._register_global_c(c)
365 return c
366
367 @LoginRequired()
368 @HasPermissionAnyDecorator('hg.admin')
369 def integration_list(self):
370 return self._integration_list()
371
372 @LoginRequired()
373 @HasPermissionAnyDecorator('hg.admin')
374 def settings_get(self):
375 return self._settings_get()
376
377 @LoginRequired()
378 @HasPermissionAnyDecorator('hg.admin')
379 @CSRFRequired()
380 def settings_post(self):
381 return self._settings_post()
382
383 @LoginRequired()
384 @HasPermissionAnyDecorator('hg.admin')
385 def new_integration(self):
386 return self._new_integration()
378 387
379 388
380 389 class RepoIntegrationsView(IntegrationSettingsViewBase):
381 def perm_check(self, user):
382 return auth.HasRepoPermissionAll('repository.admin')(
383 repo_name=self.repo.repo_name, user=user)
390 def load_default_context(self):
391 c = self._get_local_tmpl_context()
392
393 c.repo = self.repo
394 c.repo_group = self.repo_group
395
396 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
397 c.repo_info = self.db_repo = self.repo
398 c.rhodecode_db_repo = self.repo
399 c.repo_name = self.db_repo.repo_name
400 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
401
402 self._register_global_c(c)
403 return c
404
405 @LoginRequired()
406 @HasRepoPermissionAnyDecorator('repository.admin')
407 def integration_list(self):
408 return self._integration_list()
409
410 @LoginRequired()
411 @HasRepoPermissionAnyDecorator('repository.admin')
412 def settings_get(self):
413 return self._settings_get()
414
415 @LoginRequired()
416 @HasRepoPermissionAnyDecorator('repository.admin')
417 @CSRFRequired()
418 def settings_post(self):
419 return self._settings_post()
420
421 @LoginRequired()
422 @HasRepoPermissionAnyDecorator('repository.admin')
423 def new_integration(self):
424 return self._new_integration()
384 425
385 426
386 427 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
387 def perm_check(self, user):
388 return auth.HasRepoGroupPermissionAll('group.admin')(
389 group_name=self.repo_group.group_name, user=user)
428 def load_default_context(self):
429 c = self._get_local_tmpl_context()
430 c.repo = self.repo
431 c.repo_group = self.repo_group
432 c.navlist = navigation_list(self.request)
433 self._register_global_c(c)
434 return c
435
436 @LoginRequired()
437 @HasRepoGroupPermissionAnyDecorator('group.admin')
438 def integration_list(self):
439 return self._integration_list()
390 440
441 @LoginRequired()
442 @HasRepoGroupPermissionAnyDecorator('group.admin')
443 def settings_get(self):
444 return self._settings_get()
445
446 @LoginRequired()
447 @HasRepoGroupPermissionAnyDecorator('group.admin')
448 @CSRFRequired()
449 def settings_post(self):
450 return self._settings_post()
451
452 @LoginRequired()
453 @HasRepoGroupPermissionAnyDecorator('group.admin')
454 def new_integration(self):
455 return self._new_integration()
@@ -1,226 +1,226 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 17 pyroutes.register('favicon', '/favicon.ico', []);
18 18 pyroutes.register('robots', '/robots.txt', []);
19 19 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
20 20 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
21 21 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
22 22 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
23 23 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
24 24 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
25 25 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
26 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
26 27 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
28 28 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
29 29 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
30 30 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
31 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
31 32 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
33 33 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
34 34 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
35 35 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
36 36 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
37 37 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
38 38 pyroutes.register('admin_home', '/_admin', []);
39 39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 40 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
41 41 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
42 42 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
43 43 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
44 44 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
45 45 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
46 46 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
47 47 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
48 48 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
49 49 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
50 50 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
51 51 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
52 52 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
53 53 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
54 54 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
55 55 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
56 56 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
57 57 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
58 58 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
59 59 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
60 60 pyroutes.register('users', '/_admin/users', []);
61 61 pyroutes.register('users_data', '/_admin/users_data', []);
62 62 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
63 63 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
64 64 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
65 65 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
66 66 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
67 67 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
68 68 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
69 69 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
70 70 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
71 71 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
72 72 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
73 73 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
74 74 pyroutes.register('user_groups', '/_admin/user_groups', []);
75 75 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
76 76 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
77 77 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
78 78 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
79 79 pyroutes.register('channelstream_proxy', '/_channelstream', []);
80 80 pyroutes.register('login', '/_admin/login', []);
81 81 pyroutes.register('logout', '/_admin/logout', []);
82 82 pyroutes.register('register', '/_admin/register', []);
83 83 pyroutes.register('reset_password', '/_admin/password_reset', []);
84 84 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
85 85 pyroutes.register('home', '/', []);
86 86 pyroutes.register('user_autocomplete_data', '/_users', []);
87 87 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
88 88 pyroutes.register('repo_list_data', '/_repos', []);
89 89 pyroutes.register('goto_switcher_data', '/_goto_data', []);
90 90 pyroutes.register('journal', '/_admin/journal', []);
91 91 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
92 92 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
93 93 pyroutes.register('journal_public', '/_admin/public_journal', []);
94 94 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
95 95 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
96 96 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
97 97 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
98 98 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
99 99 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
100 100 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
101 101 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
102 102 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
103 103 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
104 104 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
105 105 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
106 106 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
107 107 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
108 108 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
109 109 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
110 110 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
111 111 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
112 112 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
113 113 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
114 114 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
115 115 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
116 116 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
117 117 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
118 118 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
119 119 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
120 120 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
121 121 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
122 122 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
123 123 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
124 124 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
125 125 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 126 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 127 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
128 128 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 129 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
130 130 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 131 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 132 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 133 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 134 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 135 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
136 136 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
137 137 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
138 138 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
139 139 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
140 140 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
141 141 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
142 142 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
143 143 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
144 144 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
145 145 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
146 146 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
147 147 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
148 148 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
149 149 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
150 150 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
151 151 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
152 152 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
153 153 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
154 154 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
155 155 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
156 156 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
157 157 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
158 158 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
159 159 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
160 160 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
161 161 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
162 162 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
163 163 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
164 164 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
165 165 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
166 166 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
167 167 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
168 168 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
169 169 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
170 170 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
171 171 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
172 172 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
173 173 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
174 174 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
175 175 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
176 176 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
177 177 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
178 178 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
179 179 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
180 180 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
181 181 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
182 182 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
183 183 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
184 184 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
185 185 pyroutes.register('search', '/_admin/search', []);
186 186 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
187 187 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
188 188 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
189 189 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
190 190 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
191 191 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
192 192 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
193 193 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
194 194 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
195 195 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
196 196 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
197 197 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
198 198 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
199 199 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
200 200 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
201 201 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
202 202 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
203 203 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
204 204 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
205 205 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
206 206 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
207 207 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
208 208 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
209 209 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
210 210 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
211 211 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
212 212 pyroutes.register('gists_show', '/_admin/gists', []);
213 213 pyroutes.register('gists_new', '/_admin/gists/new', []);
214 214 pyroutes.register('gists_create', '/_admin/gists/create', []);
215 215 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
216 216 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
217 217 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
218 218 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
219 219 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
220 220 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
221 221 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
222 222 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
223 223 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
224 224 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
225 225 pyroutes.register('apiv2', '/_admin/api', []);
226 226 }
@@ -1,69 +1,69 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 4 <%def name="breadcrumbs_links()">
5 5 %if c.repo:
6 6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 7 &raquo;
8 8 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
9 9 &raquo;
10 ${h.link_to(current_IntegrationType.display_name,
10 ${h.link_to(c.current_IntegrationType.display_name,
11 11 request.route_url(route_name='repo_integrations_list',
12 12 repo_name=c.repo.repo_name,
13 integration=current_IntegrationType.key))}
13 integration=c.current_IntegrationType.key))}
14 14 %elif c.repo_group:
15 15 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
16 16 &raquo;
17 17 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
18 18 &raquo;
19 19 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
20 20 &raquo;
21 21 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
22 22 &raquo;
23 ${h.link_to(current_IntegrationType.display_name,
23 ${h.link_to(c.current_IntegrationType.display_name,
24 24 request.route_url(route_name='repo_group_integrations_list',
25 25 repo_group_name=c.repo_group.group_name,
26 integration=current_IntegrationType.key))}
26 integration=c.current_IntegrationType.key))}
27 27 %else:
28 28 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
29 29 &raquo;
30 30 ${h.link_to(_('Settings'),h.url('admin_settings'))}
31 31 &raquo;
32 32 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
33 33 &raquo;
34 ${h.link_to(current_IntegrationType.display_name,
34 ${h.link_to(c.current_IntegrationType.display_name,
35 35 request.route_url(route_name='global_integrations_list',
36 integration=current_IntegrationType.key))}
36 integration=c.current_IntegrationType.key))}
37 37 %endif
38 38
39 %if integration:
39 %if c.integration:
40 40 &raquo;
41 ${integration.name}
42 %elif current_IntegrationType:
41 ${c.integration.name}
42 %elif c.current_IntegrationType:
43 43 &raquo;
44 ${current_IntegrationType.display_name}
44 ${c.current_IntegrationType.display_name}
45 45 %endif
46 46 </%def>
47 47
48 48 <style>
49 49 .control-inputs.item-options, .control-inputs.item-settings {
50 50 float: left;
51 51 width: 100%;
52 52 }
53 53 </style>
54 54 <div class="panel panel-default">
55 55 <div class="panel-heading">
56 56 <h2 class="panel-title">
57 %if integration:
58 ${current_IntegrationType.display_name} - ${integration.name}
57 %if c.integration:
58 ${c.current_IntegrationType.display_name} - ${c.integration.name}
59 59 %else:
60 60 ${_('Create New %(integration_type)s Integration') % {
61 'integration_type': current_IntegrationType.display_name
61 'integration_type': c.current_IntegrationType.display_name
62 62 }}
63 63 %endif
64 64 </h2>
65 65 </div>
66 66 <div class="panel-body">
67 ${form.render() | n}
67 ${c.form.render() | n}
68 68 </div>
69 69 </div>
@@ -1,252 +1,254 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 4 <%def name="breadcrumbs_links()">
5 5 %if c.repo:
6 6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 7 %elif c.repo_group:
8 8 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
9 9 &raquo;
10 10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
11 11 &raquo;
12 12 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
13 13 %else:
14 14 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
15 15 &raquo;
16 16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
17 17 %endif
18 %if current_IntegrationType:
18 %if c.current_IntegrationType:
19 19 &raquo;
20 20 %if c.repo:
21 21 ${h.link_to(_('Integrations'),
22 request.route_url(route_name='repo_integrations_home',
22 request.route_path(route_name='repo_integrations_home',
23 23 repo_name=c.repo.repo_name))}
24 24 %elif c.repo_group:
25 25 ${h.link_to(_('Integrations'),
26 request.route_url(route_name='repo_group_integrations_home',
26 request.route_path(route_name='repo_group_integrations_home',
27 27 repo_group_name=c.repo_group.group_name))}
28 28 %else:
29 29 ${h.link_to(_('Integrations'),
30 request.route_url(route_name='global_integrations_home'))}
30 request.route_path(route_name='global_integrations_home'))}
31 31 %endif
32 32 &raquo;
33 ${current_IntegrationType.display_name}
33 ${c.current_IntegrationType.display_name}
34 34 %else:
35 35 &raquo;
36 36 ${_('Integrations')}
37 37 %endif
38 38 </%def>
39 39
40 40 <div class="panel panel-default">
41 41 <div class="panel-heading">
42 42 <h3 class="panel-title">
43 43 %if c.repo:
44 44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
45 45 %elif c.repo_group:
46 46 ${_('Current Integrations for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
47 47 %else:
48 48 ${_('Current Integrations')}
49 49 %endif
50 50 </h3>
51 51 </div>
52 52 <div class="panel-body">
53 53 <%
54 54 if c.repo:
55 55 home_url = request.route_path('repo_integrations_home',
56 56 repo_name=c.repo.repo_name)
57 57 elif c.repo_group:
58 58 home_url = request.route_path('repo_group_integrations_home',
59 59 repo_group_name=c.repo_group.group_name)
60 60 else:
61 61 home_url = request.route_path('global_integrations_home')
62 62 %>
63 63
64 <a href="${home_url}" class="btn ${not current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a>
64 <a href="${home_url}" class="btn ${not c.current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a>
65 65
66 %for integration_key, IntegrationType in available_integrations.items():
66 %for integration_key, IntegrationType in c.available_integrations.items():
67 67 <%
68 68 if c.repo:
69 69 list_url = request.route_path('repo_integrations_list',
70 70 repo_name=c.repo.repo_name,
71 71 integration=integration_key)
72 72 elif c.repo_group:
73 73 list_url = request.route_path('repo_group_integrations_list',
74 74 repo_group_name=c.repo_group.group_name,
75 75 integration=integration_key)
76 76 else:
77 77 list_url = request.route_path('global_integrations_list',
78 78 integration=integration_key)
79 79 %>
80 80 <a href="${list_url}"
81 class="btn ${current_IntegrationType and integration_key == current_IntegrationType.key and 'btn-primary' or ''}">
81 class="btn ${c.current_IntegrationType and integration_key == c.current_IntegrationType.key and 'btn-primary' or ''}">
82 82 ${IntegrationType.display_name}
83 83 </a>
84 84 %endfor
85 85
86 86 <%
87 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
88
87 89 if c.repo:
88 90 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
89 91 elif c.repo_group:
90 92 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
91 93 else:
92 94 create_url = h.route_path('global_integrations_new')
93 95 %>
94 96 <p class="pull-right">
95 97 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
96 98 </p>
97 99
98 100 <table class="rctable integrations">
99 101 <thead>
100 102 <tr>
101 <th><a href="?sort=enabled:${rev_sort_dir}">${_('Enabled')}</a></th>
102 <th><a href="?sort=name:${rev_sort_dir}">${_('Name')}</a></th>
103 <th colspan="2"><a href="?sort=integration_type:${rev_sort_dir}">${_('Type')}</a></th>
104 <th><a href="?sort=scope:${rev_sort_dir}">${_('Scope')}</a></th>
103 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
104 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
105 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
106 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
105 107 <th>${_('Actions')}</th>
106 108 <th></th>
107 109 </tr>
108 110 </thead>
109 111 <tbody>
110 %if not integrations_list:
112 %if not c.integrations_list:
111 113 <tr>
112 114 <td colspan="7">
113 <% integration_type = current_IntegrationType and current_IntegrationType.display_name or '' %>
115
114 116 %if c.repo:
115 117 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
116 118 %elif c.repo_group:
117 119 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
118 120 %else:
119 121 ${_('No {type} integrations exist yet.').format(type=integration_type)}
120 122 %endif
121 123
122 %if current_IntegrationType:
124 %if c.current_IntegrationType:
123 125 <%
124 126 if c.repo:
125 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=current_IntegrationType.key)
127 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
126 128 elif c.repo_group:
127 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=current_IntegrationType.key)
129 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
128 130 else:
129 create_url = h.route_path('global_integrations_create', integration=current_IntegrationType.key)
131 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
130 132 %>
131 133 %endif
132 134
133 135 <a href="${create_url}">${_(u'Create one')}</a>
134 136 </td>
135 137 </tr>
136 138 %endif
137 %for IntegrationType, integration in integrations_list:
139 %for IntegrationType, integration in c.integrations_list:
138 140 <tr id="integration_${integration.integration_id}">
139 141 <td class="td-enabled">
140 142 %if integration.enabled:
141 143 <div class="flag_status approved pull-left"></div>
142 144 %else:
143 145 <div class="flag_status rejected pull-left"></div>
144 146 %endif
145 147 </td>
146 148 <td class="td-description">
147 149 ${integration.name}
148 150 </td>
149 151 <td class="td-icon">
150 %if integration.integration_type in available_integrations:
152 %if integration.integration_type in c.available_integrations:
151 153 <div class="integration-icon">
152 ${available_integrations[integration.integration_type].icon|n}
154 ${c.available_integrations[integration.integration_type].icon|n}
153 155 </div>
154 156 %else:
155 157 ?
156 158 %endif
157 159 </td>
158 160 <td class="td-type">
159 161 ${integration.integration_type}
160 162 </td>
161 163 <td class="td-scope">
162 164 %if integration.repo:
163 165 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
164 166 ${_('repo')}:${integration.repo.repo_name}
165 167 </a>
166 168 %elif integration.repo_group:
167 169 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
168 170 ${_('repogroup')}:${integration.repo_group.group_name}
169 171 %if integration.child_repos_only:
170 172 ${_('child repos only')}
171 173 %else:
172 174 ${_('cascade to all')}
173 175 %endif
174 176 </a>
175 177 %else:
176 178 %if integration.child_repos_only:
177 179 ${_('top level repos only')}
178 180 %else:
179 181 ${_('global')}
180 182 %endif
181 183 </td>
182 184 %endif
183 185 <td class="td-action">
184 186 %if not IntegrationType:
185 187 ${_('unknown integration')}
186 188 %else:
187 189 <%
188 190 if c.repo:
189 191 edit_url = request.route_path('repo_integrations_edit',
190 192 repo_name=c.repo.repo_name,
191 193 integration=integration.integration_type,
192 194 integration_id=integration.integration_id)
193 195 elif c.repo_group:
194 196 edit_url = request.route_path('repo_group_integrations_edit',
195 197 repo_group_name=c.repo_group.group_name,
196 198 integration=integration.integration_type,
197 199 integration_id=integration.integration_id)
198 200 else:
199 201 edit_url = request.route_path('global_integrations_edit',
200 202 integration=integration.integration_type,
201 203 integration_id=integration.integration_id)
202 204 %>
203 205 <div class="grid_edit">
204 206 <a href="${edit_url}">${_('Edit')}</a>
205 207 </div>
206 208 <div class="grid_delete">
207 209 <a href="${edit_url}"
208 210 class="btn btn-link btn-danger delete_integration_entry"
209 211 data-desc="${integration.name}"
210 212 data-uid="${integration.integration_id}">
211 213 ${_('Delete')}
212 214 </a>
213 215 </div>
214 216 %endif
215 217 </td>
216 218 </tr>
217 219 %endfor
218 220 <tr id="last-row"></tr>
219 221 </tbody>
220 222 </table>
221 223 <div class="integrations-paginator">
222 224 <div class="pagination-wh pagination-left">
223 ${integrations_list.pager('$link_previous ~2~ $link_next')}
225 ${c.integrations_list.pager('$link_previous ~2~ $link_next')}
224 226 </div>
225 227 </div>
226 228 </div>
227 229 </div>
228 230 <script type="text/javascript">
229 231 var delete_integration = function(entry) {
230 232 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
231 233 var request = $.ajax({
232 234 type: "POST",
233 235 url: $(entry).attr('href'),
234 236 data: {
235 237 'delete': 'delete',
236 238 'csrf_token': CSRF_TOKEN
237 239 },
238 240 success: function(){
239 241 location.reload();
240 242 },
241 243 error: function(data, textStatus, errorThrown){
242 244 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
243 245 }
244 246 });
245 247 };
246 248 };
247 249
248 250 $('.delete_integration_entry').on('click', function(e){
249 251 e.preventDefault();
250 252 delete_integration(this);
251 253 });
252 254 </script> No newline at end of file
@@ -1,66 +1,66 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3 <%namespace name="widgets" file="/widgets.mako"/>
4 4
5 5 <%def name="breadcrumbs_links()">
6 6 %if c.repo:
7 7 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
8 8 &raquo;
9 9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
10 10 %elif c.repo_group:
11 11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 12 &raquo;
13 13 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
14 14 &raquo;
15 15 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
16 16 &raquo;
17 17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
18 18 %else:
19 19 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
20 20 &raquo;
21 21 ${h.link_to(_('Settings'),h.url('admin_settings'))}
22 22 &raquo;
23 23 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
24 24 %endif
25 25 &raquo;
26 26 ${_('Create new integration')}
27 27 </%def>
28 28 <%widgets:panel class_='integrations'>
29 29 <%def name="title()">
30 30 %if c.repo:
31 31 ${_('Create New Integration for repository: {repo_name}').format(repo_name=c.repo.repo_name)}
32 32 %elif c.repo_group:
33 33 ${_('Create New Integration for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
34 34 %else:
35 35 ${_('Create New Global Integration')}
36 36 %endif
37 37 </%def>
38 38
39 %for integration, IntegrationType in available_integrations.items():
39 %for integration, IntegrationType in c.available_integrations.items():
40 40 <%
41 41 if c.repo:
42 42 create_url = request.route_path('repo_integrations_create',
43 43 repo_name=c.repo.repo_name,
44 44 integration=integration)
45 45 elif c.repo_group:
46 46 create_url = request.route_path('repo_group_integrations_create',
47 47 repo_group_name=c.repo_group.group_name,
48 48 integration=integration)
49 49 else:
50 50 create_url = request.route_path('global_integrations_create',
51 51 integration=integration)
52 52 %>
53 53 <a href="${create_url}" class="integration-box">
54 54 <%widgets:panel>
55 55 <h2>
56 56 <div class="integration-icon">
57 57 ${IntegrationType.icon|n}
58 58 </div>
59 59 ${IntegrationType.display_name}
60 60 </h2>
61 61 ${IntegrationType.description or _('No description available')}
62 62 </%widgets:panel>
63 63 </a>
64 64 %endfor
65 65 <div style="clear:both"></div>
66 66 </%widgets:panel>
@@ -1,222 +1,216 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import time
22 22 import pytest
23 23
24 24 from rhodecode import events
25 25 from rhodecode.tests.fixture import Fixture
26 26 from rhodecode.model.db import Session, Integration
27 27 from rhodecode.model.integration import IntegrationModel
28 from rhodecode.integrations.types.base import IntegrationTypeBase
29 28
30 29
31 30 class TestDeleteScopesDeletesIntegrations(object):
32 def test_delete_repo_with_integration_deletes_integration(self,
33 repo_integration_stub):
31 def test_delete_repo_with_integration_deletes_integration(
32 self, repo_integration_stub):
33
34 34 Session().delete(repo_integration_stub.repo)
35 35 Session().commit()
36 36 Session().expire_all()
37 37 integration = Integration.get(repo_integration_stub.integration_id)
38 38 assert integration is None
39 39
40 def test_delete_repo_group_with_integration_deletes_integration(
41 self, repogroup_integration_stub):
40 42
41 def test_delete_repo_group_with_integration_deletes_integration(self,
42 repogroup_integration_stub):
43 43 Session().delete(repogroup_integration_stub.repo_group)
44 44 Session().commit()
45 45 Session().expire_all()
46 46 integration = Integration.get(repogroup_integration_stub.integration_id)
47 47 assert integration is None
48 48
49 49
50 50 @pytest.fixture
51 51 def integration_repos(request, StubIntegrationType, stub_integration_settings):
52 52 """
53 53 Create repositories and integrations for testing, and destroy them after
54 54
55 55 Structure:
56 56 root_repo
57 57 parent_group/
58 58 parent_repo
59 59 child_group/
60 60 child_repo
61 61 other_group/
62 62 other_repo
63 63 """
64 64 fixture = Fixture()
65 65
66 66
67 67 parent_group_id = 'int_test_parent_group_%s' % time.time()
68 68 parent_group = fixture.create_repo_group(parent_group_id)
69 69
70 70 other_group_id = 'int_test_other_group_%s' % time.time()
71 71 other_group = fixture.create_repo_group(other_group_id)
72 72
73 73 child_group_id = (
74 74 parent_group_id + '/' + 'int_test_child_group_%s' % time.time())
75 75 child_group = fixture.create_repo_group(child_group_id)
76 76
77 77 parent_repo_id = 'int_test_parent_repo_%s' % time.time()
78 78 parent_repo = fixture.create_repo(parent_repo_id, repo_group=parent_group)
79 79
80 80 child_repo_id = 'int_test_child_repo_%s' % time.time()
81 81 child_repo = fixture.create_repo(child_repo_id, repo_group=child_group)
82 82
83 83 other_repo_id = 'int_test_other_repo_%s' % time.time()
84 84 other_repo = fixture.create_repo(other_repo_id, repo_group=other_group)
85 85
86 86 root_repo_id = 'int_test_repo_root_%s' % time.time()
87 87 root_repo = fixture.create_repo(root_repo_id)
88 88
89 89 integrations = {}
90 90 for name, repo, repo_group, child_repos_only in [
91 91 ('global', None, None, None),
92 92 ('root_repos', None, None, True),
93 93 ('parent_repo', parent_repo, None, None),
94 94 ('child_repo', child_repo, None, None),
95 95 ('other_repo', other_repo, None, None),
96 96 ('root_repo', root_repo, None, None),
97 97 ('parent_group', None, parent_group, True),
98 98 ('parent_group_recursive', None, parent_group, False),
99 99 ('child_group', None, child_group, True),
100 100 ('child_group_recursive', None, child_group, False),
101 101 ('other_group', None, other_group, True),
102 102 ('other_group_recursive', None, other_group, False),
103 103 ]:
104 104 integrations[name] = IntegrationModel().create(
105 105 StubIntegrationType, settings=stub_integration_settings,
106 106 enabled=True, name='test %s integration' % name,
107 107 repo=repo, repo_group=repo_group, child_repos_only=child_repos_only)
108 108
109 109 Session().commit()
110 110
111 111 def _cleanup():
112 112 for integration in integrations.values():
113 113 Session.delete(integration)
114 114
115 115 fixture.destroy_repo(root_repo)
116 116 fixture.destroy_repo(child_repo)
117 117 fixture.destroy_repo(parent_repo)
118 118 fixture.destroy_repo(other_repo)
119 119 fixture.destroy_repo_group(child_group)
120 120 fixture.destroy_repo_group(parent_group)
121 121 fixture.destroy_repo_group(other_group)
122 122
123 123 request.addfinalizer(_cleanup)
124 124
125 125 return {
126 126 'integrations': integrations,
127 127 'repos': {
128 128 'root_repo': root_repo,
129 129 'other_repo': other_repo,
130 130 'parent_repo': parent_repo,
131 131 'child_repo': child_repo,
132 132 }
133 133 }
134 134
135 135
136 136 def test_enabled_integration_repo_scopes(integration_repos):
137 137 integrations = integration_repos['integrations']
138 138 repos = integration_repos['repos']
139 139
140 140 triggered_integrations = IntegrationModel().get_for_event(
141 141 events.RepoEvent(repos['root_repo']))
142 142
143 143 assert triggered_integrations == [
144 144 integrations['global'],
145 145 integrations['root_repos'],
146 146 integrations['root_repo'],
147 147 ]
148 148
149
150 149 triggered_integrations = IntegrationModel().get_for_event(
151 150 events.RepoEvent(repos['other_repo']))
152 151
153 152 assert triggered_integrations == [
154 153 integrations['global'],
155 154 integrations['other_repo'],
156 155 integrations['other_group'],
157 156 integrations['other_group_recursive'],
158 157 ]
159 158
160
161 159 triggered_integrations = IntegrationModel().get_for_event(
162 160 events.RepoEvent(repos['parent_repo']))
163 161
164 162 assert triggered_integrations == [
165 163 integrations['global'],
166 164 integrations['parent_repo'],
167 165 integrations['parent_group'],
168 166 integrations['parent_group_recursive'],
169 167 ]
170 168
171 169 triggered_integrations = IntegrationModel().get_for_event(
172 170 events.RepoEvent(repos['child_repo']))
173 171
174 172 assert triggered_integrations == [
175 173 integrations['global'],
176 174 integrations['child_repo'],
177 175 integrations['parent_group_recursive'],
178 176 integrations['child_group'],
179 177 integrations['child_group_recursive'],
180 178 ]
181 179
182 180
183 181 def test_disabled_integration_repo_scopes(integration_repos):
184 182 integrations = integration_repos['integrations']
185 183 repos = integration_repos['repos']
186 184
187 185 for integration in integrations.values():
188 186 integration.enabled = False
189 187 Session().commit()
190 188
191 189 triggered_integrations = IntegrationModel().get_for_event(
192 190 events.RepoEvent(repos['root_repo']))
193 191
194 192 assert triggered_integrations == []
195 193
196
197 194 triggered_integrations = IntegrationModel().get_for_event(
198 195 events.RepoEvent(repos['parent_repo']))
199 196
200 197 assert triggered_integrations == []
201 198
202
203 199 triggered_integrations = IntegrationModel().get_for_event(
204 200 events.RepoEvent(repos['child_repo']))
205 201
206 202 assert triggered_integrations == []
207 203
208
209 204 triggered_integrations = IntegrationModel().get_for_event(
210 205 events.RepoEvent(repos['other_repo']))
211 206
212 207 assert triggered_integrations == []
213 208
214 209
215
216 210 def test_enabled_non_repo_integrations(integration_repos):
217 211 integrations = integration_repos['integrations']
218 212
219 213 triggered_integrations = IntegrationModel().get_for_event(
220 214 events.UserPreCreate({}))
221 215
222 216 assert triggered_integrations == [integrations['global']]
@@ -1,1832 +1,1832 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import collections
22 22 import datetime
23 23 import hashlib
24 24 import os
25 25 import re
26 26 import pprint
27 27 import shutil
28 28 import socket
29 29 import subprocess32
30 30 import time
31 31 import uuid
32 32 import dateutil.tz
33 33
34 34 import mock
35 35 import pyramid.testing
36 36 import pytest
37 37 import colander
38 38 import requests
39 39
40 40 import rhodecode
41 41 from rhodecode.lib.utils2 import AttributeDict
42 42 from rhodecode.model.changeset_status import ChangesetStatusModel
43 43 from rhodecode.model.comment import CommentsModel
44 44 from rhodecode.model.db import (
45 45 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
46 46 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.pull_request import PullRequestModel
49 49 from rhodecode.model.repo import RepoModel
50 50 from rhodecode.model.repo_group import RepoGroupModel
51 51 from rhodecode.model.user import UserModel
52 52 from rhodecode.model.settings import VcsSettingsModel
53 53 from rhodecode.model.user_group import UserGroupModel
54 54 from rhodecode.model.integration import IntegrationModel
55 55 from rhodecode.integrations import integration_type_registry
56 56 from rhodecode.integrations.types.base import IntegrationTypeBase
57 57 from rhodecode.lib.utils import repo2db_mapper
58 58 from rhodecode.lib.vcs import create_vcsserver_proxy
59 59 from rhodecode.lib.vcs.backends import get_backend
60 60 from rhodecode.lib.vcs.nodes import FileNode
61 61 from rhodecode.tests import (
62 62 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
63 63 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
64 64 TEST_USER_REGULAR_PASS)
65 65 from rhodecode.tests.utils import CustomTestApp, set_anonymous_access, add_test_routes
66 66 from rhodecode.tests.fixture import Fixture
67 67
68 68
69 69 def _split_comma(value):
70 70 return value.split(',')
71 71
72 72
73 73 def pytest_addoption(parser):
74 74 parser.addoption(
75 75 '--keep-tmp-path', action='store_true',
76 76 help="Keep the test temporary directories")
77 77 parser.addoption(
78 78 '--backends', action='store', type=_split_comma,
79 79 default=['git', 'hg', 'svn'],
80 80 help="Select which backends to test for backend specific tests.")
81 81 parser.addoption(
82 82 '--dbs', action='store', type=_split_comma,
83 83 default=['sqlite'],
84 84 help="Select which database to test for database specific tests. "
85 85 "Possible options are sqlite,postgres,mysql")
86 86 parser.addoption(
87 87 '--appenlight', '--ae', action='store_true',
88 88 help="Track statistics in appenlight.")
89 89 parser.addoption(
90 90 '--appenlight-api-key', '--ae-key',
91 91 help="API key for Appenlight.")
92 92 parser.addoption(
93 93 '--appenlight-url', '--ae-url',
94 94 default="https://ae.rhodecode.com",
95 95 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
96 96 parser.addoption(
97 97 '--sqlite-connection-string', action='store',
98 98 default='', help="Connection string for the dbs tests with SQLite")
99 99 parser.addoption(
100 100 '--postgres-connection-string', action='store',
101 101 default='', help="Connection string for the dbs tests with Postgres")
102 102 parser.addoption(
103 103 '--mysql-connection-string', action='store',
104 104 default='', help="Connection string for the dbs tests with MySQL")
105 105 parser.addoption(
106 106 '--repeat', type=int, default=100,
107 107 help="Number of repetitions in performance tests.")
108 108
109 109
110 110 def pytest_configure(config):
111 111 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
112 112 from rhodecode.config import patches
113 113 patches.kombu_1_5_1_python_2_7_11()
114 114
115 115
116 116 def pytest_collection_modifyitems(session, config, items):
117 117 # nottest marked, compare nose, used for transition from nose to pytest
118 118 remaining = [
119 119 i for i in items if getattr(i.obj, '__test__', True)]
120 120 items[:] = remaining
121 121
122 122
123 123 def pytest_generate_tests(metafunc):
124 124 # Support test generation based on --backend parameter
125 125 if 'backend_alias' in metafunc.fixturenames:
126 126 backends = get_backends_from_metafunc(metafunc)
127 127 scope = None
128 128 if not backends:
129 129 pytest.skip("Not enabled for any of selected backends")
130 130 metafunc.parametrize('backend_alias', backends, scope=scope)
131 131 elif hasattr(metafunc.function, 'backends'):
132 132 backends = get_backends_from_metafunc(metafunc)
133 133 if not backends:
134 134 pytest.skip("Not enabled for any of selected backends")
135 135
136 136
137 137 def get_backends_from_metafunc(metafunc):
138 138 requested_backends = set(metafunc.config.getoption('--backends'))
139 139 if hasattr(metafunc.function, 'backends'):
140 140 # Supported backends by this test function, created from
141 141 # pytest.mark.backends
142 142 backends = metafunc.function.backends.args
143 143 elif hasattr(metafunc.cls, 'backend_alias'):
144 144 # Support class attribute "backend_alias", this is mainly
145 145 # for legacy reasons for tests not yet using pytest.mark.backends
146 146 backends = [metafunc.cls.backend_alias]
147 147 else:
148 148 backends = metafunc.config.getoption('--backends')
149 149 return requested_backends.intersection(backends)
150 150
151 151
152 152 @pytest.fixture(scope='session', autouse=True)
153 153 def activate_example_rcextensions(request):
154 154 """
155 155 Patch in an example rcextensions module which verifies passed in kwargs.
156 156 """
157 157 from rhodecode.tests.other import example_rcextensions
158 158
159 159 old_extensions = rhodecode.EXTENSIONS
160 160 rhodecode.EXTENSIONS = example_rcextensions
161 161
162 162 @request.addfinalizer
163 163 def cleanup():
164 164 rhodecode.EXTENSIONS = old_extensions
165 165
166 166
167 167 @pytest.fixture
168 168 def capture_rcextensions():
169 169 """
170 170 Returns the recorded calls to entry points in rcextensions.
171 171 """
172 172 calls = rhodecode.EXTENSIONS.calls
173 173 calls.clear()
174 174 # Note: At this moment, it is still the empty dict, but that will
175 175 # be filled during the test run and since it is a reference this
176 176 # is enough to make it work.
177 177 return calls
178 178
179 179
180 180 @pytest.fixture(scope='session')
181 181 def http_environ_session():
182 182 """
183 183 Allow to use "http_environ" in session scope.
184 184 """
185 185 return http_environ(
186 186 http_host_stub=http_host_stub())
187 187
188 188
189 189 @pytest.fixture
190 190 def http_host_stub():
191 191 """
192 192 Value of HTTP_HOST in the test run.
193 193 """
194 194 return 'example.com:80'
195 195
196 196
197 197 @pytest.fixture
198 198 def http_host_only_stub():
199 199 """
200 200 Value of HTTP_HOST in the test run.
201 201 """
202 202 return http_host_stub().split(':')[0]
203 203
204 204
205 205 @pytest.fixture
206 206 def http_environ(http_host_stub):
207 207 """
208 208 HTTP extra environ keys.
209 209
210 210 User by the test application and as well for setting up the pylons
211 211 environment. In the case of the fixture "app" it should be possible
212 212 to override this for a specific test case.
213 213 """
214 214 return {
215 215 'SERVER_NAME': http_host_only_stub(),
216 216 'SERVER_PORT': http_host_stub.split(':')[1],
217 217 'HTTP_HOST': http_host_stub,
218 218 'HTTP_USER_AGENT': 'rc-test-agent',
219 219 'REQUEST_METHOD': 'GET'
220 220 }
221 221
222 222
223 223 @pytest.fixture(scope='function')
224 224 def app(request, config_stub, pylonsapp, http_environ):
225 225 app = CustomTestApp(
226 226 pylonsapp,
227 227 extra_environ=http_environ)
228 228 if request.cls:
229 229 request.cls.app = app
230 230 return app
231 231
232 232
233 233 @pytest.fixture(scope='session')
234 234 def app_settings(pylonsapp, pylons_config):
235 235 """
236 236 Settings dictionary used to create the app.
237 237
238 238 Parses the ini file and passes the result through the sanitize and apply
239 239 defaults mechanism in `rhodecode.config.middleware`.
240 240 """
241 241 from paste.deploy.loadwsgi import loadcontext, APP
242 242 from rhodecode.config.middleware import (
243 243 sanitize_settings_and_apply_defaults)
244 244 context = loadcontext(APP, 'config:' + pylons_config)
245 245 settings = sanitize_settings_and_apply_defaults(context.config())
246 246 return settings
247 247
248 248
249 249 @pytest.fixture(scope='session')
250 250 def db(app_settings):
251 251 """
252 252 Initializes the database connection.
253 253
254 254 It uses the same settings which are used to create the ``pylonsapp`` or
255 255 ``app`` fixtures.
256 256 """
257 257 from rhodecode.config.utils import initialize_database
258 258 initialize_database(app_settings)
259 259
260 260
261 261 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
262 262
263 263
264 264 def _autologin_user(app, *args):
265 265 session = login_user_session(app, *args)
266 266 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
267 267 return LoginData(csrf_token, session['rhodecode_user'])
268 268
269 269
270 270 @pytest.fixture
271 271 def autologin_user(app):
272 272 """
273 273 Utility fixture which makes sure that the admin user is logged in
274 274 """
275 275 return _autologin_user(app)
276 276
277 277
278 278 @pytest.fixture
279 279 def autologin_regular_user(app):
280 280 """
281 281 Utility fixture which makes sure that the regular user is logged in
282 282 """
283 283 return _autologin_user(
284 284 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
285 285
286 286
287 287 @pytest.fixture(scope='function')
288 288 def csrf_token(request, autologin_user):
289 289 return autologin_user.csrf_token
290 290
291 291
292 292 @pytest.fixture(scope='function')
293 293 def xhr_header(request):
294 294 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
295 295
296 296
297 297 @pytest.fixture
298 298 def real_crypto_backend(monkeypatch):
299 299 """
300 300 Switch the production crypto backend on for this test.
301 301
302 302 During the test run the crypto backend is replaced with a faster
303 303 implementation based on the MD5 algorithm.
304 304 """
305 305 monkeypatch.setattr(rhodecode, 'is_test', False)
306 306
307 307
308 308 @pytest.fixture(scope='class')
309 309 def index_location(request, pylonsapp):
310 310 index_location = pylonsapp.config['app_conf']['search.location']
311 311 if request.cls:
312 312 request.cls.index_location = index_location
313 313 return index_location
314 314
315 315
316 316 @pytest.fixture(scope='session', autouse=True)
317 317 def tests_tmp_path(request):
318 318 """
319 319 Create temporary directory to be used during the test session.
320 320 """
321 321 if not os.path.exists(TESTS_TMP_PATH):
322 322 os.makedirs(TESTS_TMP_PATH)
323 323
324 324 if not request.config.getoption('--keep-tmp-path'):
325 325 @request.addfinalizer
326 326 def remove_tmp_path():
327 327 shutil.rmtree(TESTS_TMP_PATH)
328 328
329 329 return TESTS_TMP_PATH
330 330
331 331
332 332 @pytest.fixture
333 333 def test_repo_group(request):
334 334 """
335 335 Create a temporary repository group, and destroy it after
336 336 usage automatically
337 337 """
338 338 fixture = Fixture()
339 repogroupid = 'test_repo_group_%s' % int(time.time())
339 repogroupid = 'test_repo_group_%s' % str(time.time()).replace('.', '')
340 340 repo_group = fixture.create_repo_group(repogroupid)
341 341
342 342 def _cleanup():
343 343 fixture.destroy_repo_group(repogroupid)
344 344
345 345 request.addfinalizer(_cleanup)
346 346 return repo_group
347 347
348 348
349 349 @pytest.fixture
350 350 def test_user_group(request):
351 351 """
352 352 Create a temporary user group, and destroy it after
353 353 usage automatically
354 354 """
355 355 fixture = Fixture()
356 usergroupid = 'test_user_group_%s' % int(time.time())
356 usergroupid = 'test_user_group_%s' % str(time.time()).replace('.', '')
357 357 user_group = fixture.create_user_group(usergroupid)
358 358
359 359 def _cleanup():
360 360 fixture.destroy_user_group(user_group)
361 361
362 362 request.addfinalizer(_cleanup)
363 363 return user_group
364 364
365 365
366 366 @pytest.fixture(scope='session')
367 367 def test_repo(request):
368 368 container = TestRepoContainer()
369 369 request.addfinalizer(container._cleanup)
370 370 return container
371 371
372 372
373 373 class TestRepoContainer(object):
374 374 """
375 375 Container for test repositories which are used read only.
376 376
377 377 Repositories will be created on demand and re-used during the lifetime
378 378 of this object.
379 379
380 380 Usage to get the svn test repository "minimal"::
381 381
382 382 test_repo = TestContainer()
383 383 repo = test_repo('minimal', 'svn')
384 384
385 385 """
386 386
387 387 dump_extractors = {
388 388 'git': utils.extract_git_repo_from_dump,
389 389 'hg': utils.extract_hg_repo_from_dump,
390 390 'svn': utils.extract_svn_repo_from_dump,
391 391 }
392 392
393 393 def __init__(self):
394 394 self._cleanup_repos = []
395 395 self._fixture = Fixture()
396 396 self._repos = {}
397 397
398 398 def __call__(self, dump_name, backend_alias, config=None):
399 399 key = (dump_name, backend_alias)
400 400 if key not in self._repos:
401 401 repo = self._create_repo(dump_name, backend_alias, config)
402 402 self._repos[key] = repo.repo_id
403 403 return Repository.get(self._repos[key])
404 404
405 405 def _create_repo(self, dump_name, backend_alias, config):
406 406 repo_name = '%s-%s' % (backend_alias, dump_name)
407 407 backend_class = get_backend(backend_alias)
408 408 dump_extractor = self.dump_extractors[backend_alias]
409 409 repo_path = dump_extractor(dump_name, repo_name)
410 410
411 411 vcs_repo = backend_class(repo_path, config=config)
412 412 repo2db_mapper({repo_name: vcs_repo})
413 413
414 414 repo = RepoModel().get_by_repo_name(repo_name)
415 415 self._cleanup_repos.append(repo_name)
416 416 return repo
417 417
418 418 def _cleanup(self):
419 419 for repo_name in reversed(self._cleanup_repos):
420 420 self._fixture.destroy_repo(repo_name)
421 421
422 422
423 423 @pytest.fixture
424 424 def backend(request, backend_alias, pylonsapp, test_repo):
425 425 """
426 426 Parametrized fixture which represents a single backend implementation.
427 427
428 428 It respects the option `--backends` to focus the test run on specific
429 429 backend implementations.
430 430
431 431 It also supports `pytest.mark.xfail_backends` to mark tests as failing
432 432 for specific backends. This is intended as a utility for incremental
433 433 development of a new backend implementation.
434 434 """
435 435 if backend_alias not in request.config.getoption('--backends'):
436 436 pytest.skip("Backend %s not selected." % (backend_alias, ))
437 437
438 438 utils.check_xfail_backends(request.node, backend_alias)
439 439 utils.check_skip_backends(request.node, backend_alias)
440 440
441 441 repo_name = 'vcs_test_%s' % (backend_alias, )
442 442 backend = Backend(
443 443 alias=backend_alias,
444 444 repo_name=repo_name,
445 445 test_name=request.node.name,
446 446 test_repo_container=test_repo)
447 447 request.addfinalizer(backend.cleanup)
448 448 return backend
449 449
450 450
451 451 @pytest.fixture
452 452 def backend_git(request, pylonsapp, test_repo):
453 453 return backend(request, 'git', pylonsapp, test_repo)
454 454
455 455
456 456 @pytest.fixture
457 457 def backend_hg(request, pylonsapp, test_repo):
458 458 return backend(request, 'hg', pylonsapp, test_repo)
459 459
460 460
461 461 @pytest.fixture
462 462 def backend_svn(request, pylonsapp, test_repo):
463 463 return backend(request, 'svn', pylonsapp, test_repo)
464 464
465 465
466 466 @pytest.fixture
467 467 def backend_random(backend_git):
468 468 """
469 469 Use this to express that your tests need "a backend.
470 470
471 471 A few of our tests need a backend, so that we can run the code. This
472 472 fixture is intended to be used for such cases. It will pick one of the
473 473 backends and run the tests.
474 474
475 475 The fixture `backend` would run the test multiple times for each
476 476 available backend which is a pure waste of time if the test is
477 477 independent of the backend type.
478 478 """
479 479 # TODO: johbo: Change this to pick a random backend
480 480 return backend_git
481 481
482 482
483 483 @pytest.fixture
484 484 def backend_stub(backend_git):
485 485 """
486 486 Use this to express that your tests need a backend stub
487 487
488 488 TODO: mikhail: Implement a real stub logic instead of returning
489 489 a git backend
490 490 """
491 491 return backend_git
492 492
493 493
494 494 @pytest.fixture
495 495 def repo_stub(backend_stub):
496 496 """
497 497 Use this to express that your tests need a repository stub
498 498 """
499 499 return backend_stub.create_repo()
500 500
501 501
502 502 class Backend(object):
503 503 """
504 504 Represents the test configuration for one supported backend
505 505
506 506 Provides easy access to different test repositories based on
507 507 `__getitem__`. Such repositories will only be created once per test
508 508 session.
509 509 """
510 510
511 511 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
512 512 _master_repo = None
513 513 _commit_ids = {}
514 514
515 515 def __init__(self, alias, repo_name, test_name, test_repo_container):
516 516 self.alias = alias
517 517 self.repo_name = repo_name
518 518 self._cleanup_repos = []
519 519 self._test_name = test_name
520 520 self._test_repo_container = test_repo_container
521 521 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
522 522 # Fixture will survive in the end.
523 523 self._fixture = Fixture()
524 524
525 525 def __getitem__(self, key):
526 526 return self._test_repo_container(key, self.alias)
527 527
528 528 def create_test_repo(self, key, config=None):
529 529 return self._test_repo_container(key, self.alias, config)
530 530
531 531 @property
532 532 def repo(self):
533 533 """
534 534 Returns the "current" repository. This is the vcs_test repo or the
535 535 last repo which has been created with `create_repo`.
536 536 """
537 537 from rhodecode.model.db import Repository
538 538 return Repository.get_by_repo_name(self.repo_name)
539 539
540 540 @property
541 541 def default_branch_name(self):
542 542 VcsRepository = get_backend(self.alias)
543 543 return VcsRepository.DEFAULT_BRANCH_NAME
544 544
545 545 @property
546 546 def default_head_id(self):
547 547 """
548 548 Returns the default head id of the underlying backend.
549 549
550 550 This will be the default branch name in case the backend does have a
551 551 default branch. In the other cases it will point to a valid head
552 552 which can serve as the base to create a new commit on top of it.
553 553 """
554 554 vcsrepo = self.repo.scm_instance()
555 555 head_id = (
556 556 vcsrepo.DEFAULT_BRANCH_NAME or
557 557 vcsrepo.commit_ids[-1])
558 558 return head_id
559 559
560 560 @property
561 561 def commit_ids(self):
562 562 """
563 563 Returns the list of commits for the last created repository
564 564 """
565 565 return self._commit_ids
566 566
567 567 def create_master_repo(self, commits):
568 568 """
569 569 Create a repository and remember it as a template.
570 570
571 571 This allows to easily create derived repositories to construct
572 572 more complex scenarios for diff, compare and pull requests.
573 573
574 574 Returns a commit map which maps from commit message to raw_id.
575 575 """
576 576 self._master_repo = self.create_repo(commits=commits)
577 577 return self._commit_ids
578 578
579 579 def create_repo(
580 580 self, commits=None, number_of_commits=0, heads=None,
581 581 name_suffix=u'', **kwargs):
582 582 """
583 583 Create a repository and record it for later cleanup.
584 584
585 585 :param commits: Optional. A sequence of dict instances.
586 586 Will add a commit per entry to the new repository.
587 587 :param number_of_commits: Optional. If set to a number, this number of
588 588 commits will be added to the new repository.
589 589 :param heads: Optional. Can be set to a sequence of of commit
590 590 names which shall be pulled in from the master repository.
591 591
592 592 """
593 593 self.repo_name = self._next_repo_name() + name_suffix
594 594 repo = self._fixture.create_repo(
595 595 self.repo_name, repo_type=self.alias, **kwargs)
596 596 self._cleanup_repos.append(repo.repo_name)
597 597
598 598 commits = commits or [
599 599 {'message': 'Commit %s of %s' % (x, self.repo_name)}
600 600 for x in xrange(number_of_commits)]
601 601 self._add_commits_to_repo(repo.scm_instance(), commits)
602 602 if heads:
603 603 self.pull_heads(repo, heads)
604 604
605 605 return repo
606 606
607 607 def pull_heads(self, repo, heads):
608 608 """
609 609 Make sure that repo contains all commits mentioned in `heads`
610 610 """
611 611 vcsmaster = self._master_repo.scm_instance()
612 612 vcsrepo = repo.scm_instance()
613 613 vcsrepo.config.clear_section('hooks')
614 614 commit_ids = [self._commit_ids[h] for h in heads]
615 615 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
616 616
617 617 def create_fork(self):
618 618 repo_to_fork = self.repo_name
619 619 self.repo_name = self._next_repo_name()
620 620 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
621 621 self._cleanup_repos.append(self.repo_name)
622 622 return repo
623 623
624 624 def new_repo_name(self, suffix=u''):
625 625 self.repo_name = self._next_repo_name() + suffix
626 626 self._cleanup_repos.append(self.repo_name)
627 627 return self.repo_name
628 628
629 629 def _next_repo_name(self):
630 630 return u"%s_%s" % (
631 631 self.invalid_repo_name.sub(u'_', self._test_name),
632 632 len(self._cleanup_repos))
633 633
634 634 def ensure_file(self, filename, content='Test content\n'):
635 635 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
636 636 commits = [
637 637 {'added': [
638 638 FileNode(filename, content=content),
639 639 ]},
640 640 ]
641 641 self._add_commits_to_repo(self.repo.scm_instance(), commits)
642 642
643 643 def enable_downloads(self):
644 644 repo = self.repo
645 645 repo.enable_downloads = True
646 646 Session().add(repo)
647 647 Session().commit()
648 648
649 649 def cleanup(self):
650 650 for repo_name in reversed(self._cleanup_repos):
651 651 self._fixture.destroy_repo(repo_name)
652 652
653 653 def _add_commits_to_repo(self, repo, commits):
654 654 commit_ids = _add_commits_to_repo(repo, commits)
655 655 if not commit_ids:
656 656 return
657 657 self._commit_ids = commit_ids
658 658
659 659 # Creating refs for Git to allow fetching them from remote repository
660 660 if self.alias == 'git':
661 661 refs = {}
662 662 for message in self._commit_ids:
663 663 # TODO: mikhail: do more special chars replacements
664 664 ref_name = 'refs/test-refs/{}'.format(
665 665 message.replace(' ', ''))
666 666 refs[ref_name] = self._commit_ids[message]
667 667 self._create_refs(repo, refs)
668 668
669 669 def _create_refs(self, repo, refs):
670 670 for ref_name in refs:
671 671 repo.set_refs(ref_name, refs[ref_name])
672 672
673 673
674 674 @pytest.fixture
675 675 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
676 676 """
677 677 Parametrized fixture which represents a single vcs backend implementation.
678 678
679 679 See the fixture `backend` for more details. This one implements the same
680 680 concept, but on vcs level. So it does not provide model instances etc.
681 681
682 682 Parameters are generated dynamically, see :func:`pytest_generate_tests`
683 683 for how this works.
684 684 """
685 685 if backend_alias not in request.config.getoption('--backends'):
686 686 pytest.skip("Backend %s not selected." % (backend_alias, ))
687 687
688 688 utils.check_xfail_backends(request.node, backend_alias)
689 689 utils.check_skip_backends(request.node, backend_alias)
690 690
691 691 repo_name = 'vcs_test_%s' % (backend_alias, )
692 692 repo_path = os.path.join(tests_tmp_path, repo_name)
693 693 backend = VcsBackend(
694 694 alias=backend_alias,
695 695 repo_path=repo_path,
696 696 test_name=request.node.name,
697 697 test_repo_container=test_repo)
698 698 request.addfinalizer(backend.cleanup)
699 699 return backend
700 700
701 701
702 702 @pytest.fixture
703 703 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
704 704 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
705 705
706 706
707 707 @pytest.fixture
708 708 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
709 709 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
710 710
711 711
712 712 @pytest.fixture
713 713 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
714 714 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
715 715
716 716
717 717 @pytest.fixture
718 718 def vcsbackend_random(vcsbackend_git):
719 719 """
720 720 Use this to express that your tests need "a vcsbackend".
721 721
722 722 The fixture `vcsbackend` would run the test multiple times for each
723 723 available vcs backend which is a pure waste of time if the test is
724 724 independent of the vcs backend type.
725 725 """
726 726 # TODO: johbo: Change this to pick a random backend
727 727 return vcsbackend_git
728 728
729 729
730 730 @pytest.fixture
731 731 def vcsbackend_stub(vcsbackend_git):
732 732 """
733 733 Use this to express that your test just needs a stub of a vcsbackend.
734 734
735 735 Plan is to eventually implement an in-memory stub to speed tests up.
736 736 """
737 737 return vcsbackend_git
738 738
739 739
740 740 class VcsBackend(object):
741 741 """
742 742 Represents the test configuration for one supported vcs backend.
743 743 """
744 744
745 745 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
746 746
747 747 def __init__(self, alias, repo_path, test_name, test_repo_container):
748 748 self.alias = alias
749 749 self._repo_path = repo_path
750 750 self._cleanup_repos = []
751 751 self._test_name = test_name
752 752 self._test_repo_container = test_repo_container
753 753
754 754 def __getitem__(self, key):
755 755 return self._test_repo_container(key, self.alias).scm_instance()
756 756
757 757 @property
758 758 def repo(self):
759 759 """
760 760 Returns the "current" repository. This is the vcs_test repo of the last
761 761 repo which has been created.
762 762 """
763 763 Repository = get_backend(self.alias)
764 764 return Repository(self._repo_path)
765 765
766 766 @property
767 767 def backend(self):
768 768 """
769 769 Returns the backend implementation class.
770 770 """
771 771 return get_backend(self.alias)
772 772
773 773 def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None):
774 774 repo_name = self._next_repo_name()
775 775 self._repo_path = get_new_dir(repo_name)
776 776 repo_class = get_backend(self.alias)
777 777 src_url = None
778 778 if _clone_repo:
779 779 src_url = _clone_repo.path
780 780 repo = repo_class(self._repo_path, create=True, src_url=src_url)
781 781 self._cleanup_repos.append(repo)
782 782
783 783 commits = commits or [
784 784 {'message': 'Commit %s of %s' % (x, repo_name)}
785 785 for x in xrange(number_of_commits)]
786 786 _add_commits_to_repo(repo, commits)
787 787 return repo
788 788
789 789 def clone_repo(self, repo):
790 790 return self.create_repo(_clone_repo=repo)
791 791
792 792 def cleanup(self):
793 793 for repo in self._cleanup_repos:
794 794 shutil.rmtree(repo.path)
795 795
796 796 def new_repo_path(self):
797 797 repo_name = self._next_repo_name()
798 798 self._repo_path = get_new_dir(repo_name)
799 799 return self._repo_path
800 800
801 801 def _next_repo_name(self):
802 802 return "%s_%s" % (
803 803 self.invalid_repo_name.sub('_', self._test_name),
804 804 len(self._cleanup_repos))
805 805
806 806 def add_file(self, repo, filename, content='Test content\n'):
807 807 imc = repo.in_memory_commit
808 808 imc.add(FileNode(filename, content=content))
809 809 imc.commit(
810 810 message=u'Automatic commit from vcsbackend fixture',
811 811 author=u'Automatic')
812 812
813 813 def ensure_file(self, filename, content='Test content\n'):
814 814 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
815 815 self.add_file(self.repo, filename, content)
816 816
817 817
818 818 def _add_commits_to_repo(vcs_repo, commits):
819 819 commit_ids = {}
820 820 if not commits:
821 821 return commit_ids
822 822
823 823 imc = vcs_repo.in_memory_commit
824 824 commit = None
825 825
826 826 for idx, commit in enumerate(commits):
827 827 message = unicode(commit.get('message', 'Commit %s' % idx))
828 828
829 829 for node in commit.get('added', []):
830 830 imc.add(FileNode(node.path, content=node.content))
831 831 for node in commit.get('changed', []):
832 832 imc.change(FileNode(node.path, content=node.content))
833 833 for node in commit.get('removed', []):
834 834 imc.remove(FileNode(node.path))
835 835
836 836 parents = [
837 837 vcs_repo.get_commit(commit_id=commit_ids[p])
838 838 for p in commit.get('parents', [])]
839 839
840 840 operations = ('added', 'changed', 'removed')
841 841 if not any((commit.get(o) for o in operations)):
842 842 imc.add(FileNode('file_%s' % idx, content=message))
843 843
844 844 commit = imc.commit(
845 845 message=message,
846 846 author=unicode(commit.get('author', 'Automatic')),
847 847 date=commit.get('date'),
848 848 branch=commit.get('branch'),
849 849 parents=parents)
850 850
851 851 commit_ids[commit.message] = commit.raw_id
852 852
853 853 return commit_ids
854 854
855 855
856 856 @pytest.fixture
857 857 def reposerver(request):
858 858 """
859 859 Allows to serve a backend repository
860 860 """
861 861
862 862 repo_server = RepoServer()
863 863 request.addfinalizer(repo_server.cleanup)
864 864 return repo_server
865 865
866 866
867 867 class RepoServer(object):
868 868 """
869 869 Utility to serve a local repository for the duration of a test case.
870 870
871 871 Supports only Subversion so far.
872 872 """
873 873
874 874 url = None
875 875
876 876 def __init__(self):
877 877 self._cleanup_servers = []
878 878
879 879 def serve(self, vcsrepo):
880 880 if vcsrepo.alias != 'svn':
881 881 raise TypeError("Backend %s not supported" % vcsrepo.alias)
882 882
883 883 proc = subprocess32.Popen(
884 884 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
885 885 '--root', vcsrepo.path])
886 886 self._cleanup_servers.append(proc)
887 887 self.url = 'svn://localhost'
888 888
889 889 def cleanup(self):
890 890 for proc in self._cleanup_servers:
891 891 proc.terminate()
892 892
893 893
894 894 @pytest.fixture
895 895 def pr_util(backend, request, config_stub):
896 896 """
897 897 Utility for tests of models and for functional tests around pull requests.
898 898
899 899 It gives an instance of :class:`PRTestUtility` which provides various
900 900 utility methods around one pull request.
901 901
902 902 This fixture uses `backend` and inherits its parameterization.
903 903 """
904 904
905 905 util = PRTestUtility(backend)
906 906
907 907 @request.addfinalizer
908 908 def cleanup():
909 909 util.cleanup()
910 910
911 911 return util
912 912
913 913
914 914 class PRTestUtility(object):
915 915
916 916 pull_request = None
917 917 pull_request_id = None
918 918 mergeable_patcher = None
919 919 mergeable_mock = None
920 920 notification_patcher = None
921 921
922 922 def __init__(self, backend):
923 923 self.backend = backend
924 924
925 925 def create_pull_request(
926 926 self, commits=None, target_head=None, source_head=None,
927 927 revisions=None, approved=False, author=None, mergeable=False,
928 928 enable_notifications=True, name_suffix=u'', reviewers=None,
929 929 title=u"Test", description=u"Description"):
930 930 self.set_mergeable(mergeable)
931 931 if not enable_notifications:
932 932 # mock notification side effect
933 933 self.notification_patcher = mock.patch(
934 934 'rhodecode.model.notification.NotificationModel.create')
935 935 self.notification_patcher.start()
936 936
937 937 if not self.pull_request:
938 938 if not commits:
939 939 commits = [
940 940 {'message': 'c1'},
941 941 {'message': 'c2'},
942 942 {'message': 'c3'},
943 943 ]
944 944 target_head = 'c1'
945 945 source_head = 'c2'
946 946 revisions = ['c2']
947 947
948 948 self.commit_ids = self.backend.create_master_repo(commits)
949 949 self.target_repository = self.backend.create_repo(
950 950 heads=[target_head], name_suffix=name_suffix)
951 951 self.source_repository = self.backend.create_repo(
952 952 heads=[source_head], name_suffix=name_suffix)
953 953 self.author = author or UserModel().get_by_username(
954 954 TEST_USER_ADMIN_LOGIN)
955 955
956 956 model = PullRequestModel()
957 957 self.create_parameters = {
958 958 'created_by': self.author,
959 959 'source_repo': self.source_repository.repo_name,
960 960 'source_ref': self._default_branch_reference(source_head),
961 961 'target_repo': self.target_repository.repo_name,
962 962 'target_ref': self._default_branch_reference(target_head),
963 963 'revisions': [self.commit_ids[r] for r in revisions],
964 964 'reviewers': reviewers or self._get_reviewers(),
965 965 'title': title,
966 966 'description': description,
967 967 }
968 968 self.pull_request = model.create(**self.create_parameters)
969 969 assert model.get_versions(self.pull_request) == []
970 970
971 971 self.pull_request_id = self.pull_request.pull_request_id
972 972
973 973 if approved:
974 974 self.approve()
975 975
976 976 Session().add(self.pull_request)
977 977 Session().commit()
978 978
979 979 return self.pull_request
980 980
981 981 def approve(self):
982 982 self.create_status_votes(
983 983 ChangesetStatus.STATUS_APPROVED,
984 984 *self.pull_request.reviewers)
985 985
986 986 def close(self):
987 987 PullRequestModel().close_pull_request(self.pull_request, self.author)
988 988
989 989 def _default_branch_reference(self, commit_message):
990 990 reference = '%s:%s:%s' % (
991 991 'branch',
992 992 self.backend.default_branch_name,
993 993 self.commit_ids[commit_message])
994 994 return reference
995 995
996 996 def _get_reviewers(self):
997 997 return [
998 998 (TEST_USER_REGULAR_LOGIN, ['default1'], False),
999 999 (TEST_USER_REGULAR2_LOGIN, ['default2'], False),
1000 1000 ]
1001 1001
1002 1002 def update_source_repository(self, head=None):
1003 1003 heads = [head or 'c3']
1004 1004 self.backend.pull_heads(self.source_repository, heads=heads)
1005 1005
1006 1006 def add_one_commit(self, head=None):
1007 1007 self.update_source_repository(head=head)
1008 1008 old_commit_ids = set(self.pull_request.revisions)
1009 1009 PullRequestModel().update_commits(self.pull_request)
1010 1010 commit_ids = set(self.pull_request.revisions)
1011 1011 new_commit_ids = commit_ids - old_commit_ids
1012 1012 assert len(new_commit_ids) == 1
1013 1013 return new_commit_ids.pop()
1014 1014
1015 1015 def remove_one_commit(self):
1016 1016 assert len(self.pull_request.revisions) == 2
1017 1017 source_vcs = self.source_repository.scm_instance()
1018 1018 removed_commit_id = source_vcs.commit_ids[-1]
1019 1019
1020 1020 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
1021 1021 # remove the if once that's sorted out.
1022 1022 if self.backend.alias == "git":
1023 1023 kwargs = {'branch_name': self.backend.default_branch_name}
1024 1024 else:
1025 1025 kwargs = {}
1026 1026 source_vcs.strip(removed_commit_id, **kwargs)
1027 1027
1028 1028 PullRequestModel().update_commits(self.pull_request)
1029 1029 assert len(self.pull_request.revisions) == 1
1030 1030 return removed_commit_id
1031 1031
1032 1032 def create_comment(self, linked_to=None):
1033 1033 comment = CommentsModel().create(
1034 1034 text=u"Test comment",
1035 1035 repo=self.target_repository.repo_name,
1036 1036 user=self.author,
1037 1037 pull_request=self.pull_request)
1038 1038 assert comment.pull_request_version_id is None
1039 1039
1040 1040 if linked_to:
1041 1041 PullRequestModel()._link_comments_to_version(linked_to)
1042 1042
1043 1043 return comment
1044 1044
1045 1045 def create_inline_comment(
1046 1046 self, linked_to=None, line_no=u'n1', file_path='file_1'):
1047 1047 comment = CommentsModel().create(
1048 1048 text=u"Test comment",
1049 1049 repo=self.target_repository.repo_name,
1050 1050 user=self.author,
1051 1051 line_no=line_no,
1052 1052 f_path=file_path,
1053 1053 pull_request=self.pull_request)
1054 1054 assert comment.pull_request_version_id is None
1055 1055
1056 1056 if linked_to:
1057 1057 PullRequestModel()._link_comments_to_version(linked_to)
1058 1058
1059 1059 return comment
1060 1060
1061 1061 def create_version_of_pull_request(self):
1062 1062 pull_request = self.create_pull_request()
1063 1063 version = PullRequestModel()._create_version_from_snapshot(
1064 1064 pull_request)
1065 1065 return version
1066 1066
1067 1067 def create_status_votes(self, status, *reviewers):
1068 1068 for reviewer in reviewers:
1069 1069 ChangesetStatusModel().set_status(
1070 1070 repo=self.pull_request.target_repo,
1071 1071 status=status,
1072 1072 user=reviewer.user_id,
1073 1073 pull_request=self.pull_request)
1074 1074
1075 1075 def set_mergeable(self, value):
1076 1076 if not self.mergeable_patcher:
1077 1077 self.mergeable_patcher = mock.patch.object(
1078 1078 VcsSettingsModel, 'get_general_settings')
1079 1079 self.mergeable_mock = self.mergeable_patcher.start()
1080 1080 self.mergeable_mock.return_value = {
1081 1081 'rhodecode_pr_merge_enabled': value}
1082 1082
1083 1083 def cleanup(self):
1084 1084 # In case the source repository is already cleaned up, the pull
1085 1085 # request will already be deleted.
1086 1086 pull_request = PullRequest().get(self.pull_request_id)
1087 1087 if pull_request:
1088 1088 PullRequestModel().delete(pull_request, pull_request.author)
1089 1089 Session().commit()
1090 1090
1091 1091 if self.notification_patcher:
1092 1092 self.notification_patcher.stop()
1093 1093
1094 1094 if self.mergeable_patcher:
1095 1095 self.mergeable_patcher.stop()
1096 1096
1097 1097
1098 1098 @pytest.fixture
1099 1099 def user_admin(pylonsapp):
1100 1100 """
1101 1101 Provides the default admin test user as an instance of `db.User`.
1102 1102 """
1103 1103 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1104 1104 return user
1105 1105
1106 1106
1107 1107 @pytest.fixture
1108 1108 def user_regular(pylonsapp):
1109 1109 """
1110 1110 Provides the default regular test user as an instance of `db.User`.
1111 1111 """
1112 1112 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1113 1113 return user
1114 1114
1115 1115
1116 1116 @pytest.fixture
1117 1117 def user_util(request, pylonsapp):
1118 1118 """
1119 1119 Provides a wired instance of `UserUtility` with integrated cleanup.
1120 1120 """
1121 1121 utility = UserUtility(test_name=request.node.name)
1122 1122 request.addfinalizer(utility.cleanup)
1123 1123 return utility
1124 1124
1125 1125
1126 1126 # TODO: johbo: Split this up into utilities per domain or something similar
1127 1127 class UserUtility(object):
1128 1128
1129 1129 def __init__(self, test_name="test"):
1130 1130 self._test_name = self._sanitize_name(test_name)
1131 1131 self.fixture = Fixture()
1132 1132 self.repo_group_ids = []
1133 1133 self.repos_ids = []
1134 1134 self.user_ids = []
1135 1135 self.user_group_ids = []
1136 1136 self.user_repo_permission_ids = []
1137 1137 self.user_group_repo_permission_ids = []
1138 1138 self.user_repo_group_permission_ids = []
1139 1139 self.user_group_repo_group_permission_ids = []
1140 1140 self.user_user_group_permission_ids = []
1141 1141 self.user_group_user_group_permission_ids = []
1142 1142 self.user_permissions = []
1143 1143
1144 1144 def _sanitize_name(self, name):
1145 1145 for char in ['[', ']']:
1146 1146 name = name.replace(char, '_')
1147 1147 return name
1148 1148
1149 1149 def create_repo_group(
1150 1150 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1151 1151 group_name = "{prefix}_repogroup_{count}".format(
1152 1152 prefix=self._test_name,
1153 1153 count=len(self.repo_group_ids))
1154 1154 repo_group = self.fixture.create_repo_group(
1155 1155 group_name, cur_user=owner)
1156 1156 if auto_cleanup:
1157 1157 self.repo_group_ids.append(repo_group.group_id)
1158 1158 return repo_group
1159 1159
1160 1160 def create_repo(self, owner=TEST_USER_ADMIN_LOGIN, parent=None,
1161 1161 auto_cleanup=True, repo_type='hg'):
1162 1162 repo_name = "{prefix}_repository_{count}".format(
1163 1163 prefix=self._test_name,
1164 1164 count=len(self.repos_ids))
1165 1165
1166 1166 repository = self.fixture.create_repo(
1167 1167 repo_name, cur_user=owner, repo_group=parent, repo_type=repo_type)
1168 1168 if auto_cleanup:
1169 1169 self.repos_ids.append(repository.repo_id)
1170 1170 return repository
1171 1171
1172 1172 def create_user(self, auto_cleanup=True, **kwargs):
1173 1173 user_name = "{prefix}_user_{count}".format(
1174 1174 prefix=self._test_name,
1175 1175 count=len(self.user_ids))
1176 1176 user = self.fixture.create_user(user_name, **kwargs)
1177 1177 if auto_cleanup:
1178 1178 self.user_ids.append(user.user_id)
1179 1179 return user
1180 1180
1181 1181 def create_user_with_group(self):
1182 1182 user = self.create_user()
1183 1183 user_group = self.create_user_group(members=[user])
1184 1184 return user, user_group
1185 1185
1186 1186 def create_user_group(self, owner=TEST_USER_ADMIN_LOGIN, members=None,
1187 1187 auto_cleanup=True, **kwargs):
1188 1188 group_name = "{prefix}_usergroup_{count}".format(
1189 1189 prefix=self._test_name,
1190 1190 count=len(self.user_group_ids))
1191 1191 user_group = self.fixture.create_user_group(
1192 1192 group_name, cur_user=owner, **kwargs)
1193 1193
1194 1194 if auto_cleanup:
1195 1195 self.user_group_ids.append(user_group.users_group_id)
1196 1196 if members:
1197 1197 for user in members:
1198 1198 UserGroupModel().add_user_to_group(user_group, user)
1199 1199 return user_group
1200 1200
1201 1201 def grant_user_permission(self, user_name, permission_name):
1202 1202 self._inherit_default_user_permissions(user_name, False)
1203 1203 self.user_permissions.append((user_name, permission_name))
1204 1204
1205 1205 def grant_user_permission_to_repo_group(
1206 1206 self, repo_group, user, permission_name):
1207 1207 permission = RepoGroupModel().grant_user_permission(
1208 1208 repo_group, user, permission_name)
1209 1209 self.user_repo_group_permission_ids.append(
1210 1210 (repo_group.group_id, user.user_id))
1211 1211 return permission
1212 1212
1213 1213 def grant_user_group_permission_to_repo_group(
1214 1214 self, repo_group, user_group, permission_name):
1215 1215 permission = RepoGroupModel().grant_user_group_permission(
1216 1216 repo_group, user_group, permission_name)
1217 1217 self.user_group_repo_group_permission_ids.append(
1218 1218 (repo_group.group_id, user_group.users_group_id))
1219 1219 return permission
1220 1220
1221 1221 def grant_user_permission_to_repo(
1222 1222 self, repo, user, permission_name):
1223 1223 permission = RepoModel().grant_user_permission(
1224 1224 repo, user, permission_name)
1225 1225 self.user_repo_permission_ids.append(
1226 1226 (repo.repo_id, user.user_id))
1227 1227 return permission
1228 1228
1229 1229 def grant_user_group_permission_to_repo(
1230 1230 self, repo, user_group, permission_name):
1231 1231 permission = RepoModel().grant_user_group_permission(
1232 1232 repo, user_group, permission_name)
1233 1233 self.user_group_repo_permission_ids.append(
1234 1234 (repo.repo_id, user_group.users_group_id))
1235 1235 return permission
1236 1236
1237 1237 def grant_user_permission_to_user_group(
1238 1238 self, target_user_group, user, permission_name):
1239 1239 permission = UserGroupModel().grant_user_permission(
1240 1240 target_user_group, user, permission_name)
1241 1241 self.user_user_group_permission_ids.append(
1242 1242 (target_user_group.users_group_id, user.user_id))
1243 1243 return permission
1244 1244
1245 1245 def grant_user_group_permission_to_user_group(
1246 1246 self, target_user_group, user_group, permission_name):
1247 1247 permission = UserGroupModel().grant_user_group_permission(
1248 1248 target_user_group, user_group, permission_name)
1249 1249 self.user_group_user_group_permission_ids.append(
1250 1250 (target_user_group.users_group_id, user_group.users_group_id))
1251 1251 return permission
1252 1252
1253 1253 def revoke_user_permission(self, user_name, permission_name):
1254 1254 self._inherit_default_user_permissions(user_name, True)
1255 1255 UserModel().revoke_perm(user_name, permission_name)
1256 1256
1257 1257 def _inherit_default_user_permissions(self, user_name, value):
1258 1258 user = UserModel().get_by_username(user_name)
1259 1259 user.inherit_default_permissions = value
1260 1260 Session().add(user)
1261 1261 Session().commit()
1262 1262
1263 1263 def cleanup(self):
1264 1264 self._cleanup_permissions()
1265 1265 self._cleanup_repos()
1266 1266 self._cleanup_repo_groups()
1267 1267 self._cleanup_user_groups()
1268 1268 self._cleanup_users()
1269 1269
1270 1270 def _cleanup_permissions(self):
1271 1271 if self.user_permissions:
1272 1272 for user_name, permission_name in self.user_permissions:
1273 1273 self.revoke_user_permission(user_name, permission_name)
1274 1274
1275 1275 for permission in self.user_repo_permission_ids:
1276 1276 RepoModel().revoke_user_permission(*permission)
1277 1277
1278 1278 for permission in self.user_group_repo_permission_ids:
1279 1279 RepoModel().revoke_user_group_permission(*permission)
1280 1280
1281 1281 for permission in self.user_repo_group_permission_ids:
1282 1282 RepoGroupModel().revoke_user_permission(*permission)
1283 1283
1284 1284 for permission in self.user_group_repo_group_permission_ids:
1285 1285 RepoGroupModel().revoke_user_group_permission(*permission)
1286 1286
1287 1287 for permission in self.user_user_group_permission_ids:
1288 1288 UserGroupModel().revoke_user_permission(*permission)
1289 1289
1290 1290 for permission in self.user_group_user_group_permission_ids:
1291 1291 UserGroupModel().revoke_user_group_permission(*permission)
1292 1292
1293 1293 def _cleanup_repo_groups(self):
1294 1294 def _repo_group_compare(first_group_id, second_group_id):
1295 1295 """
1296 1296 Gives higher priority to the groups with the most complex paths
1297 1297 """
1298 1298 first_group = RepoGroup.get(first_group_id)
1299 1299 second_group = RepoGroup.get(second_group_id)
1300 1300 first_group_parts = (
1301 1301 len(first_group.group_name.split('/')) if first_group else 0)
1302 1302 second_group_parts = (
1303 1303 len(second_group.group_name.split('/')) if second_group else 0)
1304 1304 return cmp(second_group_parts, first_group_parts)
1305 1305
1306 1306 sorted_repo_group_ids = sorted(
1307 1307 self.repo_group_ids, cmp=_repo_group_compare)
1308 1308 for repo_group_id in sorted_repo_group_ids:
1309 1309 self.fixture.destroy_repo_group(repo_group_id)
1310 1310
1311 1311 def _cleanup_repos(self):
1312 1312 sorted_repos_ids = sorted(self.repos_ids)
1313 1313 for repo_id in sorted_repos_ids:
1314 1314 self.fixture.destroy_repo(repo_id)
1315 1315
1316 1316 def _cleanup_user_groups(self):
1317 1317 def _user_group_compare(first_group_id, second_group_id):
1318 1318 """
1319 1319 Gives higher priority to the groups with the most complex paths
1320 1320 """
1321 1321 first_group = UserGroup.get(first_group_id)
1322 1322 second_group = UserGroup.get(second_group_id)
1323 1323 first_group_parts = (
1324 1324 len(first_group.users_group_name.split('/'))
1325 1325 if first_group else 0)
1326 1326 second_group_parts = (
1327 1327 len(second_group.users_group_name.split('/'))
1328 1328 if second_group else 0)
1329 1329 return cmp(second_group_parts, first_group_parts)
1330 1330
1331 1331 sorted_user_group_ids = sorted(
1332 1332 self.user_group_ids, cmp=_user_group_compare)
1333 1333 for user_group_id in sorted_user_group_ids:
1334 1334 self.fixture.destroy_user_group(user_group_id)
1335 1335
1336 1336 def _cleanup_users(self):
1337 1337 for user_id in self.user_ids:
1338 1338 self.fixture.destroy_user(user_id)
1339 1339
1340 1340
1341 1341 # TODO: Think about moving this into a pytest-pyro package and make it a
1342 1342 # pytest plugin
1343 1343 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1344 1344 def pytest_runtest_makereport(item, call):
1345 1345 """
1346 1346 Adding the remote traceback if the exception has this information.
1347 1347
1348 1348 VCSServer attaches this information as the attribute `_vcs_server_traceback`
1349 1349 to the exception instance.
1350 1350 """
1351 1351 outcome = yield
1352 1352 report = outcome.get_result()
1353 1353 if call.excinfo:
1354 1354 _add_vcsserver_remote_traceback(report, call.excinfo.value)
1355 1355
1356 1356
1357 1357 def _add_vcsserver_remote_traceback(report, exc):
1358 1358 vcsserver_traceback = getattr(exc, '_vcs_server_traceback', None)
1359 1359
1360 1360 if vcsserver_traceback:
1361 1361 section = 'VCSServer remote traceback ' + report.when
1362 1362 report.sections.append((section, vcsserver_traceback))
1363 1363
1364 1364
1365 1365 @pytest.fixture(scope='session')
1366 1366 def testrun():
1367 1367 return {
1368 1368 'uuid': uuid.uuid4(),
1369 1369 'start': datetime.datetime.utcnow().isoformat(),
1370 1370 'timestamp': int(time.time()),
1371 1371 }
1372 1372
1373 1373
1374 1374 @pytest.fixture(autouse=True)
1375 1375 def collect_appenlight_stats(request, testrun):
1376 1376 """
1377 1377 This fixture reports memory consumtion of single tests.
1378 1378
1379 1379 It gathers data based on `psutil` and sends them to Appenlight. The option
1380 1380 ``--ae`` has te be used to enable this fixture and the API key for your
1381 1381 application has to be provided in ``--ae-key``.
1382 1382 """
1383 1383 try:
1384 1384 # cygwin cannot have yet psutil support.
1385 1385 import psutil
1386 1386 except ImportError:
1387 1387 return
1388 1388
1389 1389 if not request.config.getoption('--appenlight'):
1390 1390 return
1391 1391 else:
1392 1392 # Only request the pylonsapp fixture if appenlight tracking is
1393 1393 # enabled. This will speed up a test run of unit tests by 2 to 3
1394 1394 # seconds if appenlight is not enabled.
1395 1395 pylonsapp = request.getfuncargvalue("pylonsapp")
1396 1396 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1397 1397 client = AppenlightClient(
1398 1398 url=url,
1399 1399 api_key=request.config.getoption('--appenlight-api-key'),
1400 1400 namespace=request.node.nodeid,
1401 1401 request=str(testrun['uuid']),
1402 1402 testrun=testrun)
1403 1403
1404 1404 client.collect({
1405 1405 'message': "Starting",
1406 1406 })
1407 1407
1408 1408 server_and_port = pylonsapp.config['vcs.server']
1409 1409 protocol = pylonsapp.config['vcs.server.protocol']
1410 1410 server = create_vcsserver_proxy(server_and_port, protocol)
1411 1411 with server:
1412 1412 vcs_pid = server.get_pid()
1413 1413 server.run_gc()
1414 1414 vcs_process = psutil.Process(vcs_pid)
1415 1415 mem = vcs_process.memory_info()
1416 1416 client.tag_before('vcsserver.rss', mem.rss)
1417 1417 client.tag_before('vcsserver.vms', mem.vms)
1418 1418
1419 1419 test_process = psutil.Process()
1420 1420 mem = test_process.memory_info()
1421 1421 client.tag_before('test.rss', mem.rss)
1422 1422 client.tag_before('test.vms', mem.vms)
1423 1423
1424 1424 client.tag_before('time', time.time())
1425 1425
1426 1426 @request.addfinalizer
1427 1427 def send_stats():
1428 1428 client.tag_after('time', time.time())
1429 1429 with server:
1430 1430 gc_stats = server.run_gc()
1431 1431 for tag, value in gc_stats.items():
1432 1432 client.tag_after(tag, value)
1433 1433 mem = vcs_process.memory_info()
1434 1434 client.tag_after('vcsserver.rss', mem.rss)
1435 1435 client.tag_after('vcsserver.vms', mem.vms)
1436 1436
1437 1437 mem = test_process.memory_info()
1438 1438 client.tag_after('test.rss', mem.rss)
1439 1439 client.tag_after('test.vms', mem.vms)
1440 1440
1441 1441 client.collect({
1442 1442 'message': "Finished",
1443 1443 })
1444 1444 client.send_stats()
1445 1445
1446 1446 return client
1447 1447
1448 1448
1449 1449 class AppenlightClient():
1450 1450
1451 1451 url_template = '{url}?protocol_version=0.5'
1452 1452
1453 1453 def __init__(
1454 1454 self, url, api_key, add_server=True, add_timestamp=True,
1455 1455 namespace=None, request=None, testrun=None):
1456 1456 self.url = self.url_template.format(url=url)
1457 1457 self.api_key = api_key
1458 1458 self.add_server = add_server
1459 1459 self.add_timestamp = add_timestamp
1460 1460 self.namespace = namespace
1461 1461 self.request = request
1462 1462 self.server = socket.getfqdn(socket.gethostname())
1463 1463 self.tags_before = {}
1464 1464 self.tags_after = {}
1465 1465 self.stats = []
1466 1466 self.testrun = testrun or {}
1467 1467
1468 1468 def tag_before(self, tag, value):
1469 1469 self.tags_before[tag] = value
1470 1470
1471 1471 def tag_after(self, tag, value):
1472 1472 self.tags_after[tag] = value
1473 1473
1474 1474 def collect(self, data):
1475 1475 if self.add_server:
1476 1476 data.setdefault('server', self.server)
1477 1477 if self.add_timestamp:
1478 1478 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1479 1479 if self.namespace:
1480 1480 data.setdefault('namespace', self.namespace)
1481 1481 if self.request:
1482 1482 data.setdefault('request', self.request)
1483 1483 self.stats.append(data)
1484 1484
1485 1485 def send_stats(self):
1486 1486 tags = [
1487 1487 ('testrun', self.request),
1488 1488 ('testrun.start', self.testrun['start']),
1489 1489 ('testrun.timestamp', self.testrun['timestamp']),
1490 1490 ('test', self.namespace),
1491 1491 ]
1492 1492 for key, value in self.tags_before.items():
1493 1493 tags.append((key + '.before', value))
1494 1494 try:
1495 1495 delta = self.tags_after[key] - value
1496 1496 tags.append((key + '.delta', delta))
1497 1497 except Exception:
1498 1498 pass
1499 1499 for key, value in self.tags_after.items():
1500 1500 tags.append((key + '.after', value))
1501 1501 self.collect({
1502 1502 'message': "Collected tags",
1503 1503 'tags': tags,
1504 1504 })
1505 1505
1506 1506 response = requests.post(
1507 1507 self.url,
1508 1508 headers={
1509 1509 'X-appenlight-api-key': self.api_key},
1510 1510 json=self.stats,
1511 1511 )
1512 1512
1513 1513 if not response.status_code == 200:
1514 1514 pprint.pprint(self.stats)
1515 1515 print response.headers
1516 1516 print response.text
1517 1517 raise Exception('Sending to appenlight failed')
1518 1518
1519 1519
1520 1520 @pytest.fixture
1521 1521 def gist_util(request, pylonsapp):
1522 1522 """
1523 1523 Provides a wired instance of `GistUtility` with integrated cleanup.
1524 1524 """
1525 1525 utility = GistUtility()
1526 1526 request.addfinalizer(utility.cleanup)
1527 1527 return utility
1528 1528
1529 1529
1530 1530 class GistUtility(object):
1531 1531 def __init__(self):
1532 1532 self.fixture = Fixture()
1533 1533 self.gist_ids = []
1534 1534
1535 1535 def create_gist(self, **kwargs):
1536 1536 gist = self.fixture.create_gist(**kwargs)
1537 1537 self.gist_ids.append(gist.gist_id)
1538 1538 return gist
1539 1539
1540 1540 def cleanup(self):
1541 1541 for id_ in self.gist_ids:
1542 1542 self.fixture.destroy_gists(str(id_))
1543 1543
1544 1544
1545 1545 @pytest.fixture
1546 1546 def enabled_backends(request):
1547 1547 backends = request.config.option.backends
1548 1548 return backends[:]
1549 1549
1550 1550
1551 1551 @pytest.fixture
1552 1552 def settings_util(request):
1553 1553 """
1554 1554 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1555 1555 """
1556 1556 utility = SettingsUtility()
1557 1557 request.addfinalizer(utility.cleanup)
1558 1558 return utility
1559 1559
1560 1560
1561 1561 class SettingsUtility(object):
1562 1562 def __init__(self):
1563 1563 self.rhodecode_ui_ids = []
1564 1564 self.rhodecode_setting_ids = []
1565 1565 self.repo_rhodecode_ui_ids = []
1566 1566 self.repo_rhodecode_setting_ids = []
1567 1567
1568 1568 def create_repo_rhodecode_ui(
1569 1569 self, repo, section, value, key=None, active=True, cleanup=True):
1570 1570 key = key or hashlib.sha1(
1571 1571 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1572 1572
1573 1573 setting = RepoRhodeCodeUi()
1574 1574 setting.repository_id = repo.repo_id
1575 1575 setting.ui_section = section
1576 1576 setting.ui_value = value
1577 1577 setting.ui_key = key
1578 1578 setting.ui_active = active
1579 1579 Session().add(setting)
1580 1580 Session().commit()
1581 1581
1582 1582 if cleanup:
1583 1583 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1584 1584 return setting
1585 1585
1586 1586 def create_rhodecode_ui(
1587 1587 self, section, value, key=None, active=True, cleanup=True):
1588 1588 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1589 1589
1590 1590 setting = RhodeCodeUi()
1591 1591 setting.ui_section = section
1592 1592 setting.ui_value = value
1593 1593 setting.ui_key = key
1594 1594 setting.ui_active = active
1595 1595 Session().add(setting)
1596 1596 Session().commit()
1597 1597
1598 1598 if cleanup:
1599 1599 self.rhodecode_ui_ids.append(setting.ui_id)
1600 1600 return setting
1601 1601
1602 1602 def create_repo_rhodecode_setting(
1603 1603 self, repo, name, value, type_, cleanup=True):
1604 1604 setting = RepoRhodeCodeSetting(
1605 1605 repo.repo_id, key=name, val=value, type=type_)
1606 1606 Session().add(setting)
1607 1607 Session().commit()
1608 1608
1609 1609 if cleanup:
1610 1610 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1611 1611 return setting
1612 1612
1613 1613 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1614 1614 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1615 1615 Session().add(setting)
1616 1616 Session().commit()
1617 1617
1618 1618 if cleanup:
1619 1619 self.rhodecode_setting_ids.append(setting.app_settings_id)
1620 1620
1621 1621 return setting
1622 1622
1623 1623 def cleanup(self):
1624 1624 for id_ in self.rhodecode_ui_ids:
1625 1625 setting = RhodeCodeUi.get(id_)
1626 1626 Session().delete(setting)
1627 1627
1628 1628 for id_ in self.rhodecode_setting_ids:
1629 1629 setting = RhodeCodeSetting.get(id_)
1630 1630 Session().delete(setting)
1631 1631
1632 1632 for id_ in self.repo_rhodecode_ui_ids:
1633 1633 setting = RepoRhodeCodeUi.get(id_)
1634 1634 Session().delete(setting)
1635 1635
1636 1636 for id_ in self.repo_rhodecode_setting_ids:
1637 1637 setting = RepoRhodeCodeSetting.get(id_)
1638 1638 Session().delete(setting)
1639 1639
1640 1640 Session().commit()
1641 1641
1642 1642
1643 1643 @pytest.fixture
1644 1644 def no_notifications(request):
1645 1645 notification_patcher = mock.patch(
1646 1646 'rhodecode.model.notification.NotificationModel.create')
1647 1647 notification_patcher.start()
1648 1648 request.addfinalizer(notification_patcher.stop)
1649 1649
1650 1650
1651 1651 @pytest.fixture(scope='session')
1652 1652 def repeat(request):
1653 1653 """
1654 1654 The number of repetitions is based on this fixture.
1655 1655
1656 1656 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1657 1657 tests are not too slow in our default test suite.
1658 1658 """
1659 1659 return request.config.getoption('--repeat')
1660 1660
1661 1661
1662 1662 @pytest.fixture
1663 1663 def rhodecode_fixtures():
1664 1664 return Fixture()
1665 1665
1666 1666
1667 1667 @pytest.fixture
1668 1668 def request_stub():
1669 1669 """
1670 1670 Stub request object.
1671 1671 """
1672 1672 request = pyramid.testing.DummyRequest()
1673 1673 request.scheme = 'https'
1674 1674 return request
1675 1675
1676 1676
1677 1677 @pytest.fixture
1678 1678 def context_stub():
1679 1679 """
1680 1680 Stub context object.
1681 1681 """
1682 1682 context = pyramid.testing.DummyResource()
1683 1683 return context
1684 1684
1685 1685
1686 1686 @pytest.fixture
1687 1687 def config_stub(request, request_stub):
1688 1688 """
1689 1689 Set up pyramid.testing and return the Configurator.
1690 1690 """
1691 1691 config = pyramid.testing.setUp(request=request_stub)
1692 1692 add_test_routes(config)
1693 1693
1694 1694 @request.addfinalizer
1695 1695 def cleanup():
1696 1696 pyramid.testing.tearDown()
1697 1697
1698 1698 return config
1699 1699
1700 1700
1701 1701 @pytest.fixture
1702 1702 def StubIntegrationType():
1703 1703 class _StubIntegrationType(IntegrationTypeBase):
1704 1704 """ Test integration type class """
1705 1705
1706 1706 key = 'test'
1707 1707 display_name = 'Test integration type'
1708 1708 description = 'A test integration type for testing'
1709 1709 icon = 'test_icon_html_image'
1710 1710
1711 1711 def __init__(self, settings):
1712 1712 super(_StubIntegrationType, self).__init__(settings)
1713 1713 self.sent_events = [] # for testing
1714 1714
1715 1715 def send_event(self, event):
1716 1716 self.sent_events.append(event)
1717 1717
1718 1718 def settings_schema(self):
1719 1719 class SettingsSchema(colander.Schema):
1720 1720 test_string_field = colander.SchemaNode(
1721 1721 colander.String(),
1722 1722 missing=colander.required,
1723 1723 title='test string field',
1724 1724 )
1725 1725 test_int_field = colander.SchemaNode(
1726 1726 colander.Int(),
1727 1727 title='some integer setting',
1728 1728 )
1729 1729 return SettingsSchema()
1730 1730
1731 1731
1732 1732 integration_type_registry.register_integration_type(_StubIntegrationType)
1733 1733 return _StubIntegrationType
1734 1734
1735 1735 @pytest.fixture
1736 1736 def stub_integration_settings():
1737 1737 return {
1738 1738 'test_string_field': 'some data',
1739 1739 'test_int_field': 100,
1740 1740 }
1741 1741
1742 1742
1743 1743 @pytest.fixture
1744 1744 def repo_integration_stub(request, repo_stub, StubIntegrationType,
1745 1745 stub_integration_settings):
1746 1746 integration = IntegrationModel().create(
1747 1747 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1748 1748 name='test repo integration',
1749 1749 repo=repo_stub, repo_group=None, child_repos_only=None)
1750 1750
1751 1751 @request.addfinalizer
1752 1752 def cleanup():
1753 1753 IntegrationModel().delete(integration)
1754 1754
1755 1755 return integration
1756 1756
1757 1757
1758 1758 @pytest.fixture
1759 1759 def repogroup_integration_stub(request, test_repo_group, StubIntegrationType,
1760 1760 stub_integration_settings):
1761 1761 integration = IntegrationModel().create(
1762 1762 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1763 1763 name='test repogroup integration',
1764 1764 repo=None, repo_group=test_repo_group, child_repos_only=True)
1765 1765
1766 1766 @request.addfinalizer
1767 1767 def cleanup():
1768 1768 IntegrationModel().delete(integration)
1769 1769
1770 1770 return integration
1771 1771
1772 1772
1773 1773 @pytest.fixture
1774 1774 def repogroup_recursive_integration_stub(request, test_repo_group,
1775 1775 StubIntegrationType, stub_integration_settings):
1776 1776 integration = IntegrationModel().create(
1777 1777 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1778 1778 name='test recursive repogroup integration',
1779 1779 repo=None, repo_group=test_repo_group, child_repos_only=False)
1780 1780
1781 1781 @request.addfinalizer
1782 1782 def cleanup():
1783 1783 IntegrationModel().delete(integration)
1784 1784
1785 1785 return integration
1786 1786
1787 1787
1788 1788 @pytest.fixture
1789 1789 def global_integration_stub(request, StubIntegrationType,
1790 1790 stub_integration_settings):
1791 1791 integration = IntegrationModel().create(
1792 1792 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1793 1793 name='test global integration',
1794 1794 repo=None, repo_group=None, child_repos_only=None)
1795 1795
1796 1796 @request.addfinalizer
1797 1797 def cleanup():
1798 1798 IntegrationModel().delete(integration)
1799 1799
1800 1800 return integration
1801 1801
1802 1802
1803 1803 @pytest.fixture
1804 1804 def root_repos_integration_stub(request, StubIntegrationType,
1805 1805 stub_integration_settings):
1806 1806 integration = IntegrationModel().create(
1807 1807 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1808 1808 name='test global integration',
1809 1809 repo=None, repo_group=None, child_repos_only=True)
1810 1810
1811 1811 @request.addfinalizer
1812 1812 def cleanup():
1813 1813 IntegrationModel().delete(integration)
1814 1814
1815 1815 return integration
1816 1816
1817 1817
1818 1818 @pytest.fixture
1819 1819 def local_dt_to_utc():
1820 1820 def _factory(dt):
1821 1821 return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(
1822 1822 dateutil.tz.tzutc()).replace(tzinfo=None)
1823 1823 return _factory
1824 1824
1825 1825
1826 1826 @pytest.fixture
1827 1827 def disable_anonymous_user(request, pylonsapp):
1828 1828 set_anonymous_access(False)
1829 1829
1830 1830 @request.addfinalizer
1831 1831 def cleanup():
1832 1832 set_anonymous_access(True)
General Comments 0
You need to be logged in to leave comments. Login now